feat(ce-release-notes): add skill for browsing plugin release history (#589)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Trevin Chow
2026-04-17 02:00:37 -07:00
committed by GitHub
parent e7cf0ae957
commit 59dbaef376
6 changed files with 1308 additions and 0 deletions

View File

@@ -0,0 +1,360 @@
import { afterAll, describe, expect, test } from "bun:test"
import type { Server } from "bun"
import { promises as fs } from "fs"
import os from "os"
import path from "path"
const helperPath = path.join(
import.meta.dir,
"..",
"..",
"plugins",
"compound-engineering",
"skills",
"ce-release-notes",
"scripts",
"list-plugin-releases.py",
)
type RunResult = { exitCode: number; stdout: string; stderr: string }
async function runHelper(
args: string[] = [],
opts: { ghBin?: string; apiBase?: string } = {},
): Promise<RunResult> {
const env: Record<string, string> = {}
for (const [k, v] of Object.entries(process.env)) {
if (v !== undefined) env[k] = v
}
if (opts.ghBin !== undefined) env.CE_RELEASE_NOTES_GH_BIN = opts.ghBin
const fullArgs = ["python3", helperPath, ...args]
if (opts.apiBase) fullArgs.push("--api-base", opts.apiBase)
const proc = Bun.spawn(fullArgs, { env, stderr: "pipe", stdout: "pipe" })
const [exitCode, stdout, stderr] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
])
return { exitCode, stdout, stderr }
}
async function makeGhShim(stdout: string, exitCode = 0): Promise<string> {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "ce-rn-gh-"))
const ghPath = path.join(dir, "gh")
// Use printf to avoid heredoc quoting issues with arbitrary JSON content.
const script = `#!/usr/bin/env bash\nprintf '%s' ${shellQuote(stdout)}\nexit ${exitCode}\n`
await fs.writeFile(ghPath, script, { mode: 0o755 })
return ghPath
}
function shellQuote(s: string): string {
return `'${s.replace(/'/g, "'\\''")}'`
}
let server: Server | null = null
let serverHandler: (req: Request) => Response | Promise<Response> = () =>
new Response("not configured", { status: 500 })
function startServer(): string {
if (!server) {
server = Bun.serve({
port: 0,
fetch: (req) => serverHandler(req),
})
}
return `http://localhost:${server.port}`
}
function setHandler(h: typeof serverHandler) {
serverHandler = h
}
afterAll(() => {
if (server) {
server.stop(true)
server = null
}
})
// ---- Fixtures ----
const PLUGIN_267 = {
tagName: "compound-engineering-v2.67.0",
name: "compound-engineering: v2.67.0",
publishedAt: "2026-04-17T05:59:30Z",
url: "https://github.com/EveryInc/compound-engineering-plugin/releases/tag/compound-engineering-v2.67.0",
body:
"## Features\n* **ce-polish-beta:** thing ([#568](https://github.com/EveryInc/compound-engineering-plugin/issues/568))\n* fixes ([#575](https://github.com/EveryInc/compound-engineering-plugin/issues/575))\n",
}
const PLUGIN_266 = {
tagName: "compound-engineering-v2.66.1",
name: "compound-engineering: v2.66.1",
publishedAt: "2026-04-15T10:00:00Z",
url: "https://github.com/EveryInc/compound-engineering-plugin/releases/tag/compound-engineering-v2.66.1",
body:
"## Bug Fixes\n* something ([#560](https://github.com/EveryInc/compound-engineering-plugin/issues/560))\n",
}
const CLI_267 = {
tagName: "cli-v2.67.0",
name: "cli: v2.67.0",
publishedAt: "2026-04-17T06:00:00Z",
url: "https://github.com/EveryInc/compound-engineering-plugin/releases/tag/cli-v2.67.0",
body:
"## Features\n* cli stuff ([#600](https://github.com/EveryInc/compound-engineering-plugin/issues/600))\n",
}
type GhRelease = typeof PLUGIN_267
function toApiShape(r: GhRelease) {
return {
tag_name: r.tagName,
name: r.name,
published_at: r.publishedAt,
html_url: r.url,
body: r.body,
}
}
// ---- Tests ----
describe("list-plugin-releases.py", () => {
describe("gh path", () => {
test("mixed tags → only compound-engineering-v* surfaced, sorted newest first", async () => {
const ghBin = await makeGhShim(
JSON.stringify([CLI_267, PLUGIN_266, PLUGIN_267].map(toApiShape)),
)
const result = await runHelper(["--limit", "10"], { ghBin })
expect(result.exitCode).toBe(0)
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("gh")
expect(data.releases).toHaveLength(2)
expect(data.releases[0].tag).toBe("compound-engineering-v2.67.0")
expect(data.releases[0].version).toBe("2.67.0")
expect(data.releases[0].linked_prs).toEqual([568, 575])
expect(data.releases[1].tag).toBe("compound-engineering-v2.66.1")
})
test("multiple PR refs in body → linked_prs deduplicated and ordered", async () => {
const release = {
...PLUGIN_267,
body:
"Stuff ([#100](https://x/100)) and ([#200](https://x/200)) again ([#100](https://x/dup))",
}
const ghBin = await makeGhShim(JSON.stringify([release].map(toApiShape)))
const result = await runHelper(["--limit", "10"], { ghBin })
const data = JSON.parse(result.stdout)
expect(data.releases[0].linked_prs).toEqual([100, 200])
})
test("body with bare #N references → NOT in linked_prs", async () => {
const release = { ...PLUGIN_267, body: "fixes #123 and refs #456" }
const ghBin = await makeGhShim(JSON.stringify([release].map(toApiShape)))
const result = await runHelper(["--limit", "10"], { ghBin })
const data = JSON.parse(result.stdout)
expect(data.releases[0].linked_prs).toEqual([])
})
test("body with commit-SHA parens → NOT in linked_prs", async () => {
const release = {
...PLUGIN_267,
body: "([070092d](https://github.com/x/commit/070092d))",
}
const ghBin = await makeGhShim(JSON.stringify([release].map(toApiShape)))
const result = await runHelper(["--limit", "10"], { ghBin })
const data = JSON.parse(result.stdout)
expect(data.releases[0].linked_prs).toEqual([])
})
test("empty body → linked_prs is []", async () => {
const release = { ...PLUGIN_267, body: "" }
const ghBin = await makeGhShim(JSON.stringify([release].map(toApiShape)))
const result = await runHelper(["--limit", "10"], { ghBin })
const data = JSON.parse(result.stdout)
expect(data.releases[0].body).toBe("")
expect(data.releases[0].linked_prs).toEqual([])
})
test("url prefers html_url over api url when both present", async () => {
const apiShaped = {
tag_name: PLUGIN_267.tagName,
name: PLUGIN_267.name,
published_at: PLUGIN_267.publishedAt,
html_url:
"https://github.com/EveryInc/compound-engineering-plugin/releases/tag/compound-engineering-v2.67.0",
url:
"https://api.github.com/repos/EveryInc/compound-engineering-plugin/releases/310187170",
body: PLUGIN_267.body,
}
const ghBin = await makeGhShim(JSON.stringify([apiShaped]))
const result = await runHelper(["--limit", "10"], { ghBin })
const data = JSON.parse(result.stdout)
expect(data.releases[0].url).toBe(
"https://github.com/EveryInc/compound-engineering-plugin/releases/tag/compound-engineering-v2.67.0",
)
})
})
describe("gh fallback to anon", () => {
test("gh binary missing → falls back to anon", async () => {
const apiBase = startServer()
setHandler(() => Response.json([toApiShape(PLUGIN_267)]))
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh-binary",
apiBase,
})
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("anon")
expect(data.releases).toHaveLength(1)
})
test("gh exits non-zero → falls back to anon", async () => {
const apiBase = startServer()
setHandler(() => Response.json([toApiShape(PLUGIN_267)]))
const ghBin = await makeGhShim("simulated error", 1)
const result = await runHelper(["--limit", "10"], { ghBin, apiBase })
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("anon")
})
test("gh succeeds but yields zero plugin tags (GHE-pointing case) → falls back to anon", async () => {
const apiBase = startServer()
setHandler(() => Response.json([toApiShape(PLUGIN_267)]))
const ghBin = await makeGhShim(JSON.stringify([toApiShape(CLI_267)]))
const result = await runHelper(["--limit", "10"], { ghBin, apiBase })
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("anon")
expect(data.releases[0].tag).toBe("compound-engineering-v2.67.0")
})
test("gh returns malformed JSON → falls back to anon", async () => {
const apiBase = startServer()
setHandler(() => Response.json([toApiShape(PLUGIN_267)]))
const ghBin = await makeGhShim("not json {{{")
const result = await runHelper(["--limit", "10"], { ghBin, apiBase })
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("anon")
})
})
describe("anon path", () => {
test("anon HTTP 200 → ok:true, source=anon, releases parsed and filtered", async () => {
const apiBase = startServer()
setHandler(() =>
Response.json([toApiShape(PLUGIN_267), toApiShape(CLI_267), toApiShape(PLUGIN_266)]),
)
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh",
apiBase,
})
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(true)
expect(data.source).toBe("anon")
expect(data.releases).toHaveLength(2)
expect(data.releases[0].tag).toBe("compound-engineering-v2.67.0")
})
})
describe("anon error paths", () => {
test("HTTP 403 + X-RateLimit-Remaining:0 → ok:false code=rate_limit", async () => {
const apiBase = startServer()
const reset = Math.floor(Date.now() / 1000) + 1080
setHandler(
() =>
new Response("rate limited", {
status: 403,
headers: {
"X-RateLimit-Remaining": "0",
"X-RateLimit-Reset": String(reset),
},
}),
)
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh",
apiBase,
})
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(false)
expect(data.error.code).toBe("rate_limit")
expect(data.error.user_hint).toContain(
"github.com/EveryInc/compound-engineering-plugin/releases",
)
expect(data.error.message).toMatch(/resets in \d+ minutes/)
})
test("HTTP 500 → ok:false code=network_outage", async () => {
const apiBase = startServer()
setHandler(() => new Response("internal error", { status: 500 }))
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh",
apiBase,
})
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(false)
expect(data.error.code).toBe("network_outage")
expect(data.error.user_hint).toContain(
"github.com/EveryInc/compound-engineering-plugin/releases",
)
})
test("malformed JSON from API → ok:false code=network_outage", async () => {
const apiBase = startServer()
setHandler(() => new Response("not json {{{", { status: 200 }))
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh",
apiBase,
})
const data = JSON.parse(result.stdout)
expect(data.ok).toBe(false)
expect(data.error.code).toBe("network_outage")
})
})
describe("integration", () => {
test("invoked from an unrelated working directory still works", async () => {
const ghBin = await makeGhShim(JSON.stringify([toApiShape(PLUGIN_267)]))
const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "ce-rn-cwd-"))
const env: Record<string, string> = {}
for (const [k, v] of Object.entries(process.env)) {
if (v !== undefined) env[k] = v
}
env.CE_RELEASE_NOTES_GH_BIN = ghBin
const proc = Bun.spawn(["python3", helperPath, "--limit", "10"], {
cwd: tmpdir,
env,
stderr: "pipe",
stdout: "pipe",
})
const [exitCode, stdout] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
])
expect(exitCode).toBe(0)
const data = JSON.parse(stdout)
expect(data.ok).toBe(true)
expect(data.releases[0].tag).toBe("compound-engineering-v2.67.0")
})
test("contract always exits 0 even on rate-limit failure", async () => {
const apiBase = startServer()
setHandler(
() =>
new Response("nope", {
status: 403,
headers: { "X-RateLimit-Remaining": "0", "X-RateLimit-Reset": "0" },
}),
)
const result = await runHelper(["--limit", "10"], {
ghBin: "/nonexistent/gh",
apiBase,
})
expect(result.exitCode).toBe(0)
})
})
})