@martinloop/mcp 0.8.5 → 0.8.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.
@@ -54,7 +54,7 @@ export declare function buildArcadeToolDefinitions(capabilities: unknown): reado
54
54
  readonly displayOutcome: {
55
55
  readonly anyOf: readonly [{
56
56
  readonly type: "string";
57
- readonly enum: readonly ["VERIFIED", "STOPPED", "NEEDS REVIEW"];
57
+ readonly enum: readonly ["VERIFIED", "STOPPED", "NEEDS_REVIEW"];
58
58
  }, {
59
59
  readonly type: "null";
60
60
  }];
@@ -1,3 +1,4 @@
1
+ import { VERIFIED_HANDOFF_OUTCOMES } from "../vendor/contracts/index.js";
1
2
  export const MARTIN_ARCADE_UI_EXTENSION_ID = "io.modelcontextprotocol/ui";
2
3
  export const MARTIN_ARCADE_MIME_TYPE = "text/html;profile=mcp-app";
3
4
  export const MARTIN_ARCADE_RESOURCE_URI = "ui://martinloop/arcade/index.html";
@@ -51,7 +52,7 @@ export function buildArcadeToolDefinitions(capabilities) {
51
52
  completed: { type: "boolean", description: "Whether the run is outside an active lifecycle state." },
52
53
  displayOutcome: {
53
54
  anyOf: [
54
- { type: "string", enum: ["VERIFIED", "STOPPED", "NEEDS REVIEW"] },
55
+ { type: "string", enum: [...VERIFIED_HANDOFF_OUTCOMES] },
55
56
  { type: "null" }
56
57
  ],
57
58
  description: "Terminal verified-handoff outcome, or null while the run is active."
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.5";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.8.7";
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by build — do not edit by hand.
2
2
  // Source of truth: packages/mcp/package.json#version
3
- export const MARTIN_MCP_PACKAGE_VERSION = "0.8.5";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.8.7";
@@ -435,7 +435,7 @@ function validateGetVerificationResultsInput(args) {
435
435
  function validateRunDossierInput(args) {
436
436
  const record = requireObject(args);
437
437
  assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format"]);
438
- const base = validateGetRunInput(args);
438
+ const base = validateGetRunInput(pickKeys(record, RUN_SELECTOR_KEYS));
439
439
  return {
440
440
  ...base,
441
441
  ...optionalEnumAsObject(record.format, "format", ["json", "md", "github-pr"])
@@ -447,7 +447,7 @@ function validateEvalInput(args) {
447
447
  function validateRunControlInput(args) {
448
448
  const record = requireObject(args);
449
449
  assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "reason", "requestedBy"]);
450
- const base = validateGetRunInput(args);
450
+ const base = validateGetRunInput(pickKeys(record, RUN_SELECTOR_KEYS));
451
451
  return {
452
452
  ...base,
453
453
  ...optionalString(record.reason, "reason"),
@@ -457,7 +457,7 @@ function validateRunControlInput(args) {
457
457
  function validateCreatePrInput(args) {
458
458
  const record = requireObject(args);
459
459
  assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format", "title", "base", "execute"]);
460
- const base = validateRunDossierInput(args);
460
+ const base = validateRunDossierInput(pickKeys(record, [...RUN_SELECTOR_KEYS, "format"]));
461
461
  return {
462
462
  ...base,
463
463
  ...optionalString(record.title, "title"),
@@ -468,12 +468,17 @@ function validateCreatePrInput(args) {
468
468
  function validateReviewPrInput(args) {
469
469
  const record = requireObject(args);
470
470
  assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format", "prBody"]);
471
- const base = validateRunDossierInput(args);
471
+ const base = validateRunDossierInput(pickKeys(record, [...RUN_SELECTOR_KEYS, "format"]));
472
472
  return {
473
473
  ...base,
474
474
  ...optionalString(record.prBody, "prBody")
475
475
  };
476
476
  }
477
+ const RUN_SELECTOR_KEYS = ["file", "loopId", "runsDir", "latest"];
478
+ // Base validators check their own allowed keys, so composed validators hand them only those keys.
479
+ function pickKeys(record, keys) {
480
+ return Object.fromEntries(Object.entries(record).filter(([key]) => keys.includes(key)));
481
+ }
477
482
  function requireObject(value) {
478
483
  if (!value || typeof value !== "object" || Array.isArray(value)) {
479
484
  throw invalidArgumentsError("Tool arguments must be an object.");
package/dist/server.js CHANGED
@@ -501,6 +501,25 @@ const preflightOutputSchema = {
501
501
  },
502
502
  required: ["available", "detail"]
503
503
  },
504
+ // Present when the Codex launch probe ran on a live host.
505
+ codexDiagnostics: {
506
+ type: "object",
507
+ additionalProperties: true,
508
+ properties: {
509
+ selectedPath: { type: "string" },
510
+ hostPlatform: { type: "string" },
511
+ installKind: { type: "string" },
512
+ nativeInstallValid: { type: "boolean" },
513
+ invocationMode: { type: "string" },
514
+ sandboxMode: { type: "string" },
515
+ sandboxCompatible: { type: "boolean" },
516
+ launchReady: { type: "boolean" },
517
+ summary: { type: "string" },
518
+ remediation: { type: "string" },
519
+ candidateProbeResults: { type: "array", items: { type: "object", additionalProperties: true } }
520
+ },
521
+ required: ["launchReady", "summary"]
522
+ },
504
523
  runsRoot: { type: "string" },
505
524
  pathScope: {
506
525
  type: "object",
@@ -775,9 +794,21 @@ const controlOutputSchema = {
775
794
  controlId: { type: "string", description: "Unique receipt ID for this control action." },
776
795
  requestedAt: { type: "string", description: "ISO 8601 timestamp when the control was recorded." },
777
796
  requestedBy: { type: "string", description: "Identity that requested the control, if provided." },
778
- reason: { type: "string", description: "Human-readable reason for the control, if provided." }
797
+ reason: { type: "string", description: "Human-readable reason for the control, if provided." },
798
+ ok: { type: "boolean", description: "Whether the control receipt was recorded." },
799
+ summary: { type: "string", description: "Human-readable result of the control request." },
800
+ requestedAction: {
801
+ type: "string",
802
+ enum: ["pause", "cancel", "continue"],
803
+ description: "The control action that was requested."
804
+ },
805
+ state: {
806
+ type: "object",
807
+ additionalProperties: true,
808
+ description: "Run control state after this request, including all recorded receipts."
809
+ }
779
810
  },
780
- required: ["loopId", "action", "controlId", "requestedAt"]
811
+ required: ["loopId", "action", "controlId", "requestedAt", "ok", "summary", "requestedAction", "state"]
781
812
  };
782
813
  const evalOutputSchema = {
783
814
  type: "object",
@@ -830,6 +861,17 @@ const prSummaryOutputSchema = {
830
861
  },
831
862
  required: ["loopId", "title", "body", "grade", "score"]
832
863
  };
864
+ const createPrOutputSchema = {
865
+ ...prSummaryOutputSchema,
866
+ properties: {
867
+ ...prSummaryOutputSchema.properties,
868
+ execute: { type: "boolean", description: "Whether gh was asked to create the pull request." },
869
+ created: { type: "boolean", description: "Whether a pull request was created." },
870
+ branch: { type: "string", description: "Current branch of the run's repository, when known." },
871
+ url: { type: "string", description: "URL of the created pull request." }
872
+ },
873
+ required: [...prSummaryOutputSchema.required, "execute", "created"]
874
+ };
833
875
  const prReviewOutputSchema = {
834
876
  type: "object",
835
877
  additionalProperties: false,
@@ -1505,7 +1547,7 @@ export function createMartinMcpServer(serverInfo) {
1505
1547
  execute: { type: "boolean" }
1506
1548
  }
1507
1549
  },
1508
- outputSchema: prSummaryOutputSchema
1550
+ outputSchema: createPrOutputSchema
1509
1551
  },
1510
1552
  {
1511
1553
  name: "martin_review_pr",
@@ -33,6 +33,18 @@ export async function martinCreatePrTool(input) {
33
33
  ...(branch ? { branch } : {})
34
34
  };
35
35
  }
36
+ // Opening a PR claims the work is done. Only a VERIFIED run with intact receipts qualifies.
37
+ const dossier = await martinRunDossierTool(input);
38
+ const outcome = dossier.verifiedHandoff.outcome;
39
+ const integrity = dossier.receiptIntegrity.state;
40
+ if (outcome !== "VERIFIED" || integrity !== "verified") {
41
+ throw new MartinToolError("policy_blocked", `Refusing to open a PR: run ${summary.loopId} is ${outcome} with receipt integrity ${integrity}.`, {
42
+ category: "policy_blocked",
43
+ suggestion: "Open a PR only from a VERIFIED run, or rerun martin_create_pr with execute=false to preview it.",
44
+ retryable: false,
45
+ details: { outcome, receiptIntegrity: integrity }
46
+ });
47
+ }
36
48
  const gh = detectCliAvailability("gh");
37
49
  if (!gh.available) {
38
50
  throw new MartinToolError("engine_unavailable", "GitHub CLI is not available on PATH.", {
@@ -1,5 +1,5 @@
1
1
  import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
2
- import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, } from "../vendor/core/index.js";
2
+ import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, describeMalformedVerifier, } from "../vendor/core/index.js";
3
3
  import { MARTIN_MCP_PACKAGE_VERSION } from "../package-version.js";
4
4
  import { resolveSafeRepoRoot } from "../server-validation.js";
5
5
  import { createSkippedCliAvailability, formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
@@ -27,6 +27,9 @@ export async function martinPreflightTool(input) {
27
27
  })
28
28
  : undefined;
29
29
  const warnings = [];
30
+ const malformedVerifiers = (input.verificationPlan ?? [])
31
+ .map((command) => describeMalformedVerifier(command))
32
+ .filter((reason) => reason !== undefined);
30
33
  const allowedPaths = input.allowedPaths ?? [];
31
34
  const deniedPaths = input.deniedPaths ?? [];
32
35
  const overlappingScopes = allowedPaths.filter((candidate) => deniedPaths.includes(candidate));
@@ -47,6 +50,7 @@ export async function martinPreflightTool(input) {
47
50
  if ((input.verificationPlan?.length ?? 0) === 0) {
48
51
  warnings.push("No verificationPlan was provided; Martin can run, but completion confidence will be lower.");
49
52
  }
53
+ warnings.push(...malformedVerifiers);
50
54
  if ((input.allowedPaths?.length ?? 0) === 0) {
51
55
  warnings.push("No allowedPaths were provided; Martin will rely on the broader repo root scope.");
52
56
  }
@@ -58,7 +62,7 @@ export async function martinPreflightTool(input) {
58
62
  const policy = buildPolicyPackDefinition(input.policyPack, signals);
59
63
  const engineReady = !executionMode.liveMode ||
60
64
  (engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
61
- const ok = engineReady;
65
+ const ok = engineReady && malformedVerifiers.length === 0;
62
66
  const receiptScope = {
63
67
  invocationRoot: workspaceRoot,
64
68
  workingDirectory,
@@ -69,9 +73,9 @@ export async function martinPreflightTool(input) {
69
73
  ok,
70
74
  summary: ok
71
75
  ? `Preflight ready for ${engine} in ${workingDirectory} with a ${formatUsd(budget.maxUsd)} budget cap and ${runContract.risk.level} risk.`
72
- : `Preflight blocked: ${engine === "codex" && codexProbe && !codexProbe.ok
76
+ : `Preflight blocked: ${malformedVerifiers[0] ?? (engine === "codex" && codexProbe && !codexProbe.ok
73
77
  ? codexProbe.summary
74
- : `${engine} is not available for live execution.`}`,
78
+ : `${engine} is not available for live execution.`)}`,
75
79
  warnings,
76
80
  receiptScope,
77
81
  scope: {
@@ -29,6 +29,11 @@ export interface MartinRunControlOutput {
29
29
  loopId: string;
30
30
  requestedAction: MartinControlAction;
31
31
  state: MartinRunControlState;
32
+ action: MartinControlAction;
33
+ controlId: string;
34
+ requestedAt: string;
35
+ reason?: string;
36
+ requestedBy?: string;
32
37
  }
33
38
  export declare function createRunControlReceipt(action: MartinControlAction, input: MartinRunControlRequestInput): Promise<MartinRunControlOutput>;
34
39
  export declare function readRunControlState(detailOrInput: DetailedLoopSource | MartinRunControlRequestInput): Promise<MartinRunControlState>;
@@ -27,7 +27,12 @@ export async function createRunControlReceipt(action, input) {
27
27
  : `Continue request recorded for ${detail.loop.loopId}.`,
28
28
  loopId: detail.loop.loopId,
29
29
  requestedAction: action,
30
- state
30
+ state,
31
+ action: receipt.action,
32
+ controlId: receipt.controlId,
33
+ requestedAt: receipt.requestedAt,
34
+ ...(receipt.reason ? { reason: receipt.reason } : {}),
35
+ ...(receipt.requestedBy ? { requestedBy: receipt.requestedBy } : {})
31
36
  };
32
37
  }
33
38
  export async function readRunControlState(detailOrInput) {
@@ -152,7 +152,7 @@ function extractClaudeObservedModel(stdout) {
152
152
  return undefined;
153
153
  }
154
154
  function extractUsage(parsed, modelLabel, modelSource) {
155
- if (!parsed?.usage) {
155
+ if (!parsed) {
156
156
  return normalizeUsage({
157
157
  actualUsd: 0,
158
158
  tokensIn: 0,
@@ -160,11 +160,12 @@ function extractUsage(parsed, modelLabel, modelSource) {
160
160
  provenance: "unavailable"
161
161
  });
162
162
  }
163
- const promptTokens = parsed.usage.inputTokens ?? parsed.usage.input_tokens ?? 0;
164
- const cacheCreationInputTokens = parsed.usage.cacheCreationInputTokens ?? parsed.usage.cache_creation_input_tokens ?? 0;
165
- const cachedInputTokens = parsed.usage.cacheReadInputTokens ?? parsed.usage.cache_read_input_tokens ?? 0;
163
+ const usage = parsed.usage ?? {};
164
+ const promptTokens = usage.inputTokens ?? usage.input_tokens ?? 0;
165
+ const cacheCreationInputTokens = usage.cacheCreationInputTokens ?? usage.cache_creation_input_tokens ?? 0;
166
+ const cachedInputTokens = usage.cacheReadInputTokens ?? usage.cache_read_input_tokens ?? 0;
166
167
  const tokensIn = promptTokens + cachedInputTokens + cacheCreationInputTokens;
167
- const tokensOut = parsed.usage.outputTokens ?? parsed.usage.output_tokens ?? 0;
168
+ const tokensOut = usage.outputTokens ?? usage.output_tokens ?? 0;
168
169
  const pricingResolution = resolveModelPricing(modelLabel);
169
170
  // Prefer Claude's own authoritative total_cost_usd (present on the final
170
171
  // `result` event in json/stream-json output) over our pricing-table estimate,
@@ -210,6 +211,39 @@ function extractUsage(parsed, modelLabel, modelSource) {
210
211
  }
211
212
  });
212
213
  }
214
+ /**
215
+ * Returns the provider's failure message when the Codex JSONL stream shows a failed turn:
216
+ * an explicit `turn.failed`, or an `error` event with no `turn.completed`. Undefined otherwise.
217
+ */
218
+ function findCodexTurnFailure(stdout) {
219
+ let completed = false;
220
+ let turnFailed = false;
221
+ let message;
222
+ for (const line of stdout.split(/\r?\n/u)) {
223
+ let event;
224
+ try {
225
+ event = JSON.parse(line.trim());
226
+ }
227
+ catch {
228
+ continue;
229
+ }
230
+ if (event.type === "turn.completed")
231
+ completed = true;
232
+ if (event.type === "turn.failed") {
233
+ turnFailed = true;
234
+ message = String(event.error?.message ?? message ?? "Codex turn failed.");
235
+ }
236
+ else if (event.type === "error") {
237
+ message ??= String(event.message ?? "Codex reported an error.");
238
+ }
239
+ }
240
+ if (turnFailed)
241
+ return message;
242
+ return message !== undefined && !completed ? message : undefined;
243
+ }
244
+ function codexTurnCompleted(stdout) {
245
+ return /"type"\s*:\s*"turn\.completed"/u.test(stdout);
246
+ }
213
247
  function extractCodexJsonlResult(stdout, modelLabel) {
214
248
  const events = stdout
215
249
  .split(/\r?\n/u)
@@ -620,6 +654,13 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate,
620
654
  * the final `result` event, which carries the same `result`/`usage`/
621
655
  * `total_cost_usd` fields as the single-blob `json` format.
622
656
  */
657
+ function findClaudeResultError(result) {
658
+ const errorSubtype = result.subtype?.startsWith("error") === true;
659
+ if (result.is_error !== true && !errorSubtype) {
660
+ return undefined;
661
+ }
662
+ return truncate(result.result ?? result.error ?? result.subtype ?? "unknown error", 500);
663
+ }
623
664
  function parseStreamJsonResult(stdout) {
624
665
  let lastResult;
625
666
  for (const rawLine of stdout.split(/\r?\n/u)) {
@@ -829,8 +870,40 @@ export function createAgentCliAdapter(options) {
829
870
  const geminiJsonResult = !supportsJsonOutput && options.command === "gemini"
830
871
  ? extractGeminiJsonResult(agentResult.stdout, options.model)
831
872
  : undefined;
873
+ // A failed Codex turn did no verified work: never let the verifier run against an
874
+ // untouched workspace and certify it.
875
+ const codexTurnFailure = options.command === "codex" ? findCodexTurnFailure(agentResult.stdout) : undefined;
876
+ if (codexTurnFailure !== undefined) {
877
+ const failureMessage = `codex turn failed: ${codexTurnFailure}`;
878
+ return {
879
+ status: "failed",
880
+ summary: "codex turn failed before verifier execution.",
881
+ usage: codexJsonlResult?.usage ?? normalizeUsage({
882
+ actualUsd: 0,
883
+ tokensIn: 0,
884
+ tokensOut: 0,
885
+ provenance: "unavailable"
886
+ }),
887
+ verification: { passed: false, summary: `Verifier not run: ${failureMessage}` },
888
+ failure: { message: failureMessage }
889
+ };
890
+ }
891
+ // A Claude session that ended in an error (API failure, max turns) did no
892
+ // verified work: never let the verifier certify the untouched workspace.
893
+ const claudeResultError = parsed ? findClaudeResultError(parsed) : undefined;
894
+ if (claudeResultError !== undefined) {
895
+ const failureMessage = `${options.command} reported an error: ${claudeResultError}`;
896
+ return {
897
+ status: "failed",
898
+ summary: `${options.command} session ended in an error before verifier execution.`,
899
+ usage: extractUsage(parsed, options.model, options.model ? "explicit_override" : "agent_default"),
900
+ verification: { passed: false, summary: `Verifier not run: ${failureMessage}` },
901
+ failure: { message: failureMessage }
902
+ };
903
+ }
832
904
  const producedStructuredCompletion = parsed?.result !== undefined ||
833
- codexJsonlResult !== undefined ||
905
+ (codexJsonlResult !== undefined &&
906
+ (agentResult.exitCode === 0 || codexTurnCompleted(agentResult.stdout))) ||
834
907
  geminiJsonResult !== undefined;
835
908
  if (agentResult.exitCode !== 0 && !producedStructuredCompletion) {
836
909
  const failureMessage = formatPreVerifierSubprocessFailure(options.command, agentResult.stderr || agentResult.stdout, agentResult.exitCode);
@@ -51,6 +51,8 @@ export interface ApprovalPolicy {
51
51
  migrations?: boolean;
52
52
  configChanges?: boolean;
53
53
  externalWrites?: boolean;
54
+ /** Allows an attempt to remove or rewrite lines in existing test files. */
55
+ testChanges?: boolean;
54
56
  }
55
57
  export interface VerificationStep {
56
58
  /** Shell command to run. */
@@ -430,9 +432,23 @@ export interface RollbackBoundaryArtifact {
430
432
  strategy: RollbackBoundaryStrategy;
431
433
  capturedAt: string;
432
434
  headRef?: string;
435
+ /** Branch HEAD pointed at when the boundary was captured; absent when detached. */
436
+ headBranch?: string;
433
437
  trackedDirtyFiles: string[];
434
438
  untrackedFiles: string[];
435
439
  snapshots: RollbackFileSnapshot[];
440
+ /**
441
+ * Git-ignored files present at the boundary. Dependency and build output
442
+ * directories are recorded as a single collapsed `dir/` entry.
443
+ */
444
+ ignoredFiles?: RollbackIgnoredFileStamp[];
445
+ }
446
+ export interface RollbackIgnoredFileStamp {
447
+ path: string;
448
+ size: number;
449
+ mtimeMs: number;
450
+ /** Content hash for non-output ignored files; contents are never persisted. */
451
+ sha256?: string;
436
452
  }
437
453
  export type RollbackOutcomeStatus = "restored" | "not_required" | "failed" | "unavailable";
438
454
  export interface RollbackOutcomeArtifact {
@@ -450,6 +466,11 @@ export interface RollbackOutcomeArtifact {
450
466
  };
451
467
  restoredFiles: string[];
452
468
  deletedFiles: string[];
469
+ /** Set when attempt-created commits were rewound to the boundary HEAD. */
470
+ rewoundHead?: {
471
+ from: string;
472
+ to: string;
473
+ };
453
474
  error?: string;
454
475
  }
455
476
  export { createGovernanceSnapshot } from "./governance.js";
@@ -456,7 +456,7 @@ function budgetWidened(parent, child) {
456
456
  && (child.maxTokens === undefined || child.maxTokens > parent.maxTokens));
457
457
  }
458
458
  function approvalWidened(parent, child) {
459
- const keys = ["dependencyAdds", "migrations", "configChanges", "externalWrites"];
459
+ const keys = ["dependencyAdds", "migrations", "configChanges", "externalWrites", "testChanges"];
460
460
  return keys.some((key) => child[key] === true && parent[key] !== true);
461
461
  }
462
462
  function addDuplicateErrors(values, code, path, errors) {
@@ -5,6 +5,21 @@ import { homedir } from "node:os";
5
5
  import { extname, join, relative } from "node:path";
6
6
  import { promisify } from "node:util";
7
7
  const execFileAsync = promisify(execFile);
8
+ // Callables provided by the JavaScript language, Node.js, browsers, and common test runners.
9
+ // They are never declared in the repository, so they must not count as ungrounded symbols.
10
+ const STANDARD_CALLABLE_GLOBALS = new Set([
11
+ // CommonJS / Node.js
12
+ "require", "setTimeout", "setInterval", "setImmediate", "clearTimeout", "clearInterval",
13
+ "clearImmediate", "queueMicrotask", "structuredClone", "fetch",
14
+ // ECMAScript globals
15
+ "parseInt", "parseFloat", "isNaN", "isFinite", "encodeURIComponent", "decodeURIComponent",
16
+ "encodeURI", "decodeURI", "escape", "unescape", "eval",
17
+ // Browser globals
18
+ "alert", "confirm", "prompt", "atob", "btoa", "requestAnimationFrame", "cancelAnimationFrame",
19
+ // Test runners (node:test, vitest, jest, mocha, chai)
20
+ "describe", "suite", "test", "it", "expect", "assert", "before", "after", "beforeEach",
21
+ "afterEach", "beforeAll", "afterAll", "context", "specify", "should"
22
+ ]);
8
23
  const TEXT_EXTENSIONS = new Set([
9
24
  ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
10
25
  ".json", ".md", ".txt", ".yaml", ".yml", ".py", ".go",
@@ -284,17 +299,30 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
284
299
  "while",
285
300
  "switch",
286
301
  "catch",
287
- "typeof"
302
+ "typeof",
303
+ "super",
304
+ "yield",
305
+ "delete",
306
+ "throw",
307
+ "instanceof",
308
+ "constructor"
288
309
  ]);
289
310
  const addedLines = [...diff.matchAll(addedLinePattern)].map((match) => match[1] ?? "");
290
311
  const declaredSymbols = new Set();
291
312
  const declarationPattern = /\b(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
313
+ // Class and object-literal method definitions: `name(args) {`, optionally async/static/get/set.
314
+ const methodDefinitionPattern = /(?:^|[{,])\s*(?:(?:public|private|protected|static|async|get|set|override)\s+)*([A-Za-z_][A-Za-z0-9_]*)\s*\([^()]*\)\s*(?::[^{=]+)?\{/gu;
292
315
  for (const line of addedLines) {
293
316
  for (const declarationMatch of line.matchAll(declarationPattern)) {
294
317
  const symbol = declarationMatch[1];
295
318
  if (symbol)
296
319
  declaredSymbols.add(symbol.toLowerCase());
297
320
  }
321
+ for (const methodMatch of line.matchAll(methodDefinitionPattern)) {
322
+ const methodName = methodMatch[1];
323
+ if (methodName && !symbolKeywords.has(methodName))
324
+ declaredSymbols.add(methodName.toLowerCase());
325
+ }
298
326
  if (/^\s*import\b/u.test(line)) {
299
327
  const importClause = line.match(/^\s*import\s+(.+?)\s+from\s+['"]/u)?.[1] ?? "";
300
328
  for (const importSymbol of importClause.matchAll(/\b(?:as\s+)?([A-Za-z_][A-Za-z0-9_]*)\b/g)) {
@@ -334,6 +362,8 @@ export function scanPatchForGroundingViolations(diff, index, options = {}) {
334
362
  continue;
335
363
  if (symbolKeywords.has(symbol))
336
364
  continue;
365
+ if (STANDARD_CALLABLE_GLOBALS.has(symbol))
366
+ continue;
337
367
  if (/^[A-Z]/u.test(symbol))
338
368
  continue;
339
369
  if (declaredSymbols.has(symbol.toLowerCase()))
@@ -2,14 +2,15 @@ import { type ApprovalPolicy, type AgentExecutionIntent, type ContextHandoffRece
2
2
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
3
3
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
4
4
  import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
5
- import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
5
+ import { captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary } from "./rollback.js";
6
6
  import { type RunStore } from "./persistence/index.js";
7
7
  import { type ExitPolicyOverrides } from "./exits.js";
8
8
  import { type ExitSignalSource } from "./exit-signal.js";
9
9
  import { type VerifierExecutionBinding } from "./verified-handoff.js";
10
10
  export type { AgentExecutionIntent, ApprovalPolicy, BudgetPreflightEstimate, BudgetSettlement, CostProvenance, EvidenceVector, ExecutionProfile, FailureClass, InterventionType, PatchDecision, PatchDecisionArtifact, PatchDecisionReasonCode, PatchScore, MutationMode, RollbackBoundaryArtifact, RollbackBoundaryStrategy, RollbackFileSnapshot, RollbackOutcomeArtifact, RollbackOutcomeStatus, PolicyPhase, CallStage, AgentRole, FirstDelta, RoutingEconomics } from "../contracts/index.js";
11
11
  export { DEFAULT_AGENT_EXECUTION_INTENT, DEFAULT_PROVIDER_EXECUTION_TIMEOUT_MS, GOVERNED_AUTONOMOUS_BOUNDARY, normalizeProviderExecutionTimeoutMs } from "../contracts/index.js";
12
- export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, restoreRollbackBoundary };
12
+ export { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, evaluateVerificationLeash, evaluateFilesystemLeash, evaluateChangeApprovalLeash, evaluateSecretLeash, resolveExecutionProfile, redactSecretsFromText, buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations, captureRollbackBoundary, listAttemptChangedFilesSinceBoundary, listIgnoredFileChangesSinceBoundary, restoreRollbackBoundary };
13
+ export { describeMalformedVerifier } from "./verifier-input.js";
13
14
  export type { BudgetPreflightDecision, BudgetPreflightInput, CostGovernorState, EvidenceVectorInput, EvaluatedPatchDecision, ExitDecision, FailureAssessment, PatchDecisionInput, RecoveryDecision, RecoveryRecipe } from "./policy.js";
14
15
  export type { ResolvedExecutionProfile, SafetyLeashDecision, SafetyViolation } from "./leash.js";
15
16
  export type { GroundingScanResult, GroundingViolation, GroundingViolationKind, RepoGroundingHit, RepoGroundingIndex } from "./grounding.js";
@@ -24,7 +25,7 @@ export { calculateAvoidedUsd, calculateLoopAvoidedUsd } from "./savings.js";
24
25
  export type { AvoidedUsdInput } from "./savings.js";
25
26
  export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
26
27
  export type { RouteDecision, RouteClassificationInput } from "./routing.js";
27
- export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
28
+ export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode, isTestPath, findWeakenedTestFiles } from "./test-integrity.js";
28
29
  export type { TestIntegrityReport, TestIntegrityFinding } from "./test-integrity.js";
29
30
  export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
30
31
  export type { RepoStyleProfile } from "./repo-analyzer.js";
@@ -32,7 +33,7 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
32
33
  export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
33
34
  export { compilePromptPacket } from "./compiler.js";
34
35
  export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
35
- export { buildVerifiedHandoff, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
36
+ export { buildVerifiedHandoff, NO_FILE_CHANGES_WARNING, deriveVerifiedHandoffLifecycleTruth, deriveVerifiedHandoffExecutionBoundary, resolveVerifiedHandoffOutcome, toTestIntegrityVerdict, verifierActuallyPassed, } from "./verified-handoff.js";
36
37
  export type { BoundVerifierEvidence, BuildVerifiedHandoffInput, VerifierExecutionBinding, } from "./verified-handoff.js";
37
38
  export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
38
39
  export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";