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
145 changes: 134 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ on:
types: [closed]
branches: [main]

permissions:
contents: write

jobs:
release:
# ===========================================================================
# Step 1: Determine version and commit bump
# ===========================================================================
version:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.version.outputs.version }}
commit_sha: ${{ steps.bump.outputs.commit_sha }}

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -67,46 +74,162 @@ jobs:
jq --arg v "$VERSION" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json

- name: Commit version bump
id: bump
run: |
VERSION="${{ steps.version.outputs.version }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .claude-plugin/plugin.json
# Only commit if there are changes
if git diff --cached --quiet; then
echo "Version files already at $VERSION — no commit needed"
else
git commit -m "bump to v${VERSION}"
git push
fi
echo "commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

# ===========================================================================
# Step 2: Create draft release (tag + release atomically)
# ===========================================================================
create-release:
needs: version
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.create_release.outputs.release_created }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main

- name: Generate release notes
id: notes
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
VERSION="${{ steps.version.outputs.version }}"
VERSION="${{ needs.version.outputs.version }}"
NOTES=$(awk "/^## ${VERSION}$/,/^## /{if(/^## ${VERSION}$/)next; if(/^## /)exit; print}" CHANGELOG.md 2>/dev/null)
if [ -z "$NOTES" ]; then
# Try with v prefix
NOTES=$(awk "/^## v${VERSION}$/,/^## /{if(/^## v${VERSION}$/)next; if(/^## /)exit; print}" CHANGELOG.md 2>/dev/null)
fi
if [ -z "$NOTES" ]; then
NOTES="Merged: ${{ github.event.pull_request.title }}"
NOTES="Merged: ${PR_TITLE}"
fi
{
echo "notes<<EOF"
echo "$NOTES"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Create tag and release
- name: Create draft release
id: create_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NOTES: ${{ steps.notes.outputs.notes }}
run: |
TAG="v${{ steps.version.outputs.version }}"
TAG="v${{ needs.version.outputs.version }}"
COMMIT="${{ needs.version.outputs.commit_sha }}"
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
echo "Tag $TAG already exists — skipping release"
echo "release_created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git tag "$TAG"
# Tag the version-bumped commit, not the stale checkout
git tag "$TAG" "$COMMIT"
git push origin "$TAG"
gh release create "$TAG" --title "$TAG" --notes "${{ steps.notes.outputs.notes }}"
# Create release; clean up tag on failure
if ! gh release create "$TAG" --title "$TAG" --draft --notes "$RELEASE_NOTES"; then
echo "::warning::Release creation failed — cleaning up orphaned tag"
git push origin --delete "$TAG" || true
echo "release_created=false" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "release_created=true" >> "$GITHUB_OUTPUT"

# ===========================================================================
# Step 3: Build binaries (parallel matrix)
# ===========================================================================
binaries:
needs: [version, create-release]
if: needs.create-release.outputs.release_created == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: src
strategy:
matrix:
os: [linux, darwin, windows]
arch: [amd64, arm64]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum

- name: Build binary
run: make build-for GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }}

- name: Upload release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
EXT=""
if [ "${{ matrix.os }}" = "windows" ]; then EXT=".exe"; fi
ASSET="bin/devkit-${{ matrix.os }}-${{ matrix.arch }}${EXT}"
echo "Uploading ${ASSET}"
gh release upload "v${{ needs.version.outputs.version }}" \
"$ASSET" \
--clobber
working-directory: src

# ===========================================================================
# Step 4: Generate checksums and publish release
# ===========================================================================
publish:
needs: [version, create-release, binaries]
if: needs.create-release.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Download all release assets and generate checksums
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ needs.version.outputs.version }}"
mkdir -p /tmp/release-assets
gh release download "$TAG" --dir /tmp/release-assets
cd /tmp/release-assets
sha256sum devkit-* > checksums.txt
cat checksums.txt
gh release upload "$TAG" checksums.txt --clobber

- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release edit "v${{ needs.version.outputs.version }}" --draft=false

# ===========================================================================
# Cleanup on failure
# ===========================================================================
cleanup-on-failure:
needs: [version, create-release, binaries, publish]
if: >-
always() &&
needs.create-release.outputs.release_created == 'true' &&
(needs.binaries.result == 'failure' || needs.binaries.result == 'cancelled' ||
needs.publish.result == 'failure' || needs.publish.result == 'cancelled')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Delete draft release on failure
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ needs.version.outputs.version }}"
echo "::warning::Cleaning up draft release $TAG due to workflow failure"
gh release delete "$TAG" --yes || echo "::warning::Failed to delete release $TAG — manual cleanup required"
git push origin --delete "$TAG" || echo "::warning::Failed to delete tag $TAG — manual cleanup required"
12 changes: 11 additions & 1 deletion commands/pr-ready.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
description: Full PR preparation pipeline — validate branch, DRY review, lint, test, security, changelog, create PR.
---

Run `devkit workflow run pr-ready` to execute the deterministic PR preparation workflow.
Ensure the devkit engine is installed, then run the workflow:

```bash
ENSURE="$(find ~/.claude/plugins ${APPDATA:+$APPDATA/.claude/plugins} ${LOCALAPPDATA:+$LOCALAPPDATA/.claude/plugins} -path '*/devkit/scripts/ensure-engine.sh' 2>/dev/null | head -1)"; [ -n "$ENSURE" ] && bash "$ENSURE" || { echo "devkit plugin not found — install from https://github.com/5uck1ess/devkit/releases"; exit 1; }
```

```bash
devkit workflow run pr-ready
```

If the engine cannot be installed (no network, no write access), tell the user: "The devkit engine binary is required for deterministic workflow execution. Install manually from https://github.com/5uck1ess/devkit/releases" Do NOT fall back to manual steps.
13 changes: 13 additions & 0 deletions commands/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ echo -n "codex plugin: " && (/codex:status >/dev/null 2>&1 && echo "installed" |
echo -n "gemini plugin: " && (/gemini:status >/dev/null 2>&1 && echo "installed" || echo "not installed")
```

### Devkit Engine

```bash
echo "=== Devkit Engine ==="
if command -v devkit >/dev/null 2>&1; then
echo "engine: ✓ installed ($(devkit --version 2>/dev/null || echo 'unknown version'))"
else
echo "engine: ✗ NOT INSTALLED — workflows will not run deterministically"
echo " install: bash \"\$(find ~/.claude/plugins -path '*/devkit/scripts/install-engine.sh' 2>/dev/null | head -1)\""
echo " or download from: https://github.com/5uck1ess/devkit/releases"
fi
```

### External CLIs

```bash
Expand Down
12 changes: 11 additions & 1 deletion commands/tri-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
description: Triple-agent debugging — independent root-cause hypotheses from Claude, Codex, and Gemini, then consensus fix.
---

Run `devkit workflow run tri-debug` to execute the deterministic debug workflow.
Ensure the devkit engine is installed, then run the workflow:

```bash
ENSURE="$(find ~/.claude/plugins ${APPDATA:+$APPDATA/.claude/plugins} ${LOCALAPPDATA:+$LOCALAPPDATA/.claude/plugins} -path '*/devkit/scripts/ensure-engine.sh' 2>/dev/null | head -1)"; [ -n "$ENSURE" ] && bash "$ENSURE" || { echo "devkit plugin not found — install from https://github.com/5uck1ess/devkit/releases"; exit 1; }
```

```bash
devkit workflow run tri-debug
```

If the engine cannot be installed (no network, no write access), tell the user: "The devkit engine binary is required for deterministic workflow execution. Install manually from https://github.com/5uck1ess/devkit/releases" Do NOT fall back to manual steps.
12 changes: 11 additions & 1 deletion commands/tri-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
description: Triple-agent code review — dispatches to Claude, Codex, and Gemini in parallel, consolidates findings.
---

Run `devkit workflow run tri-review` to execute the deterministic review workflow.
Ensure the devkit engine is installed, then run the workflow:

```bash
ENSURE="$(find ~/.claude/plugins ${APPDATA:+$APPDATA/.claude/plugins} ${LOCALAPPDATA:+$LOCALAPPDATA/.claude/plugins} -path '*/devkit/scripts/ensure-engine.sh' 2>/dev/null | head -1)"; [ -n "$ENSURE" ] && bash "$ENSURE" || { echo "devkit plugin not found — install from https://github.com/5uck1ess/devkit/releases"; exit 1; }
```

```bash
devkit workflow run tri-review
```

If the engine cannot be installed (no network, no write access), tell the user: "The devkit engine binary is required for deterministic workflow execution. Install manually from https://github.com/5uck1ess/devkit/releases" Do NOT fall back to manual steps.
12 changes: 11 additions & 1 deletion commands/tri-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
description: Triple-agent security audit — independent security reviews from Claude, Codex, and Gemini, consolidated with severity ranking.
---

Run `devkit workflow run tri-security` to execute the deterministic security audit workflow.
Ensure the devkit engine is installed, then run the workflow:

```bash
ENSURE="$(find ~/.claude/plugins ${APPDATA:+$APPDATA/.claude/plugins} ${LOCALAPPDATA:+$LOCALAPPDATA/.claude/plugins} -path '*/devkit/scripts/ensure-engine.sh' 2>/dev/null | head -1)"; [ -n "$ENSURE" ] && bash "$ENSURE" || { echo "devkit plugin not found — install from https://github.com/5uck1ess/devkit/releases"; exit 1; }
```

```bash
devkit workflow run tri-security
```

If the engine cannot be installed (no network, no write access), tell the user: "The devkit engine binary is required for deterministic workflow execution. Install manually from https://github.com/5uck1ess/devkit/releases" Do NOT fall back to manual steps.
49 changes: 49 additions & 0 deletions scripts/ensure-engine.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Ensure the devkit engine binary is on PATH.
# Finds and runs install-engine.sh if devkit is not installed.
# Called by commands and skills before `devkit workflow run`.

set -euo pipefail

if command -v devkit >/dev/null 2>&1; then
exit 0
fi

# Find install-engine.sh relative to this script (works when called from plugin cache)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INSTALLER="${SCRIPT_DIR}/install-engine.sh"

if [[ ! -f "$INSTALLER" ]]; then
# Fallback: search plugin cache (Unix: ~/.claude, Windows: $APPDATA/.claude)
for PLUGIN_ROOT in "$HOME/.claude/plugins" "${APPDATA:+$APPDATA/.claude/plugins}" "${LOCALAPPDATA:+$LOCALAPPDATA/.claude/plugins}"; do
[[ -z "$PLUGIN_ROOT" ]] && continue
[[ -d "$PLUGIN_ROOT" ]] || continue
INSTALLER=$(find "$PLUGIN_ROOT" -path '*/devkit/scripts/install-engine.sh' 2>/dev/null | head -1)
[[ -n "$INSTALLER" ]] && break
done
fi

if [[ -z "$INSTALLER" ]] || [[ ! -f "$INSTALLER" ]]; then
printf "Cannot find install-engine.sh.\n"
printf "Install manually: https://github.com/5uck1ess/devkit/releases\n"
exit 1
fi

# Source instead of subprocess so PATH exports propagate
# shellcheck disable=SC1090
source "$INSTALLER"

# Verify devkit is now available (handles ~/.local/bin PATH addition)
if ! command -v devkit >/dev/null 2>&1; then
# Last resort: check common install locations directly
for dir in /usr/local/bin "$HOME/.local/bin" "${LOCALAPPDATA:-}/devkit"; do
if [[ -x "${dir}/devkit" ]]; then
export PATH="${dir}:${PATH}"
break
fi
done
if ! command -v devkit >/dev/null 2>&1; then
printf "devkit installed but not on PATH. Add the install directory to your PATH.\n"
exit 1
fi
fi
Loading
Loading