From 089b0125e628939015b36124e0a54cb8046a183e Mon Sep 17 00:00:00 2001 From: Adam Poulemanos Date: Sat, 5 Sep 2026 15:57:21 -0400 Subject: [PATCH 1/2] fix(openapi-mcp): restore scoped release classification --- packages/openapi-mcp/package.json | 10 ++++ scripts/generate.mts | 14 ++++- scripts/openapi-mcp-release.test.ts | 91 +++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/openapi-mcp/package.json b/packages/openapi-mcp/package.json index eab2a98..22a0693 100644 --- a/packages/openapi-mcp/package.json +++ b/packages/openapi-mcp/package.json @@ -111,6 +111,16 @@ "release": "patch" }, { + "scope": "!(openapi-mcp)", + "release": false + }, + { + "scope": null, + "release": false + }, + { + "scope": "openapi-mcp", + "type": "!(feat|fix|perf)", "release": false } ] diff --git a/scripts/generate.mts b/scripts/generate.mts index 0fe72b5..34f0ce0 100755 --- a/scripts/generate.mts +++ b/scripts/generate.mts @@ -87,7 +87,19 @@ function buildPluginReleaseConfig( const scopes = [canonical, ...aliases]; const releaseRules = [ ...scopes.flatMap(releaseRulesForScope), - { release: false }, + // A catchall suppresses eligible minor/patch rules as well. Keep the + // OpenAPI package exclusions disjoint from its release-bearing commits. + ...(canonical === "openapi-mcp" + ? [ + { scope: `!(${scopes.join("|")})`, release: false }, + { scope: null, release: false }, + ...scopes.map((scope) => ({ + scope, + type: "!(feat|fix|perf)", + release: false, + })), + ] + : [{ release: false }]), ]; const tagPrefix = npmScope ? `@${npmScope}/` : ""; return { diff --git a/scripts/openapi-mcp-release.test.ts b/scripts/openapi-mcp-release.test.ts index 2bec3aa..43faddd 100644 --- a/scripts/openapi-mcp-release.test.ts +++ b/scripts/openapi-mcp-release.test.ts @@ -4,12 +4,103 @@ import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; +import { analyzeCommits } from "@semantic-release/commit-analyzer"; +import manifest from "../packages/openapi-mcp/package.json"; import * as release from "./openapi-mcp-release.mjs"; import { createReleaseAdapter, verifyAuditBinding, } from "./openapi-mcp-release.mjs"; +const analyzerConfig = manifest.release.plugins.find( + (plugin) => + Array.isArray(plugin) && plugin[0] === "@semantic-release/commit-analyzer", +)?.[1]; + +async function analyzeRelease(messages: string[]) { + expect(analyzerConfig).toBeDefined(); + return analyzeCommits(analyzerConfig, { + cwd: fileURLToPath(new URL("../packages/openapi-mcp", import.meta.url)), + commits: messages.map((message, index) => ({ + hash: String(index), + message, + })), + logger: { log() {} }, + }); +} + +// These fixtures catch suppressed package releases and accidental default-rule fallbacks. +test.each([ + ["feat(openapi-mcp): add search", "minor"], + ["fix(openapi-mcp): repair search", "patch"], + ["perf(openapi-mcp): speed up search", "patch"], + ["feat(openapi-mcp)!: replace search", "major"], + [ + "fix(openapi-mcp): replace search\n\nBREAKING CHANGE: remove old search", + "major", + ], + ["docs(openapi-mcp)!: redefine contract", "major"], + [ + "docs(openapi-mcp): redefine contract\n\nBREAKING CHANGE: remove old contract", + "major", + ], + ["docs(openapi-mcp): explain search", null], + ["chore(openapi-mcp): clean up tooling", null], + ["test(openapi-mcp): cover search", null], + [ + "revert(openapi-mcp): undo change\n\nThis reverts commit abcdef1234567890.", + null, + ], + ["feat(other): add feature", null], + ["fix(other): repair feature", null], + ["perf(other): speed up feature", null], + ["feat(other)!: replace feature", null], + ["docs(other): redefine contract\n\nBREAKING CHANGE: remove contract", null], + ["feat: add feature", null], + ["fix: repair feature", null], + ["perf: speed up feature", null], + ["feat!: replace feature", null], + ["docs: redefine contract\n\nBREAKING CHANGE: remove contract", null], + ["revert: undo change\n\nThis reverts commit abcdef1234567890.", null], + ["feat(openapi-mcp-extra): add feature", null], + ["fix(prefix-openapi-mcp): repair feature", null], + ["feat(openapi-mcp-extra)!: replace feature", null], + ["feat(OPENAPI-MCP): add feature", null], + ["feat(openapi-mcp,other): add feature", null], + ["Merge pull request #123 from feature", null], +])("package release policy for %s returns %s", async (message, expected) => { + expect(await analyzeRelease([message])).toBe(expected); +}); + +test.each([ + [ + ["fix(openapi-mcp): repair search", "feat(other)!: replace feature"], + "patch", + ], + [ + [ + "fix(openapi-mcp): repair search", + "feat(openapi-mcp): add search", + "docs(openapi-mcp): explain search", + ], + "minor", + ], + [ + [ + "feat(openapi-mcp): add search", + "docs(openapi-mcp)!: redefine contract", + "perf(openapi-mcp): speed up search", + ], + "major", + ], +])( + "package release takes maximum eligible severity for %j", + async (messages, expected) => { + expect(await analyzeRelease(messages)).toBe(expected); + expect(await analyzeRelease([...messages].reverse())).toBe(expected); + }, +); + test("release CLI initializes its configured adapter through the installed plugin loader", () => { const result = spawnSync( "node", From fda132aadd7eca4d4068a8811d9f957dfa9c82c8 Mon Sep 17 00:00:00 2001 From: Adam Poulemanos Date: Sat, 5 Sep 2026 16:01:02 -0400 Subject: [PATCH 2/2] fix(openapi-mcp): exclude slash-scoped release fallbacks --- packages/openapi-mcp/package.json | 4 ++-- scripts/generate.mts | 5 +++-- scripts/openapi-mcp-release.test.ts | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/openapi-mcp/package.json b/packages/openapi-mcp/package.json index 22a0693..5e3dc48 100644 --- a/packages/openapi-mcp/package.json +++ b/packages/openapi-mcp/package.json @@ -111,7 +111,7 @@ "release": "patch" }, { - "scope": "!(openapi-mcp)", + "scope": "!@(openapi-mcp)", "release": false }, { @@ -120,7 +120,7 @@ }, { "scope": "openapi-mcp", - "type": "!(feat|fix|perf)", + "type": "!@(feat|fix|perf)", "release": false } ] diff --git a/scripts/generate.mts b/scripts/generate.mts index 34f0ce0..6a55520 100755 --- a/scripts/generate.mts +++ b/scripts/generate.mts @@ -89,13 +89,14 @@ function buildPluginReleaseConfig( ...scopes.flatMap(releaseRulesForScope), // A catchall suppresses eligible minor/patch rules as well. Keep the // OpenAPI package exclusions disjoint from its release-bearing commits. + // Negate the whole match so scopes containing slashes are excluded too. ...(canonical === "openapi-mcp" ? [ - { scope: `!(${scopes.join("|")})`, release: false }, + { scope: `!@(${scopes.join("|")})`, release: false }, { scope: null, release: false }, ...scopes.map((scope) => ({ scope, - type: "!(feat|fix|perf)", + type: "!@(feat|fix|perf)", release: false, })), ] diff --git a/scripts/openapi-mcp-release.test.ts b/scripts/openapi-mcp-release.test.ts index 43faddd..d617cfb 100644 --- a/scripts/openapi-mcp-release.test.ts +++ b/scripts/openapi-mcp-release.test.ts @@ -52,6 +52,28 @@ test.each([ null, ], ["feat(other): add feature", null], + ["feat(foo/bar): add feature", null], + ["fix(other/module): repair feature", null], + [ + "docs(other/module): redefine contract\n\nBREAKING CHANGE: remove contract", + null, + ], + ["feat(openapi-mcp/other): add feature", null], + ["fix(.other): repair feature", null], + ["perf(.openapi-mcp): speed up feature", null], + ["feat(foo/bar)!: replace feature", null], + ["feat(openapi-mcp/other)!: replace feature", null], + ["docs(.other): redefine contract\n\nBREAKING CHANGE: remove contract", null], + ["docs/a(openapi-mcp): explain search", null], + [".docs(openapi-mcp): explain search", null], + [ + "revert/a(openapi-mcp): undo change\n\nThis reverts commit abcdef1234567890.", + null, + ], + [ + ".revert(openapi-mcp): undo change\n\nThis reverts commit abcdef1234567890.", + null, + ], ["fix(other): repair feature", null], ["perf(other): speed up feature", null], ["feat(other)!: replace feature", null],