feat(ce-demo-reel): add demo reel skill with Python capture pipeline (#541)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
402
tests/ce-demo-reel.test.ts
Normal file
402
tests/ce-demo-reel.test.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
import { describe, expect, test, beforeAll, afterAll } from "bun:test"
|
||||
import { promises as fs } from "fs"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
|
||||
const SCRIPT = path.join(
|
||||
process.cwd(),
|
||||
"plugins",
|
||||
"compound-engineering",
|
||||
"skills",
|
||||
"ce-demo-reel",
|
||||
"scripts",
|
||||
"capture-demo.py",
|
||||
)
|
||||
|
||||
async function run(
|
||||
...args: string[]
|
||||
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
||||
const proc = Bun.spawn(["python3", SCRIPT, ...args], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
const exitCode = await proc.exited
|
||||
const stdout = await new Response(proc.stdout).text()
|
||||
const stderr = await new Response(proc.stderr).text()
|
||||
return { exitCode, stdout, stderr }
|
||||
}
|
||||
|
||||
/** Create a minimal valid PNG (1x1 pixel, solid color). */
|
||||
function createTestPng(color: [number, number, number]): Buffer {
|
||||
const [r, g, b] = color
|
||||
|
||||
// Raw RGB pixel data: 1 row, filter byte 0, then RGB
|
||||
const rawData = Buffer.from([0, r, g, b])
|
||||
|
||||
// Compress with zlib
|
||||
const compressed = Bun.deflateSync(rawData, { level: 0 })
|
||||
const cmf = 0x78
|
||||
const flg = 0x01
|
||||
let s1 = 1
|
||||
let s2 = 0
|
||||
for (const byte of rawData) {
|
||||
s1 = (s1 + byte) % 65521
|
||||
s2 = (s2 + s1) % 65521
|
||||
}
|
||||
const adler32 = Buffer.alloc(4)
|
||||
adler32.writeUInt32BE((s2 << 16) | s1)
|
||||
const zlibData = Buffer.concat([Buffer.from([cmf, flg]), compressed, adler32])
|
||||
|
||||
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
||||
|
||||
function chunk(type: string, data: Buffer): Buffer {
|
||||
const len = Buffer.alloc(4)
|
||||
len.writeUInt32BE(data.length)
|
||||
const typeB = Buffer.from(type, "ascii")
|
||||
const body = Buffer.concat([typeB, data])
|
||||
const crc = crc32(body)
|
||||
const crcB = Buffer.alloc(4)
|
||||
crcB.writeUInt32BE(crc >>> 0)
|
||||
return Buffer.concat([len, body, crcB])
|
||||
}
|
||||
|
||||
// IHDR: 1x1, 8-bit RGB (color type 2)
|
||||
const ihdr = Buffer.alloc(13)
|
||||
ihdr.writeUInt32BE(1, 0)
|
||||
ihdr.writeUInt32BE(1, 4)
|
||||
ihdr[8] = 8 // bit depth
|
||||
ihdr[9] = 2 // color type: RGB
|
||||
ihdr[10] = 0
|
||||
ihdr[11] = 0
|
||||
ihdr[12] = 0
|
||||
|
||||
return Buffer.concat([
|
||||
signature,
|
||||
chunk("IHDR", ihdr),
|
||||
chunk("IDAT", zlibData),
|
||||
chunk("IEND", Buffer.alloc(0)),
|
||||
])
|
||||
}
|
||||
|
||||
function crc32(data: Buffer): number {
|
||||
let crc = 0xffffffff
|
||||
for (const byte of data) {
|
||||
crc ^= byte
|
||||
for (let j = 0; j < 8; j++) {
|
||||
crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1
|
||||
}
|
||||
}
|
||||
return (crc ^ 0xffffffff) >>> 0
|
||||
}
|
||||
|
||||
// --- Preflight ---
|
||||
|
||||
describe("capture-evidence.py", () => {
|
||||
describe("preflight", () => {
|
||||
test("returns JSON with tool availability", async () => {
|
||||
const { exitCode, stdout } = await run("preflight")
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result).toHaveProperty("agent_browser")
|
||||
expect(result).toHaveProperty("vhs")
|
||||
expect(result).toHaveProperty("silicon")
|
||||
expect(result).toHaveProperty("ffmpeg")
|
||||
expect(result).toHaveProperty("ffprobe")
|
||||
expect(typeof result.ffmpeg).toBe("boolean")
|
||||
})
|
||||
})
|
||||
|
||||
// --- Detect ---
|
||||
|
||||
describe("detect", () => {
|
||||
let tmpDir: string
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "evidence-detect-"))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (tmpDir) await fs.rm(tmpDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("detects web-app from package.json with react", async () => {
|
||||
const dir = path.join(tmpDir, "webapp")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ dependencies: { react: "^18.0.0" } }),
|
||||
)
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("web-app")
|
||||
})
|
||||
|
||||
test("detects cli-tool from package.json with bin field", async () => {
|
||||
const dir = path.join(tmpDir, "clitool")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ bin: { mycli: "./cli.js" } }),
|
||||
)
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("cli-tool")
|
||||
})
|
||||
|
||||
test("detects desktop-app from electron dependency", async () => {
|
||||
const dir = path.join(tmpDir, "electron")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ devDependencies: { electron: "^28.0.0", react: "^18.0.0" } }),
|
||||
)
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("desktop-app")
|
||||
})
|
||||
|
||||
test("detects library when manifest exists but no web/CLI signals", async () => {
|
||||
const dir = path.join(tmpDir, "lib")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ name: "my-utils", version: "1.0.0" }),
|
||||
)
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("library")
|
||||
})
|
||||
|
||||
test("detects text-only when no manifest exists", async () => {
|
||||
const dir = path.join(tmpDir, "textonly")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(path.join(dir, "README.md"), "# Hello")
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("text-only")
|
||||
})
|
||||
|
||||
test("electron takes priority over web-app", async () => {
|
||||
const dir = path.join(tmpDir, "electron-react")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(
|
||||
path.join(dir, "package.json"),
|
||||
JSON.stringify({ dependencies: { react: "^18.0.0" }, devDependencies: { electron: "^28.0.0" } }),
|
||||
)
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("desktop-app")
|
||||
})
|
||||
|
||||
test("detects web-app from Gemfile with rails", async () => {
|
||||
const dir = path.join(tmpDir, "rails")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(path.join(dir, "Gemfile"), 'gem "rails", "~> 7.0"')
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("web-app")
|
||||
})
|
||||
|
||||
test("detects cli-tool from go.mod with cmd/ directory", async () => {
|
||||
const dir = path.join(tmpDir, "gocli")
|
||||
await fs.mkdir(dir)
|
||||
await fs.writeFile(path.join(dir, "go.mod"), "module example.com/mycli\n\ngo 1.21")
|
||||
await fs.mkdir(path.join(dir, "cmd"))
|
||||
const { exitCode, stdout } = await run("detect", "--repo-root", dir)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.type).toBe("cli-tool")
|
||||
})
|
||||
})
|
||||
|
||||
// --- Recommend ---
|
||||
|
||||
describe("recommend", () => {
|
||||
const allTools = '{"agent_browser":true,"vhs":true,"silicon":true,"ffmpeg":true,"ffprobe":true}'
|
||||
const noTools = '{"agent_browser":false,"vhs":false,"silicon":false,"ffmpeg":false,"ffprobe":false}'
|
||||
|
||||
test("web-app with browser + ffmpeg recommends browser-reel", async () => {
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "web-app", "--change-type", "states", "--tools", allTools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.recommended).toBe("browser-reel")
|
||||
})
|
||||
|
||||
test("cli-tool with motion + vhs recommends terminal-recording", async () => {
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "cli-tool", "--change-type", "motion", "--tools", allTools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.recommended).toBe("terminal-recording")
|
||||
})
|
||||
|
||||
test("cli-tool with states + silicon recommends screenshot-reel", async () => {
|
||||
const tools = '{"agent_browser":false,"vhs":false,"silicon":true,"ffmpeg":true,"ffprobe":true}'
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "cli-tool", "--change-type", "states", "--tools", tools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.recommended).toBe("screenshot-reel")
|
||||
})
|
||||
|
||||
test("library always recommends static-screenshots", async () => {
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "library", "--change-type", "states", "--tools", allTools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.recommended).toBe("static-screenshots")
|
||||
})
|
||||
|
||||
test("no tools always falls back to static-screenshots", async () => {
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "cli-tool", "--change-type", "motion", "--tools", noTools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.recommended).toBe("static-screenshots")
|
||||
})
|
||||
|
||||
test("available list includes only tiers with tools present", async () => {
|
||||
const tools = '{"agent_browser":false,"vhs":true,"silicon":false,"ffmpeg":true,"ffprobe":true}'
|
||||
const { exitCode, stdout } = await run(
|
||||
"recommend", "--project-type", "cli-tool", "--change-type", "motion", "--tools", tools,
|
||||
)
|
||||
expect(exitCode).toBe(0)
|
||||
const result = JSON.parse(stdout.trim())
|
||||
expect(result.available).toContain("terminal-recording")
|
||||
expect(result.available).toContain("static-screenshots")
|
||||
expect(result.available).not.toContain("browser-reel")
|
||||
expect(result.available).not.toContain("screenshot-reel")
|
||||
})
|
||||
})
|
||||
|
||||
// --- Stitch arg validation ---
|
||||
|
||||
describe("stitch arg validation", () => {
|
||||
test("stitch with no args fails", async () => {
|
||||
const { exitCode, stderr } = await run("stitch")
|
||||
expect(exitCode).not.toBe(0)
|
||||
})
|
||||
|
||||
test("stitch fails on missing frame file", async () => {
|
||||
const { exitCode, stderr } = await run(
|
||||
"stitch", "out.gif", "/tmp/nonexistent-frame-abc123.png",
|
||||
)
|
||||
expect(exitCode).toBe(1)
|
||||
expect(stderr).toContain("Frame not found")
|
||||
})
|
||||
|
||||
test("upload fails on missing file", async () => {
|
||||
const { exitCode, stderr } = await run(
|
||||
"upload", "/tmp/nonexistent-file-abc123.gif",
|
||||
)
|
||||
expect(exitCode).toBe(1)
|
||||
expect(stderr).toContain("File not found")
|
||||
})
|
||||
})
|
||||
|
||||
// --- Stitch integration (requires ffmpeg) ---
|
||||
|
||||
describe("stitch integration", () => {
|
||||
let tmpDir: string
|
||||
let hasFFmpeg: boolean
|
||||
|
||||
beforeAll(async () => {
|
||||
const proc = Bun.spawn(["which", "ffmpeg"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
hasFFmpeg = (await proc.exited) === 0
|
||||
|
||||
if (!hasFFmpeg) return
|
||||
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "evidence-test-"))
|
||||
|
||||
const red = createTestPng([255, 0, 0])
|
||||
const green = createTestPng([0, 255, 0])
|
||||
const blue = createTestPng([0, 0, 255])
|
||||
|
||||
await fs.writeFile(path.join(tmpDir, "frame1.png"), red)
|
||||
await fs.writeFile(path.join(tmpDir, "frame2.png"), green)
|
||||
await fs.writeFile(path.join(tmpDir, "frame3.png"), blue)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (tmpDir) await fs.rm(tmpDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("stitches frames into a GIF", async () => {
|
||||
if (!hasFFmpeg) {
|
||||
console.log("Skipping: ffmpeg not available")
|
||||
return
|
||||
}
|
||||
|
||||
const output = path.join(tmpDir, "output.gif")
|
||||
const { exitCode, stdout } = await run(
|
||||
"stitch", "--duration", "0.5", output,
|
||||
path.join(tmpDir, "frame1.png"),
|
||||
path.join(tmpDir, "frame2.png"),
|
||||
)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
expect(stdout).toContain("Stitching 2 frames")
|
||||
expect(stdout).toContain("Created:")
|
||||
|
||||
const stat = await fs.stat(output)
|
||||
expect(stat.size).toBeGreaterThan(0)
|
||||
|
||||
const header = Buffer.alloc(6)
|
||||
const fh = await fs.open(output, "r")
|
||||
await fh.read(header, 0, 6)
|
||||
await fh.close()
|
||||
expect(header.toString("ascii").startsWith("GIF")).toBe(true)
|
||||
})
|
||||
|
||||
test("stitches 3 frames into a GIF", async () => {
|
||||
if (!hasFFmpeg) {
|
||||
console.log("Skipping: ffmpeg not available")
|
||||
return
|
||||
}
|
||||
|
||||
const output = path.join(tmpDir, "output3.gif")
|
||||
const { exitCode, stdout } = await run(
|
||||
"stitch", "--duration", "0.5", output,
|
||||
path.join(tmpDir, "frame1.png"),
|
||||
path.join(tmpDir, "frame2.png"),
|
||||
path.join(tmpDir, "frame3.png"),
|
||||
)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
expect(stdout).toContain("Stitching 3 frames")
|
||||
})
|
||||
|
||||
test("default duration is used when --duration not specified", async () => {
|
||||
if (!hasFFmpeg) {
|
||||
console.log("Skipping: ffmpeg not available")
|
||||
return
|
||||
}
|
||||
|
||||
const output = path.join(tmpDir, "output-default-dur.gif")
|
||||
const { exitCode, stdout } = await run(
|
||||
"stitch", output,
|
||||
path.join(tmpDir, "frame1.png"),
|
||||
path.join(tmpDir, "frame2.png"),
|
||||
)
|
||||
|
||||
expect(exitCode).toBe(0)
|
||||
expect(stdout).toContain("Created:")
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user