fix: enforce release metadata consistency (#297)

This commit is contained in:
Trevin Chow
2026-03-17 19:17:25 -07:00
committed by GitHub
parent 78971c9027
commit 51f906c9ff
4 changed files with 121 additions and 15 deletions

View File

@@ -44,6 +44,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
config-file: .github/release-please-config.json config-file: .github/release-please-config.json
manifest-file: .github/.release-please-manifest.json manifest-file: .github/.release-please-manifest.json
skip-labeling: true
publish-cli: publish-cli:
needs: release-pr needs: release-pr

View File

@@ -1,7 +1,7 @@
{ {
"name": "compound-engineering", "name": "compound-engineering",
"displayName": "Compound Engineering", "displayName": "Compound Engineering",
"version": "2.33.0", "version": "2.42.0",
"description": "AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.", "description": "AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.",
"author": { "author": {
"name": "Kieran Klaassen", "name": "Kieran Klaassen",

View File

@@ -41,6 +41,13 @@ export type MetadataSyncResult = {
updates: FileUpdate[] updates: FileUpdate[]
} }
function resolveExpectedVersion(
explicitVersion: string | undefined,
fallbackVersion: string,
): string {
return explicitVersion ?? fallbackVersion
}
export async function countMarkdownFiles(root: string): Promise<number> { export async function countMarkdownFiles(root: string): Promise<number> {
const entries = await fs.readdir(root, { withFileTypes: true }) const entries = await fs.readdir(root, { withFileTypes: true })
let total = 0 let total = 0
@@ -110,10 +117,18 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
const codingTutorClaude = await readJson<ClaudePluginManifest>(codingTutorClaudePath) const codingTutorClaude = await readJson<ClaudePluginManifest>(codingTutorClaudePath)
const codingTutorCursor = await readJson<CursorPluginManifest>(codingTutorCursorPath) const codingTutorCursor = await readJson<CursorPluginManifest>(codingTutorCursorPath)
const marketplaceClaude = await readJson<MarketplaceManifest>(marketplaceClaudePath) const marketplaceClaude = await readJson<MarketplaceManifest>(marketplaceClaudePath)
const expectedCompoundVersion = resolveExpectedVersion(
versions["compound-engineering"],
compoundClaude.version,
)
const expectedCodingTutorVersion = resolveExpectedVersion(
versions["coding-tutor"],
codingTutorClaude.version,
)
let changed = false let changed = false
if (versions["compound-engineering"] && compoundClaude.version !== versions["compound-engineering"]) { if (compoundClaude.version !== expectedCompoundVersion) {
compoundClaude.version = versions["compound-engineering"] compoundClaude.version = expectedCompoundVersion
changed = true changed = true
} }
if (compoundClaude.description !== compoundDescription) { if (compoundClaude.description !== compoundDescription) {
@@ -124,8 +139,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
if (write && changed) await writeJson(compoundClaudePath, compoundClaude) if (write && changed) await writeJson(compoundClaudePath, compoundClaude)
changed = false changed = false
if (versions["compound-engineering"] && compoundCursor.version !== versions["compound-engineering"]) { if (compoundCursor.version !== expectedCompoundVersion) {
compoundCursor.version = versions["compound-engineering"] compoundCursor.version = expectedCompoundVersion
changed = true changed = true
} }
if (compoundCursor.description !== compoundDescription) { if (compoundCursor.description !== compoundDescription) {
@@ -136,16 +151,16 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
if (write && changed) await writeJson(compoundCursorPath, compoundCursor) if (write && changed) await writeJson(compoundCursorPath, compoundCursor)
changed = false changed = false
if (versions["coding-tutor"] && codingTutorClaude.version !== versions["coding-tutor"]) { if (codingTutorClaude.version !== expectedCodingTutorVersion) {
codingTutorClaude.version = versions["coding-tutor"] codingTutorClaude.version = expectedCodingTutorVersion
changed = true changed = true
} }
updates.push({ path: codingTutorClaudePath, changed }) updates.push({ path: codingTutorClaudePath, changed })
if (write && changed) await writeJson(codingTutorClaudePath, codingTutorClaude) if (write && changed) await writeJson(codingTutorClaudePath, codingTutorClaude)
changed = false changed = false
if (versions["coding-tutor"] && codingTutorCursor.version !== versions["coding-tutor"]) { if (codingTutorCursor.version !== expectedCodingTutorVersion) {
codingTutorCursor.version = versions["coding-tutor"] codingTutorCursor.version = expectedCodingTutorVersion
changed = true changed = true
} }
updates.push({ path: codingTutorCursorPath, changed }) updates.push({ path: codingTutorCursorPath, changed })
@@ -159,8 +174,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
for (const plugin of marketplaceClaude.plugins) { for (const plugin of marketplaceClaude.plugins) {
if (plugin.name === "compound-engineering") { if (plugin.name === "compound-engineering") {
if (versions["compound-engineering"] && plugin.version !== versions["compound-engineering"]) { if (plugin.version !== expectedCompoundVersion) {
plugin.version = versions["compound-engineering"] plugin.version = expectedCompoundVersion
changed = true changed = true
} }
if (plugin.description !== `AI-powered development tools that get smarter with every use. Make each unit of engineering work easier than the last. Includes ${await countMarkdownFiles(path.join(root, "plugins", "compound-engineering", "agents"))} specialized agents and ${await countSkillDirectories(path.join(root, "plugins", "compound-engineering", "skills"))} skills.`) { if (plugin.description !== `AI-powered development tools that get smarter with every use. Make each unit of engineering work easier than the last. Includes ${await countMarkdownFiles(path.join(root, "plugins", "compound-engineering", "agents"))} specialized agents and ${await countSkillDirectories(path.join(root, "plugins", "compound-engineering", "skills"))} skills.`) {
@@ -169,8 +184,8 @@ export async function syncReleaseMetadata(options: SyncOptions = {}): Promise<Me
} }
} }
if (plugin.name === "coding-tutor" && versions["coding-tutor"] && plugin.version !== versions["coding-tutor"]) { if (plugin.name === "coding-tutor" && plugin.version !== expectedCodingTutorVersion) {
plugin.version = versions["coding-tutor"] plugin.version = expectedCodingTutorVersion
changed = true changed = true
} }
} }

View File

@@ -1,5 +1,86 @@
import { describe, expect, test } from "bun:test" import { mkdtemp, mkdir, writeFile } from "fs/promises"
import { buildCompoundEngineeringDescription } from "../src/release/metadata" import os from "os"
import path from "path"
import { afterEach, describe, expect, test } from "bun:test"
import { buildCompoundEngineeringDescription, syncReleaseMetadata } from "../src/release/metadata"
const tempRoots: string[] = []
afterEach(async () => {
for (const root of tempRoots.splice(0, tempRoots.length)) {
await Bun.$`rm -rf ${root}`.quiet()
}
})
async function makeFixtureRoot(): Promise<string> {
const root = await mkdtemp(path.join(os.tmpdir(), "release-metadata-"))
tempRoots.push(root)
await mkdir(path.join(root, "plugins", "compound-engineering", "agents", "review"), {
recursive: true,
})
await mkdir(path.join(root, "plugins", "compound-engineering", "skills", "ce-plan"), {
recursive: true,
})
await mkdir(path.join(root, "plugins", "compound-engineering", ".claude-plugin"), {
recursive: true,
})
await mkdir(path.join(root, "plugins", "compound-engineering", ".cursor-plugin"), {
recursive: true,
})
await mkdir(path.join(root, "plugins", "coding-tutor", ".claude-plugin"), {
recursive: true,
})
await mkdir(path.join(root, "plugins", "coding-tutor", ".cursor-plugin"), {
recursive: true,
})
await mkdir(path.join(root, ".claude-plugin"), { recursive: true })
await writeFile(
path.join(root, "plugins", "compound-engineering", "agents", "review", "agent.md"),
"# Review Agent\n",
)
await writeFile(
path.join(root, "plugins", "compound-engineering", "skills", "ce-plan", "SKILL.md"),
"# ce:plan\n",
)
await writeFile(
path.join(root, "plugins", "compound-engineering", ".mcp.json"),
JSON.stringify({ mcpServers: { context7: { command: "ctx7" } } }, null, 2),
)
await writeFile(
path.join(root, "plugins", "compound-engineering", ".claude-plugin", "plugin.json"),
JSON.stringify({ version: "2.42.0", description: "old" }, null, 2),
)
await writeFile(
path.join(root, "plugins", "compound-engineering", ".cursor-plugin", "plugin.json"),
JSON.stringify({ version: "2.33.0", description: "old" }, null, 2),
)
await writeFile(
path.join(root, "plugins", "coding-tutor", ".claude-plugin", "plugin.json"),
JSON.stringify({ version: "1.2.1" }, null, 2),
)
await writeFile(
path.join(root, "plugins", "coding-tutor", ".cursor-plugin", "plugin.json"),
JSON.stringify({ version: "1.2.1" }, null, 2),
)
await writeFile(
path.join(root, ".claude-plugin", "marketplace.json"),
JSON.stringify(
{
metadata: { version: "1.0.0", description: "marketplace" },
plugins: [
{ name: "compound-engineering", version: "2.41.0", description: "old" },
{ name: "coding-tutor", version: "1.2.0", description: "old" },
],
},
null,
2,
),
)
return root
}
describe("release metadata", () => { describe("release metadata", () => {
test("builds the current compound-engineering manifest description from repo counts", async () => { test("builds the current compound-engineering manifest description from repo counts", async () => {
@@ -8,4 +89,13 @@ describe("release metadata", () => {
"AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.", "AI-powered development tools. 29 agents, 44 skills, 1 MCP server for code review, research, design, and workflow automation.",
) )
}) })
test("detects cross-surface version drift even without explicit override versions", async () => {
const root = await makeFixtureRoot()
const result = await syncReleaseMetadata({ root, write: false })
const changedPaths = result.updates.filter((update) => update.changed).map((update) => update.path)
expect(changedPaths).toContain(path.join(root, "plugins", "compound-engineering", ".cursor-plugin", "plugin.json"))
expect(changedPaths).toContain(path.join(root, ".claude-plugin", "marketplace.json"))
})
}) })