From 69dccbe8bfe0a6f61f9488c1c4992efce0669713 Mon Sep 17 00:00:00 2001 From: Cristhofer Pincetti Date: Fri, 28 Aug 2026 17:33:15 -0400 Subject: [PATCH] ci: auto-merge catalog and manifest-sync PRs when CI is green Queue merge-when-green instead of leaving bot PRs open, and fail the job if GitHub cannot queue auto-merge. Co-authored-by: Cursor --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 4 ++ README.md | 2 +- docs/2026-08-28-auto-merge-sync-prs/plan.md | 59 +++++++++++++++++++++ docs/2026-08-28-auto-merge-sync-prs/spec.md | 43 +++++++++++++++ scripts/catalog-sync-ci.ts | 8 ++- tests/unit/release-workflow.test.ts | 6 +++ 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 docs/2026-08-28-auto-merge-sync-prs/plan.md create mode 100644 docs/2026-08-28-auto-merge-sync-prs/spec.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75f91ad..252bea3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,4 +65,4 @@ jobs: --title "chore(release): sync manifests to v${VERSION}" \ --body "Automated post-release manifest alignment. Sets package.json, manifest.json, and CHANGELOG.md to the released ${VERSION}." \ --base main --head "${BRANCH}" - gh pr merge --auto --squash --delete-branch || echo "auto-merge unavailable — merge the sync PR after CI is green" + gh pr merge --auto --squash --delete-branch diff --git a/CHANGELOG.md b/CHANGELOG.md index 9070760..cc3adb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Catalog refresh and post-release manifest PRs auto-merge when required CI is green. + ## [0.7.0] - 2026-08-28 ### Changed diff --git a/README.md b/README.md index 368f560..c63a8b1 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ bun run check # oxlint + oxfmt + bun test + tsc (same stack as workit bun run sync -- --remote # refresh models.json + manifest.json from command-code@latest ``` -CI (`.github/workflows/catalog-sync.yml`) opens a PR every 6 hours when Command Code ships a new catalog. Merge after **check (test)**, **check (typecheck)**, **check (lint)**, **check (format)**, and **check (pack)** are green. `.github/workflows/release.yml` then runs **semantic-release** (npm publish + GitHub Release + tag). Do not push to `main`. +CI (`.github/workflows/catalog-sync.yml`) opens a PR every 6 hours when Command Code ships a new catalog. That PR, and the post-release `chore/manifest-sync-v*` PR, auto-merge after **check (test)**, **check (typecheck)**, **check (lint)**, **check (format)**, and **check (pack)** are green. `.github/workflows/release.yml` then runs **semantic-release** (npm publish + GitHub Release + tag). Do not push to `main`. The GitHub Actions secret name is `NPMJS` (same as workit). It is mapped to both `NPM_TOKEN` and `NODE_AUTH_TOKEN`. Use an npm **Automation** token (bypasses 2FA). A login token from `~/.npmrc` fails CI with `EOTP`. Catalog PRs get a real CI run when `RELEASE_SYNC_TOKEN` (or `CATALOG_PUSH_TOKEN`) is a PAT; `GITHUB_TOKEN` can open the PR but GitHub will not start workflows from that event. diff --git a/docs/2026-08-28-auto-merge-sync-prs/plan.md b/docs/2026-08-28-auto-merge-sync-prs/plan.md new file mode 100644 index 0000000..109103d --- /dev/null +++ b/docs/2026-08-28-auto-merge-sync-prs/plan.md @@ -0,0 +1,59 @@ +# Auto-merge sync PRs Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Spec:** `docs/2026-08-28-auto-merge-sync-prs/spec.md` +**Branch:** `feature/2026-08-28-auto-merge-sync-prs` + +**Goal:** Queue GitHub auto-merge on bot catalog and post-release manifest PRs; fail the job if queueing fails. + +**Architecture:** Keep `gh pr merge --auto --squash --delete-branch`. Remove the swallow on the release job. Call the same command from `catalog-sync-ci.ts` after create and after updating an existing PR. Tests assert the workflow/script strings. Repo `allow_auto_merge` is enabled via GitHub API (already done). + +**Tech Stack:** GitHub Actions, `gh`, Bun tests (`tests/unit/release-workflow.test.ts`). + +## Global Constraints + +- PRs against `BrainerVirus/opencode-commandcode` base `main`. Squash + delete source branch. +- In-place `feature/2026-08-28-auto-merge-sync-prs` (no worktrees). +- Conventional commit `ci:` so path-gated semantic-release does not publish. +- Do not fold this package into workit. + +--- + +### Task 1: Fail loud on manifest auto-merge; queue catalog PRs + +**Files:** +- Modify: `.github/workflows/release.yml` +- Modify: `scripts/catalog-sync-ci.ts` +- Modify: `tests/unit/release-workflow.test.ts` +- Modify: `README.md` + +- [ ] **Step 1: Write the failing test** + +In `tests/unit/release-workflow.test.ts`, on the sync step `run` string: + +- still contains `gh pr merge --auto --squash --delete-branch` +- does **not** contain `auto-merge unavailable` or `|| echo` + +On `scripts/catalog-sync-ci.ts` source: contains `gh pr merge --auto --squash --delete-branch`. + +- [ ] **Step 2: Run the test and confirm it fails** + +```bash +bun test tests/unit/release-workflow.test.ts +``` + +- [ ] **Step 3: Implement** + +`release.yml`: drop `|| echo "auto-merge unavailable — merge the sync PR after CI is green"`. + +`catalog-sync-ci.ts`: after `gh pr create` and after logging an updated existing PR, run `gh pr merge --auto --squash --delete-branch` (stdio inherit, same cwd). Do not swallow errors. + +README Development: catalog and manifest-sync PRs auto-merge when the five checks are green. + +- [ ] **Step 4: Run tests and `bun run check`** + +```bash +bun test tests/unit/release-workflow.test.ts +bun run check +``` diff --git a/docs/2026-08-28-auto-merge-sync-prs/spec.md b/docs/2026-08-28-auto-merge-sync-prs/spec.md new file mode 100644 index 0000000..98857ae --- /dev/null +++ b/docs/2026-08-28-auto-merge-sync-prs/spec.md @@ -0,0 +1,43 @@ +# Auto-merge catalog and manifest sync PRs + +Status: approved (2026-08-28) +**Branch:** `feature/2026-08-28-auto-merge-sync-prs` + +## Goal + +Bot-opened catalog refresh PRs and post-release `chore/manifest-sync-v*` PRs merge themselves after required CI is green. A human should not have to squash-merge them so `package.json` / `manifest.json` on `main` lag behind the npm tag. + +## Locked (do not reopen) + +- `main` stays protected. Bots still open PRs; they do not push commits onto `main`. +- Merge method stays squash + delete source branch (repo default). +- npm publish is unchanged: semantic-release still writes `0.x.y` into the tarball before publish. The chore PR only copies that version onto git. +- Path-gated releases stay: this change is CI/scripts/docs only and must not cut an npm version. +- Do not fold this package into workit. + +## Requirements + +- G1: Repo setting **Allow auto-merge** is on (`allow_auto_merge: true`). Without it, `gh pr merge --auto` cannot queue merge-when-green. +- G2: After opening `chore/manifest-sync-v*`, the release job queues auto-merge. If queueing fails, the job **fails** (no `|| echo` swallow). +- G3: After opening or updating `chore/catalog-sync`, catalog-sync CI queues the same auto-merge. Failure fails the job. +- G4: Required checks stay `check (test|typecheck|pack|lint|format)`. Auto-merge waits for those; it does not skip them. + +## Non-goals + +- Bypassing branch protection or required status checks. +- Auto-merging human feature PRs. +- Changing semantic-release so it commits version bumps to `main` directly. + +## Constraints / Architecture + +```mermaid +flowchart LR + release[semantic-release publishes npm] --> pr[Open chore PR] + catalog[catalog-sync extract] --> pr2[Open or update catalog PR] + pr --> queue["gh pr merge --auto --squash"] + pr2 --> queue + queue --> ci[Required checks] + ci --> squash[Squash into main] +``` + +`gh pr merge --auto` at PR-create time is the queue. GitHub merges once checks pass. The repo must have Allow auto-merge enabled (set via API; not a file in git). diff --git a/scripts/catalog-sync-ci.ts b/scripts/catalog-sync-ci.ts index 9ecf03a..d5e6a61 100644 --- a/scripts/catalog-sync-ci.ts +++ b/scripts/catalog-sync-ci.ts @@ -66,6 +66,10 @@ function openOrUpdateCatalogBreak(input: { commandCodeVersion: string; error: st }); } +function queuePrAutoMerge(): void { + execSync("gh pr merge --auto --squash --delete-branch", { cwd: ROOT, stdio: "inherit" }); +} + function openCatalogPr(commandCodeVersion: string): void { const status = git(`status --porcelain -- ${CATALOG_FILES.join(" ")}`); if (!status) { @@ -88,12 +92,14 @@ function openCatalogPr(commandCodeVersion: string): void { const prs = JSON.parse(existing) as Array<{ number: number }>; if (prs.length > 0) { console.log(`updated catalog PR #${prs[0].number}`); + queuePrAutoMerge(); return; } execSync( - `gh pr create --base main --head ${CATALOG_BRANCH} --title ${JSON.stringify(`fix(catalog): sync command-code@${commandCodeVersion}`)} --body ${JSON.stringify(`Automated catalog refresh from command-code@${commandCodeVersion}. Merge after CI is green; semantic-release publishes the patch.`)}`, + `gh pr create --base main --head ${CATALOG_BRANCH} --title ${JSON.stringify(`fix(catalog): sync command-code@${commandCodeVersion}`)} --body ${JSON.stringify(`Automated catalog refresh from command-code@${commandCodeVersion}. Auto-merges when CI is green; semantic-release publishes the patch.`)}`, { cwd: ROOT, stdio: "inherit" }, ); + queuePrAutoMerge(); } async function main(): Promise { diff --git a/tests/unit/release-workflow.test.ts b/tests/unit/release-workflow.test.ts index 5991812..0efbbd8 100644 --- a/tests/unit/release-workflow.test.ts +++ b/tests/unit/release-workflow.test.ts @@ -119,6 +119,9 @@ describe("release.yml", () => { const sync = wf.jobs.release.steps.find((s) => s.name === "Sync release manifests to main"); expect(sync?.run).toContain("CHANGELOG.md"); expect(sync?.run).toMatch(/git add package\.json manifest\.json CHANGELOG\.md/); + expect(sync?.run).toContain("gh pr merge --auto --squash --delete-branch"); + expect(sync?.run).not.toContain("auto-merge unavailable"); + expect(sync?.run).not.toMatch(/\|\|\s*echo/); }); }); @@ -143,6 +146,9 @@ describe("catalog-sync.yml", () => { .map((s) => `${s.run ?? ""}\n${JSON.stringify(s.env ?? {})}`) .join("\n"); expect(blob).toContain("catalog-sync-ci.ts"); + expect(read("scripts/catalog-sync-ci.ts")).toContain( + "gh pr merge --auto --squash --delete-branch", + ); expect(blob).not.toMatch(/\bnpm publish\b/); expect(blob).not.toContain("semantic-release"); expect(blob).not.toContain("publish-if-needed");