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
5 changes: 5 additions & 0 deletions .github/release-state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"pullRequest": 7,
"sourceSHA": "0607dc761f2bff2bafca5c820f1427109cc13fde",
"version": "0.6.3"
}
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ name: CI

on:
pull_request:
push:
branches: [main, master]
workflow_dispatch:
branches: [release]
types: [opened, synchronize, reopened, ready_for_review, edited]

permissions:
contents: read
Expand Down Expand Up @@ -34,6 +33,8 @@ jobs:
cache: npm
- name: Install dependencies
run: npm ci
- name: Verify release notes and manifests
run: node scripts/release-pipeline.mjs verify-feature
- name: Lint, typecheck, test, and build
run: npm run check
- name: Verify package installation
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/promotion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Release promotion

on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review, edited]

permissions:
contents: read

concurrency:
group: promotion-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
ready:
name: Release ready
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out the proposed release head
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
- name: Install validation dependencies
run: npm ci --ignore-scripts
- name: Verify published version and README without building a package
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/release-pipeline.mjs verify-promotion
77 changes: 24 additions & 53 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,80 +1,51 @@
name: Release

on:
pull_request:
branches: [release]
types: [closed]
push:
tags: ['v[0-9]*', '[0-9]*']

permissions:
contents: read

# Automatic and manual releases share one publisher. Never cancel an active publication.
concurrency:
group: release-${{ github.ref }}
group: release-publication
cancel-in-progress: false

jobs:
package:
name: Build and verify release package
release:
name: Publish from release and open promotion PR
if: github.event_name == 'push' || github.event.pull_request.merged == true
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
prerelease: ${{ steps.version.outputs.prerelease }}
filename: ${{ steps.package.outputs.filename }}
timeout-minutes: 25
permissions:
contents: write
pull-requests: write
steps:
- name: Check out the tag
- name: Check out release
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: release
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Verify package version matches tag
id: version
- name: Install pipeline dependencies
run: npm ci --ignore-scripts
- name: Prepare version, publish package, and promote through a PR
env:
RELEASE_TAG: ${{ github.ref_name }}
run: node scripts/release-version.mjs "$RELEASE_TAG"
- name: Lint, typecheck, test, and build
run: npm run check
- name: Pack and verify installation
id: package
run: npm run package:check -- "$RUNNER_TEMP/release"
- name: Upload verified release files
GH_TOKEN: ${{ github.token }}
run: node scripts/release-pipeline.mjs
- name: Preserve built release files for diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-package
path: |
${{ runner.temp }}/release/*.tgz
${{ runner.temp }}/release/*.sha256
if-no-files-found: error
path: ${{ runner.temp }}/release/*
if-no-files-found: ignore
retention-days: 7

publish:
name: Publish GitHub Release
needs: package
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- name: Download verified release files
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-package
path: release
- name: Create release with package and checksum
working-directory: release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
PRERELEASE: ${{ needs.package.outputs.prerelease }}
PACKAGE_FILE: ${{ needs.package.outputs.filename }}
run: |
sha256sum --check "$PACKAGE_FILE.sha256"
args=(--repo "$GITHUB_REPOSITORY" --verify-tag --title "$RELEASE_TAG" --generate-notes)
if [[ "$PRERELEASE" == "true" ]]; then
args+=(--prerelease --latest=false)
fi
gh release create "$RELEASE_TAG" "$PACKAGE_FILE" "$PACKAGE_FILE.sha256" "${args[@]}"
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Use English for all user-facing prompts, UI labels, errors, generated bot messages, examples, and documentation. Do not hard-code a personal account in defaults.

Never push commits directly to `main` or modify its files through GitHub APIs.
All changes reach `main` by merging a PR from `release`. Feature PRs target
`release`; version and post-publication README commits belong on `release`.

## Project context

This repository implements issue-to-PR automation for OpenCode **2**: a scheduler,
Expand All @@ -24,6 +28,7 @@ steps, project setup, headless operation, and removal.
| [docs/runtime.md](docs/runtime.md) | User-visible behavior while the bot runs: GitHub questions and permission replies, branch selection, media inputs, prompt loading, follow-up comments, session tabs, and routine management commands. | Use when changing issue conversations, session continuation, runtime tools, or TUI behavior. |
| [docs/advanced.md](docs/advanced.md) | Separate scheduler/dispatcher setup, multiple repositories, custom RPC jobs, full options, timeouts, management and retry commands, persistence, reconciliation, locks, and known limits. | Use for low-level configuration, operational troubleshooting, recovery, or ownership/concurrency changes. |
| [docs/installation.md](docs/installation.md) | Loader registration, config-directory precedence, prerequisites, source installation, project-local installation, upgrade conflicts, testing on another machine, and migration limits. | Use when working on packaging, installers, registration, upgrades, or deployment troubleshooting. |
| [docs/releases.md](docs/releases.md) | Feature-to-release PR checks, automatic patch versions, manual npm version/tag releases, exact changelog notes, publication recovery, README commits on release, and promotion PRs into protected main. | Use for CI triggers, versioning, packaging, GitHub Release publication, branch permissions, or recovery after a failed release. |

For common investigations:

Expand All @@ -38,6 +43,12 @@ For common investigations:
- **Why is polling inactive or duplicated?** Read architecture ownership,
workflow section 1, and installation registration details.

For release changes, read `docs/releases.md`, `CHANGELOG.md`, and the workflows
under `.github/workflows/`. `scripts/release-pipeline.mjs` handles automatic and
manual releases, retry state, and promotion. `scripts/release-notes.mjs` extracts
the exact tagged changelog section; `scripts/update-release-readme.mjs` renders
the installation block without making remote writes. Keep its markers intact.

## From documentation to source

- `src/index.ts` loads the combined plugin; `src/plugins/` contains the scheduler
Expand Down
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Changelog

Release descriptions come from the exact version section committed with the tag.
Add feature changes under `Unreleased`; after a PR merges into `release`, automation
moves them into the new patch version's section. For a manual release, prepare
and commit the exact version section before creating its tag. Prerelease headings
include the full version, for example `## 0.7.0-beta.1`.

## Unreleased

## 0.6.3

### Fixed

- Generate release descriptions from the matching changelog section, so publishing
a tag before its PR merges no longer produces notes about an earlier release.
- Show the latest published stable package's versioned download URL in the README
on `release` after successful publication, then promote it to `main` through a PR.

### Release process

- Run full CI when feature PRs target `release`, including new commits to open PRs.
Ordinary feature pushes no longer run CI or build packages.
- Publish an automatic patch after a PR merges into `release`, and support manual
version tags on that branch without a second version bump.
- Recover interrupted publication without moving tags or republishing completed
packages. Commit README on `release` before opening or updating its PR to `main`.
- Keep `main` changes behind PR merges and verify that promotion contains the
published package's code and updated download links.

## 0.6.2

### Fixed

- Keep the automation owner service active with a heartbeat during long-running
worker sessions, preserving dispatcher access for task completion and PR publication.
- Verify the owner process before sending heartbeat requests and prevent overlapping
requests with a bounded timeout.
- Attempt every cleanup step during shutdown, releasing scheduler and dispatcher
locks even when another cleanup fails, so the plugin can start again.

### Validation and documentation

- Add regression coverage for owner heartbeats and shutdown cleanup failures.
- Document owner lifetime, lock cleanup, and recovery behavior.

## 0.6.1

### Documentation

- Add detailed bot workflow diagrams and a documentation map in `AGENTS.md`.
- Expand bundled bot instructions for planning, delegation, verification, and
handing publication back to the dispatcher.
- Add the README banner showing an OpenCode2 agent executing a task.
91 changes: 57 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,28 @@ Nothing needs to be published to npm. `$HOME` expands to your home directory.

## Install from a .tgz package

1. Download/copy the archive to the machine running OpenCode 2 and install it
(replace `VERSION` with the downloaded version):
Run this command on the machine running OpenCode 2:

```bash
npm install --global --prefix "$HOME/.local" "$HOME/Downloads/opencode2-automation-VERSION.tgz"
```
<!-- latest-release:start -->
Latest stable release: **[v0.6.3](https://github.com/d3cker/opencode2-github-automation/releases/tag/v0.6.3)**.

`postinstall` registers both the plugin and TUI automatically. No `sudo`,
source checkout, or manual config editing is needed. Do not add
`--ignore-scripts`; npm needs network access to install dependencies.
[Download the .tgz package](https://github.com/d3cker/opencode2-github-automation/releases/download/v0.6.3/opencode2-automation-0.6.3.tgz) · [SHA-256 checksum](https://github.com/d3cker/opencode2-github-automation/releases/download/v0.6.3/opencode2-automation-0.6.3.tgz.sha256)

2. Restart the service when its sessions are idle:
```bash
npm install --global --prefix "$HOME/.local" "https://github.com/d3cker/opencode2-github-automation/releases/download/v0.6.3/opencode2-automation-0.6.3.tgz"
```
<!-- latest-release:end -->

```bash
opencode2 service restart
```
`postinstall` registers both the plugin and TUI automatically. No `sudo`,
source checkout, or manual config editing is needed. Do not add
`--ignore-scripts`; npm needs network access to install dependencies.
You can also download the archive and pass its local path to the same command.

Restart the service when its sessions are idle:

```bash
opencode2 service restart
```

The CLI is now at `$HOME/.local/bin/opencode2-automation`. If `$HOME/.local/bin`
is on your PATH, you can use the shorter `opencode2-automation` command.
Expand Down Expand Up @@ -122,15 +128,13 @@ A reboot-only task does not handle later `opencode2 service restart` calls.

## Update from a .tgz package

Wait for active bot work to finish. Download the new archive, then:
Wait for active bot work to finish, then:

1. Install the new file using the **same prefix** as before:
1. Run the versioned command in [Install from a .tgz package](#install-from-a-tgz-package)
using the **same prefix** as before. After publication, the README on `release`
links to the new stable package; `main` receives that link through the promotion PR.

```bash
npm install --global --prefix "$HOME/.local" /absolute/path/to/opencode2-automation-NEW_VERSION.tgz
```

Replace the example path with your archive. `postinstall` refreshes registration;
`postinstall` refreshes registration;
project settings and queues are preserved. Do not run `init` again.

2. Reload the service:
Expand Down Expand Up @@ -287,22 +291,41 @@ copy it to another machine and follow the `.tgz` instructions above.

## GitHub Actions and releases

- **Pull requests:** CI runs ESLint, type checking, unit tests, a build, and a
package installation check on Node 22 and 24. Pushes to `main`/`master` also run CI.
- **Releases:** push a SemVer tag to build and publish a GitHub Release with the
`.tgz` and SHA-256 checksum. CI verifies that the tag matches the committed
version in `package.json` and `package-lock.json`. Tests and package installation must pass.

For a stable release, merge the PR first, then update your local `main` branch.
With a clean working tree, run (replace `0.6.0` with your next unused version):
1. Work on a feature branch and add release notes under `Unreleased` in
[CHANGELOG.md](CHANGELOG.md). Ordinary branch pushes do not run CI or publish packages.
2. Open a PR into the long-lived `release` branch. CI runs lint, type checking,
tests, a build, and an installation check on Node 22 and 24. New commits to
the open PR rerun these checks. Review and merge after they pass.
3. The merge starts **Release**. It increments the patch version on `release`,
moves the unreleased notes into that version's changelog section, and pushes
the version commit and tag atomically. It builds and verifies the tagged
package, then publishes the GitHub Release with `.tgz`, SHA-256, and exact
version notes. No package is published to npm.
4. Only after publication succeeds, automation commits the versioned README link
on `release` and opens or updates a PR from `release` into `main`.
5. Review and merge that PR with a **merge commit**. All code, version metadata,
release notes, and README changes reach protected `main` through this PR.
The automation never pushes to `main` or writes its files through the API.

To choose a version manually, prepare and commit its exact changelog section on
`release`, then use `npm version`, for example:

```bash
npm version 0.6.0
git push --atomic origin HEAD v0.6.0
git switch release
git pull --ff-only origin release
# Prepare and commit the CHANGELOG.md section for 1.0.0 first.
npm version 1.0.0
git push --atomic origin release v1.0.0
```

`npm version` updates both manifests, creates a commit, and tags it automatically.
The push sends the current branch and tag together. For testing, use a version
such as `0.7.0-beta.1` on a feature branch; CI marks it as a prerelease.
CI does not rewrite versions or publish to npm. Releases use the built-in
`GITHUB_TOKEN`; no npm token or extra secret is needed.
The pushed tag publishes exactly `1.0.0`, without another version bump. Both
`v1.0.0` and `1.0.0` tag names are accepted. The next automatic patch is `1.0.1`.
Version tags must point to code on `release`; ordinary pushes to that branch
never start publication. Finish the active release before merging another feature.

The README on `release` is updated after publication; the README on `main` changes
when the promotion PR is merged. The tag and packaged README remain snapshots
from before the later README commit.

See [Release process](docs/releases.md) for required repository permissions,
branch protection, CI approval for bot PRs, concurrency, and safe retry procedures.
8 changes: 8 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Start with the [README](../README.md) for `.tgz` or source installation, updates
project configuration, headless startup, and removal. This page covers details
and troubleshooting.

The README's package installation command contains the versioned GitHub asset URL
for the stable release promoted into that branch. After publication, automation
updates README on `release`; its PR carries the update into `main` when merged.
While that PR awaits review, `release` contains the newer download link. For
upgrades, use that branch's current README rather than a copy from an old archive
or tag, and keep the same installation prefix. Prereleases do not replace the
stable link. Maintainer setup and retries are in [Release process](releases.md).

## Package registration

`npm install --global` runs the bundled `postinstall` script. It writes two small
Expand Down
Loading