From 7f43bb14f56e300d2ddec7b58b0378c205bdfa03 Mon Sep 17 00:00:00 2001 From: psmyrdek Date: Thu, 13 Aug 2026 21:52:42 +0200 Subject: [PATCH] feat(bench-kit): install template workflows into .github/workflows GitHub only runs workflows from .github/workflows/, so init copies .bench-kit/workflows/* there; repair mode keeps existing files (the company may have customized triggers or secrets). Co-Authored-By: Claude Fable 5 --- src/commands/bench-kit.ts | 22 ++++++++++++++++++++++ tests/bench-kit-command.test.ts | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/commands/bench-kit.ts b/src/commands/bench-kit.ts index c63c719..89f23bf 100644 --- a/src/commands/bench-kit.ts +++ b/src/commands/bench-kit.ts @@ -175,6 +175,7 @@ export async function runBenchKitInit( const templateVersion = readTemplateVersion(ctx, scratch); mkdirSync(targetDir, { recursive: true }); const copied = materialize(scratch, targetDir, { skipExisting: repair }); + installWorkflows(targetDir, { skipExisting: repair }); // Running init from inside a product repo is the common flow — register // that repo as the first base repo instead of leaving the placeholder. @@ -291,6 +292,27 @@ function readTemplateVersion(ctx: OutputContext, cloneDir: string): string { return readFileSync(versionFile, "utf8").trim(); } +/** + * GitHub only runs workflows from .github/workflows/, so the template's + * .bench-kit/workflows/ files are copied there. In repair mode existing + * files are kept — the company may have customized triggers or secrets. + */ +function installWorkflows( + targetDir: string, + opts: { skipExisting: boolean }, +): void { + const srcDir = join(targetDir, ".bench-kit", "workflows"); + if (!existsSync(srcDir)) return; + const destDir = join(targetDir, ".github", "workflows"); + mkdirSync(destDir, { recursive: true }); + for (const entry of readdirSync(srcDir, { withFileTypes: true })) { + if (!entry.isFile()) continue; + const to = join(destDir, entry.name); + if (opts.skipExisting && existsSync(to)) continue; + cpSync(join(srcDir, entry.name), to); + } +} + /** * Copies the clone into the target without git history. In repair mode * existing files are never overwritten — company content is untouchable. diff --git a/tests/bench-kit-command.test.ts b/tests/bench-kit-command.test.ts index f65d2ca..b5aa5a3 100644 --- a/tests/bench-kit-command.test.ts +++ b/tests/bench-kit-command.test.ts @@ -91,6 +91,8 @@ function buildTemplateFixture(version = "0.1.0"): string { writeFileSync(join(dir, ".git", "HEAD"), "ref: refs/heads/main\n"); mkdirSync(join(dir, ".bench-kit"), { recursive: true }); writeFileSync(join(dir, ".bench-kit", "VERSION"), `${version}\n`); + mkdirSync(join(dir, ".bench-kit", "workflows"), { recursive: true }); + writeFileSync(join(dir, ".bench-kit", "workflows", "bench-run.yaml"), "name: bench-run\n"); mkdirSync(join(dir, "tasks", "demo"), { recursive: true }); writeFileSync(join(dir, "tasks", "demo", "prompt.md"), "demo prompt\n"); writeFileSync( @@ -151,6 +153,10 @@ describe("10x bench-kit init", () => { expect(existsSync(join(target, ".bench-kit", "VERSION"))).toBe(true); expect(existsSync(join(target, "tasks", "demo", "prompt.md"))).toBe(true); expect(existsSync(join(target, ".git", "HEAD"))).toBe(false); + // GitHub only runs workflows from .github/workflows/ — init installs them there. + expect(readFileSync(join(target, ".github", "workflows", "bench-run.yaml"), "utf8")).toBe( + "name: bench-run\n", + ); const manifest = JSON.parse(readFileSync(join(target, ".bench-kit", "instance.json"), "utf8")); expect(manifest.templateVersion).toBe("0.1.0"); @@ -248,6 +254,9 @@ describe("10x bench-kit init", () => { mkdirSync(join(target, ".bench-kit"), { recursive: true }); writeFileSync(join(target, ".bench-kit", "VERSION"), "0.1.0\n"); writeFileSync(join(target, "bench.config.yaml"), "base_repos: [edited by company]\n"); + // Workflow customized by the company (e.g. triggers/secrets) — repair keeps it. + mkdirSync(join(target, ".github", "workflows"), { recursive: true }); + writeFileSync(join(target, ".github", "workflows", "bench-run.yaml"), "name: customized\n"); const { deps, gitCalls } = fakeDeps(template); const result = await captureStreams(() => @@ -259,6 +268,9 @@ describe("10x bench-kit init", () => { expect(existsSync(join(target, "tasks", "demo", "prompt.md"))).toBe(true); // …company content untouched… expect(readFileSync(join(target, "bench.config.yaml"), "utf8")).toContain("edited by company"); + expect(readFileSync(join(target, ".github", "workflows", "bench-run.yaml"), "utf8")).toBe( + "name: customized\n", + ); // …and no fresh git init in repair mode. expect(gitCalls.length).toBe(0);