Skip to content

Commit 98b4dd5

Browse files
ci: auto-merge catalog and manifest-sync PRs when CI is green (#12)
Queue merge-when-green instead of leaving bot PRs open, and fail the job if GitHub cannot queue auto-merge. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ead43fa commit 98b4dd5

7 files changed

Lines changed: 121 additions & 3 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ jobs:
6565
--title "chore(release): sync manifests to v${VERSION}" \
6666
--body "Automated post-release manifest alignment. Sets package.json, manifest.json, and CHANGELOG.md to the released ${VERSION}." \
6767
--base main --head "${BRANCH}"
68-
gh pr merge --auto --squash --delete-branch || echo "auto-merge unavailable — merge the sync PR after CI is green"
68+
gh pr merge --auto --squash --delete-branch

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Catalog refresh and post-release manifest PRs auto-merge when required CI is green.
13+
1014
## [0.7.0] - 2026-08-28
1115

1216
### Changed

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ bun run check # oxlint + oxfmt + bun test + tsc (same stack as workit
8484
bun run sync -- --remote # refresh models.json + manifest.json from command-code@latest
8585
```
8686

87-
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`.
87+
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`.
8888

8989
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.
9090

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Auto-merge sync PRs Implementation Plan
2+
3+
> **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.
4+
5+
**Spec:** `docs/2026-08-28-auto-merge-sync-prs/spec.md`
6+
**Branch:** `feature/2026-08-28-auto-merge-sync-prs`
7+
8+
**Goal:** Queue GitHub auto-merge on bot catalog and post-release manifest PRs; fail the job if queueing fails.
9+
10+
**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).
11+
12+
**Tech Stack:** GitHub Actions, `gh`, Bun tests (`tests/unit/release-workflow.test.ts`).
13+
14+
## Global Constraints
15+
16+
- PRs against `BrainerVirus/opencode-commandcode` base `main`. Squash + delete source branch.
17+
- In-place `feature/2026-08-28-auto-merge-sync-prs` (no worktrees).
18+
- Conventional commit `ci:` so path-gated semantic-release does not publish.
19+
- Do not fold this package into workit.
20+
21+
---
22+
23+
### Task 1: Fail loud on manifest auto-merge; queue catalog PRs
24+
25+
**Files:**
26+
- Modify: `.github/workflows/release.yml`
27+
- Modify: `scripts/catalog-sync-ci.ts`
28+
- Modify: `tests/unit/release-workflow.test.ts`
29+
- Modify: `README.md`
30+
31+
- [ ] **Step 1: Write the failing test**
32+
33+
In `tests/unit/release-workflow.test.ts`, on the sync step `run` string:
34+
35+
- still contains `gh pr merge --auto --squash --delete-branch`
36+
- does **not** contain `auto-merge unavailable` or `|| echo`
37+
38+
On `scripts/catalog-sync-ci.ts` source: contains `gh pr merge --auto --squash --delete-branch`.
39+
40+
- [ ] **Step 2: Run the test and confirm it fails**
41+
42+
```bash
43+
bun test tests/unit/release-workflow.test.ts
44+
```
45+
46+
- [ ] **Step 3: Implement**
47+
48+
`release.yml`: drop `|| echo "auto-merge unavailable — merge the sync PR after CI is green"`.
49+
50+
`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.
51+
52+
README Development: catalog and manifest-sync PRs auto-merge when the five checks are green.
53+
54+
- [ ] **Step 4: Run tests and `bun run check`**
55+
56+
```bash
57+
bun test tests/unit/release-workflow.test.ts
58+
bun run check
59+
```
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Auto-merge catalog and manifest sync PRs
2+
3+
Status: approved (2026-08-28)
4+
**Branch:** `feature/2026-08-28-auto-merge-sync-prs`
5+
6+
## Goal
7+
8+
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.
9+
10+
## Locked (do not reopen)
11+
12+
- `main` stays protected. Bots still open PRs; they do not push commits onto `main`.
13+
- Merge method stays squash + delete source branch (repo default).
14+
- 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.
15+
- Path-gated releases stay: this change is CI/scripts/docs only and must not cut an npm version.
16+
- Do not fold this package into workit.
17+
18+
## Requirements
19+
20+
- G1: Repo setting **Allow auto-merge** is on (`allow_auto_merge: true`). Without it, `gh pr merge --auto` cannot queue merge-when-green.
21+
- G2: After opening `chore/manifest-sync-v*`, the release job queues auto-merge. If queueing fails, the job **fails** (no `|| echo` swallow).
22+
- G3: After opening or updating `chore/catalog-sync`, catalog-sync CI queues the same auto-merge. Failure fails the job.
23+
- G4: Required checks stay `check (test|typecheck|pack|lint|format)`. Auto-merge waits for those; it does not skip them.
24+
25+
## Non-goals
26+
27+
- Bypassing branch protection or required status checks.
28+
- Auto-merging human feature PRs.
29+
- Changing semantic-release so it commits version bumps to `main` directly.
30+
31+
## Constraints / Architecture
32+
33+
```mermaid
34+
flowchart LR
35+
release[semantic-release publishes npm] --> pr[Open chore PR]
36+
catalog[catalog-sync extract] --> pr2[Open or update catalog PR]
37+
pr --> queue["gh pr merge --auto --squash"]
38+
pr2 --> queue
39+
queue --> ci[Required checks]
40+
ci --> squash[Squash into main]
41+
```
42+
43+
`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).

‎scripts/catalog-sync-ci.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ function openOrUpdateCatalogBreak(input: { commandCodeVersion: string; error: st
6666
});
6767
}
6868

69+
function queuePrAutoMerge(): void {
70+
execSync("gh pr merge --auto --squash --delete-branch", { cwd: ROOT, stdio: "inherit" });
71+
}
72+
6973
function openCatalogPr(commandCodeVersion: string): void {
7074
const status = git(`status --porcelain -- ${CATALOG_FILES.join(" ")}`);
7175
if (!status) {
@@ -88,12 +92,14 @@ function openCatalogPr(commandCodeVersion: string): void {
8892
const prs = JSON.parse(existing) as Array<{ number: number }>;
8993
if (prs.length > 0) {
9094
console.log(`updated catalog PR #${prs[0].number}`);
95+
queuePrAutoMerge();
9196
return;
9297
}
9398
execSync(
94-
`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.`)}`,
99+
`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.`)}`,
95100
{ cwd: ROOT, stdio: "inherit" },
96101
);
102+
queuePrAutoMerge();
97103
}
98104

99105
async function main(): Promise<void> {

‎tests/unit/release-workflow.test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ describe("release.yml", () => {
119119
const sync = wf.jobs.release.steps.find((s) => s.name === "Sync release manifests to main");
120120
expect(sync?.run).toContain("CHANGELOG.md");
121121
expect(sync?.run).toMatch(/git add package\.json manifest\.json CHANGELOG\.md/);
122+
expect(sync?.run).toContain("gh pr merge --auto --squash --delete-branch");
123+
expect(sync?.run).not.toContain("auto-merge unavailable");
124+
expect(sync?.run).not.toMatch(/\|\|\s*echo/);
122125
});
123126
});
124127

@@ -143,6 +146,9 @@ describe("catalog-sync.yml", () => {
143146
.map((s) => `${s.run ?? ""}\n${JSON.stringify(s.env ?? {})}`)
144147
.join("\n");
145148
expect(blob).toContain("catalog-sync-ci.ts");
149+
expect(read("scripts/catalog-sync-ci.ts")).toContain(
150+
"gh pr merge --auto --squash --delete-branch",
151+
);
146152
expect(blob).not.toMatch(/\bnpm publish\b/);
147153
expect(blob).not.toContain("semantic-release");
148154
expect(blob).not.toContain("publish-if-needed");

0 commit comments

Comments
 (0)