fix: enforce release metadata consistency (#297)
This commit is contained in:
1
.github/workflows/release-pr.yml
vendored
1
.github/workflows/release-pr.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user