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
149 changes: 149 additions & 0 deletions .github/workflows/superpowers-upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: Superpowers dependency upgrade

on:
schedule:
- cron: "47 6 * * *"
workflow_dispatch:
inputs:
superpowers-version:
description: "Explicit stable Superpowers version"
required: false
type: string
validate-only:
description:
"Resolve and validate without changing files or opening a PR"
required: false
default: false
type: boolean

permissions:
contents: read

concurrency:
group: superpowers-dependency-upgrade
cancel-in-progress: false

jobs:
upgrade:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes

- name: Update Superpowers dependency and skill metadata
id: update
env:
GITHUB_TOKEN: ${{ github.token }}
SUPERPOWERS_VERSION: ${{ inputs.superpowers-version }}
VALIDATE_ONLY: ${{ inputs.validate-only || false }}
run: |
set -euo pipefail
mkdir -p .tmp
args=(--summary-json .tmp/superpowers-summary.json)
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
args+=(--mode manual)
[[ -n "${SUPERPOWERS_VERSION:-}" ]] && args+=(--superpowers-version "$SUPERPOWERS_VERSION")
[[ "$VALIDATE_ONLY" == "true" ]] && args+=(--validate-only)
else
args+=(--mode scheduled)
fi
node scripts/update-superpowers.mjs "${args[@]}"
node <<'NODE' >> "$GITHUB_OUTPUT"
const fs = require("node:fs");
const summary = JSON.parse(fs.readFileSync(".tmp/superpowers-summary.json", "utf8"));
console.log(`no_update=${summary.noUpdate ? "true" : "false"}`);
console.log(`validate_only=${summary.validateOnly ? "true" : "false"}`);
console.log(`target_version=${summary.targetVersion}`);
console.log(`target_tag=${summary.targetTag}`);
NODE

- name: Run Pi compatibility contract
if: steps.update.outputs.no_update != 'true'
run: node --test src/cli/commands/init/pi-dependency-contract.test.ts

- name: Run Node tests
if: steps.update.outputs.no_update != 'true'
run: npm test

- name: Run packed artifact smoke test
if: steps.update.outputs.no_update != 'true'
run: node scripts/smoke-packed-artifact.mjs

- name: Run lint
if: steps.update.outputs.no_update != 'true'
run: npm run lint

- name: Verify Nix npm dependency hash
if: steps.update.outputs.no_update != 'true'
run: |
cp nix/package.nix .tmp/nix-package.nix.before-hash
scripts/update-npm-deps-hash.sh
if ! cmp --silent .tmp/nix-package.nix.before-hash nix/package.nix; then
echo "::error file=nix/package.nix::npmDepsHash is stale after the Superpowers update."
exit 1
fi

- name: Build Nix package
if: steps.update.outputs.no_update != 'true'
run: nix build .#patchmill --print-build-logs

- name: Render pull request body
if: >-
steps.update.outputs.no_update != 'true' &&
steps.update.outputs.validate_only != 'true'
run: |
node -e 'import("./scripts/superpowers-upgrade-lib.mjs").then(({renderSuperpowersPullRequestBody}) => { const fs = require("node:fs"); const summary = JSON.parse(fs.readFileSync(".tmp/superpowers-summary.json", "utf8")); fs.writeFileSync(".tmp/superpowers-pr-body.md", renderSuperpowersPullRequestBody(summary)); })'

- name: Create automation bot token
id: app-token
if: >-
steps.update.outputs.no_update != 'true' &&
steps.update.outputs.validate_only != 'true'
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_PLEASE_BOT_APP_ID }}
private-key: ${{ secrets.RELEASE_PLEASE_BOT_PRIVATE_KEY }}

- name: Create or update Superpowers upgrade PR
if: >-
steps.update.outputs.no_update != 'true' &&
steps.update.outputs.validate_only != 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.app-token.outputs.token }}
add-paths: |
package.json
package-lock.json
npm-shrinkwrap.json
nix/package.nix
src/workflow/skill-pack.ts
THIRD_PARTY_NOTICES.md
.patchmill/skills
branch: automation/superpowers-${{ steps.update.outputs.target_tag }}
delete-branch: true
title: >-
chore(deps): update Superpowers to ${{
steps.update.outputs.target_tag }}
body-path: .tmp/superpowers-pr-body.md
commit-message: >-
chore(deps): update Superpowers to ${{
steps.update.outputs.target_tag }}
labels: dependencies, automated-pr
35 changes: 31 additions & 4 deletions docs/pi-dependency-upgrades.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Pi Dependency Upgrades
# Automated Dependency Upgrades

## Pi runtime upgrades

Patchmill keeps `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui`
on exact pins. The `Pi dependency upgrade` workflow discovers matching newer npm
`latest` versions on a schedule and opens a review-gated PR after compatibility,
packed-artifact, npm, and Nix validation pass.

## Manual validation
### Manual validation

```bash
node scripts/update-pi-deps.mjs \
Expand All @@ -18,11 +20,36 @@ node scripts/update-pi-deps.mjs \

Omit `--skip-nix-hash` when preparing real dependency changes.

## Superpowers upgrades

The `Superpowers dependency upgrade` workflow runs daily and opens a dedicated
review-gated pull request when `obra/superpowers` publishes a newer stable
GitHub Release. It does not combine Superpowers and Pi runtime changes.

The pull request includes the upstream release body for every stable release
after the current pin through the target, ordered from oldest to newest. Missing
or empty release notes prevent pull-request creation.

Run a non-mutating local validation with:

```bash
node scripts/update-superpowers.mjs \
--mode manual \
--superpowers-version 6.0.3 \
--validate-only \
--skip-nix-hash \
--summary-json .tmp/superpowers-summary.json
```

Omit `--superpowers-version` to validate discovery of the latest stable release.
Omit `--validate-only` and `--skip-nix-hash` only when intentionally preparing a
real Superpowers upgrade.

## Repository automation credentials

Configure the repository secrets `RELEASE_PLEASE_BOT_APP_ID` and
`RELEASE_PLEASE_BOT_PRIVATE_KEY` for a GitHub App that can create branches and
pull requests. The workflow mints a short-lived installation token only after
pull requests. Both workflows mint a short-lived installation token only after
all upgrade validations pass, so the resulting review-gated PR can trigger
normal pull request checks; GitHub Actions' default `GITHUB_TOKEN` does not
trigger those workflows. Checkout does not persist credentials while dependency
Expand All @@ -39,4 +66,4 @@ scripts/update-npm-deps-hash.sh
nix build .#patchmill --print-build-logs
```

The workflow does not auto-merge or publish Pi dependency upgrades.
Neither workflow auto-merges or publishes dependency upgrades.
Loading
Loading