Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
59 changes: 59 additions & 0 deletions docs/2026-08-28-auto-merge-sync-prs/plan.md
Original file line number Diff line number Diff line change
@@ -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
```
43 changes: 43 additions & 0 deletions docs/2026-08-28-auto-merge-sync-prs/spec.md
Original file line number Diff line number Diff line change
@@ -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).
8 changes: 7 additions & 1 deletion scripts/catalog-sync-ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<void> {
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/release-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});

Expand All @@ -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");
Expand Down