Skip to content

Prepare MCP 0.2.5 public release candidate#61

Closed
Keesan12 wants to merge 5 commits into
mainfrom
codex/mcp-v025-release-candidate
Closed

Prepare MCP 0.2.5 public release candidate#61
Keesan12 wants to merge 5 commits into
mainfrom
codex/mcp-v025-release-candidate

Conversation

@Keesan12
Copy link
Copy Markdown
Owner

Summary

  • Prepare @martinloop/mcp@0.2.5 public release candidate on current public main.
  • Harden MCP cockpit persisted-record handling for legacy taskless run records.
  • Clean public README/CONTEXT release copy and align release tests with current public surfaces.
  • Keep GitHub Actions trusted publishing path with prepublish MCP proof gates.

Verification

  • pnpm release:matrix:local passed on Windows lane.
  • Matrix covered install, build, full tests, pnpm oss:validate, pnpm public:smoke, pnpm --filter @martinloop/mcp smoke:pack, and pnpm mcp:published:smoke:pack.
  • Public forbidden-term scan over README.md AGENTS.md CONTEXT.md docs packages .github package.json found no local path/internal repo/private workspace contamination.
  • npm view martin-loop live latest: 0.1.7; npm view @martinloop/mcp live latest: 0.2.0. This PR prepares the next MCP line, 0.2.5.

Release Notes

  • This PR does not publish npm or create tags. It prepares the public candidate for CI, review, and the guarded tag/workflow release path.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request expands the Martin Loop operator surface by introducing new CLI and MCP commands for diagnostics, run validation, and run-store management, including doctor, preflight, triage, and dossier. The MCP server has been refactored to support the full Model Context Protocol discovery surface with versioned resources and prompts. Key technical changes include hardened path matching in the safety leash, platform-aware MCP configuration generation, and enhanced artifact persistence. Review feedback identifies several hardening opportunities: ensuring loopTimestamp handles invalid dates gracefully, aligning isLoopRecord validation between packages for consistency, correctly escaping the ? wildcard in glob-to-regex conversion, and adding error handling to JSONL parsing to prevent crashes on malformed lines.

Comment on lines +550 to +552
function loopTimestamp(loop: LoopRecord): number {
return Date.parse(loop.updatedAt || loop.createdAt || "");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The loopTimestamp function uses Date.parse which can return NaN if the input string is not a valid date. This can lead to unstable sorting or incorrect deduplication in upsertLoop. It should be hardened to return a default value (e.g., 0) when parsing fails.

Suggested change
function loopTimestamp(loop: LoopRecord): number {
return Date.parse(loop.updatedAt || loop.createdAt || "");
}
function loopTimestamp(loop: LoopRecord): number {
const parsed = Date.parse(loop.updatedAt || loop.createdAt || "");
return Number.isNaN(parsed) ? 0 : parsed;
}

Comment on lines +505 to +525
function isLoopRecord(value: unknown): value is LoopRecord {
if (typeof value !== "object" || value === null) {
return false;
}

const candidate = value as Partial<LoopRecord>;
return (
typeof candidate.loopId === "string" &&
typeof candidate.workspaceId === "string" &&
typeof candidate.projectId === "string" &&
typeof candidate.status === "string" &&
typeof candidate.lifecycleState === "string" &&
typeof candidate.createdAt === "string" &&
typeof candidate.updatedAt === "string" &&
typeof candidate.task?.title === "string" &&
typeof candidate.task?.objective === "string" &&
Array.isArray(candidate.attempts) &&
Array.isArray(candidate.events) &&
Array.isArray(candidate.artifacts)
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The isLoopRecord validation in the CLI package is significantly less thorough than the corresponding validation in the MCP package. It lacks checks for the structure and types of nested objects like budget and cost. Aligning these validation routines would improve consistency and prevent runtime errors when processing malformed records.

Comment on lines +433 to +440
const regexStr = normalizedPattern
.replace(/[.+^${}()|[\]\\]/gu, "\\$&")
.replace(/\*\*/gu, "__DOUBLESTAR__")
.replace(/\*/gu, "[^/]*")
.replace(/__DOUBLESTAR__/gu, ".*");

return (
normalizedFile === normalizedPattern ||
normalizedFile.startsWith(`${normalizedPattern.replace(/\/$/u, "")}/`)
);
return new RegExp(`^${regexStr}$`, "u").test(normalizedFile);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The glob-to-regex conversion in matchesPathPattern does not handle the ? character correctly. It is not escaped in the initial replace call, meaning it will be interpreted as a regex quantifier (making the preceding character optional) instead of a single-character wildcard as expected in globs.

Suggested change
const regexStr = normalizedPattern
.replace(/[.+^${}()|[\]\\]/gu, "\\$&")
.replace(/\*\*/gu, "__DOUBLESTAR__")
.replace(/\*/gu, "[^/]*")
.replace(/__DOUBLESTAR__/gu, ".*");
return (
normalizedFile === normalizedPattern ||
normalizedFile.startsWith(`${normalizedPattern.replace(/\/$/u, "")}/`)
);
return new RegExp(`^${regexStr}$`, "u").test(normalizedFile);
}
const regexStr = normalizedPattern
.replace(/[.+^${}()|[\]\\?]/gu, "\\$&")
.replace(/\\\?/gu, "[^/]")
.replace(/\*\*/gu, "__DOUBLESTAR__")
.replace(/\*/gu, "[^/]*")
.replace(/__DOUBLESTAR__/gu, ".*");

Comment on lines +492 to +503
}

async function readCanonicalLoopRecord(file: string): Promise<InspectableLoopRecord> {
try {
const text = await readFile(file, "utf8");
return validateInspectableLoopRecord(JSON.parse(text));
} catch {
throw storeUnreadableError();
}
}

async function readRecordsFromFile(file: string): Promise<InspectableLoopRecord[]> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In parseUnknownLoopValues, the JSON.parse call for .jsonl files is not wrapped in a try-catch block. If a single line in the file contains malformed JSON, the entire command will crash. Using a more resilient parsing approach that skips malformed lines would improve robustness.

function parseUnknownLoopValues(contents: string, file: string): unknown[] {
  if (file.endsWith(".jsonl")) {
    return contents
      .split(/\r?\n/u)
      .map((line) => line.trim())
      .filter(Boolean)
      .map((line) => {
        try {
          return JSON.parse(line);
        } catch {
          return null;
        }
      })
      .filter((val) => val !== null);
  }

  const parsed = JSON.parse(contents) as unknown;
  return Array.isArray(parsed) ? parsed : [parsed];
}

@Keesan12 Keesan12 force-pushed the codex/mcp-v025-release-candidate branch from e95cefc to fe5ad1f Compare May 21, 2026 21:54
@Keesan12 Keesan12 closed this May 21, 2026
@Keesan12 Keesan12 deleted the codex/mcp-v025-release-candidate branch May 21, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant