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
21 changes: 19 additions & 2 deletions .github/prompts/classify-pr.prompt.yml
Review prompt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ messages:
- role: system
content: |
You classify pull requests for release note categorization.
Respond with exactly one word: bug, enhancement, or documentation.
Respond with JSON: {"label": "bug"}, {"label": "enhancement"}, or {"label": "documentation"}.

- bug: corrects wrong behavior, broken defaults, incorrect error codes,
retry/backoff defects, auth handling bugs, compatibility regressions.
Expand All @@ -19,6 +19,23 @@ messages:
When a PR mixes categories: bug > enhancement > documentation.
Prefer diff evidence over the PR title.
model: openai/gpt-4o-mini
responseFormat: json_schema
jsonSchema: |-
{
"name": "classification",
"strict": true,
"schema": {
"type": "object",
"properties": {
"label": {
"type": "string",
"enum": ["bug", "enhancement", "documentation"]
}
},
"required": ["label"],
"additionalProperties": false
}
}
modelParameters:
maxCompletionTokens: 10
maxCompletionTokens: 25
temperature: 0
23 changes: 12 additions & 11 deletions .github/workflows/ai-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ jobs:
classify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Build prompt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.number }}
run: |
PR=${{ github.event.pull_request.number }}
gh pr diff "$PR" > /tmp/pr.diff
gh pr view "$PR" --json title --jq .title > /tmp/pr-title.txt
gh pr view "$PR" --json body --jq '.body // ""' > /tmp/pr-body.txt
Expand Down Expand Up @@ -70,20 +70,22 @@ jobs:

- name: Classify
id: classify
uses: actions/ai-inference@v2
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2
with:
prompt-file: /tmp/prompt.yml

- name: Apply label
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RESPONSE_FILE: ${{ steps.classify.outputs.response-file }}
PR: ${{ github.event.pull_request.number }}
run: |
LABEL=$(echo "${{ steps.classify.outputs.response }}" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
LABEL=$(jq -r '.label // empty' "$RESPONSE_FILE" 2>/dev/null || cat "$RESPONSE_FILE")
LABEL=$(printf '%s' "$LABEL" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
case "$LABEL" in
bug|enhancement|documentation) ;;
*) echo "Unexpected: $LABEL — skipping"; exit 0 ;;
esac
PR=${{ github.event.pull_request.number }}
CURRENT=$(gh pr view "$PR" --json labels --jq '.labels[].name')
for L in bug enhancement documentation; do
if [ "$L" != "$LABEL" ] && echo "$CURRENT" | grep -qx "$L"; then
Expand All @@ -97,15 +99,14 @@ jobs:
breaking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Build prompt
id: cmd-diff
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.number }}
run: |
PR=${{ github.event.pull_request.number }}

PATTERNS=(
"internal/cmd/*.go"
)
Expand Down Expand Up @@ -171,16 +172,17 @@ jobs:
- name: Detect breaking changes
if: steps.cmd-diff.outputs.skip != 'true'
id: detect
uses: actions/ai-inference@v2
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2
with:
prompt-file: /tmp/prompt.yml

- name: Apply breaking label
if: steps.cmd-diff.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RESPONSE_FILE: ${{ steps.detect.outputs.response-file }}
PR: ${{ github.event.pull_request.number }}
run: |
RESPONSE_FILE="${{ steps.detect.outputs.response-file }}"
if [ -z "$RESPONSE_FILE" ] || [ ! -f "$RESPONSE_FILE" ]; then
echo "::warning::Model response file is missing; skipping breaking label."
exit 0
Expand All @@ -197,7 +199,6 @@ jobs:
exit 0
fi
BREAKING=$(jq -r '.breaking' "$RESPONSE_FILE")
PR=${{ github.event.pull_request.number }}

if [ "$BREAKING" = "true" ]; then
ITEMS=$(jq -r '.items[]' "$RESPONSE_FILE" | sed 's/^/- /')
Expand Down
30 changes: 15 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,64 +12,64 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: make test

vet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: make vet

fmt-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: make fmt-check

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: make build

surface-compat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: go test ./internal/cmd/ -run TestSurface -v

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@v9
- uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9
with:
version: v2.10.1

test-race:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod
- run: go test -race ./...
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
steps:
- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

Expand Down
64 changes: 39 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
environment: release
permissions:
contents: write
id-token: write
models: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
with:
go-version-file: go.mod

Expand Down Expand Up @@ -65,10 +69,10 @@ jobs:
fi

- name: Install Cosign
uses: sigstore/cosign-installer@v3
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v3

- name: Install Syft
uses: anchore/sbom-action/download-syft@v0
uses: anchore/sbom-action/download-syft@17ae1740179002c89186b61233e0f892c3118b11 # v0

- name: Generate shell completions
run: |
Expand Down Expand Up @@ -125,25 +129,19 @@ jobs:

- name: Generate AI changelog
id: ai-changelog
uses: actions/ai-inference@v2
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2
continue-on-error: true
with:
prompt-file: /tmp/prompt.yml

- name: Set changelog env
- name: Set changelog path
id: changelog
env:
RESPONSE_FILE: ${{ steps.ai-changelog.outputs.response-file }}
run: |
RESPONSE_FILE="${{ steps.ai-changelog.outputs.response-file }}"
if [ -n "$RESPONSE_FILE" ] && [ -f "$RESPONSE_FILE" ]; then
sed -i '/^```\(markdown\)\?$/d' "$RESPONSE_FILE"
DELIM="CHANGELOG_DELIM_$(openssl rand -hex 8)"
{
echo "RELEASE_CHANGELOG<<${DELIM}"
cat "$RESPONSE_FILE"
echo ""
echo "${DELIM}"
} >> $GITHUB_ENV
else
echo "RELEASE_CHANGELOG=" >> $GITHUB_ENV
echo "file=$RESPONSE_FILE" >> "$GITHUB_OUTPUT"
fi

- name: Verify macOS signing secrets
Expand All @@ -168,25 +166,35 @@ jobs:

- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ vars.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
repositories: homebrew-tap

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
with:
version: '~> v2'
args: release --clean
install-only: true

- name: Run GoReleaser
env:
CHANGELOG_FILE: ${{ steps.changelog.outputs.file }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_TOKEN: ${{ steps.app-token.outputs.token }}
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
run: |
RELEASE_CHANGELOG=""
if [ -n "$CHANGELOG_FILE" ] && [ -f "$CHANGELOG_FILE" ]; then
RELEASE_CHANGELOG=$(cat "$CHANGELOG_FILE")
fi
export RELEASE_CHANGELOG
goreleaser release --clean

- name: Check CLI surface compatibility
run: |
Expand Down Expand Up @@ -218,9 +226,11 @@ jobs:
aur-publish:
runs-on: ubuntu-latest
needs: release
permissions:
contents: read
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Check AUR secret
id: check
Expand Down Expand Up @@ -248,12 +258,14 @@ jobs:
cancel-in-progress: false
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Generate token for skills repo
id: skills-token
uses: actions/create-github-app-token@v2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ vars.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Expand All @@ -272,13 +284,15 @@ jobs:
if: failure()
env:
GH_TOKEN: ${{ steps.skills-token.outputs.token }}
REF_NAME: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
run: |
TITLE="Skills sync failure"
BODY="The automatic skills sync from [hey-cli ${{ github.ref_name }}](https://github.com/basecamp/hey-cli/actions/runs/${{ github.run_id }}) failed. Check the workflow run for details."
BODY="The automatic skills sync from [hey-cli ${REF_NAME}](https://github.com/basecamp/hey-cli/actions/runs/${RUN_ID}) failed. Check the workflow run for details."
existing=$(gh issue list --repo basecamp/skills --state open --search "in:title $TITLE" --json number,title --jq '[.[] | select(.title == "'"$TITLE"'")][0].number // empty' 2>/dev/null || true)
if [ -n "$existing" ]; then
gh issue comment --repo basecamp/skills "$existing" --body "$BODY" || true
else
gh issue create --repo basecamp/skills --title "$TITLE" --body "$BODY" || true
fi
echo "::error::Skills sync to basecamp/skills failed for ${{ github.ref_name }}. See https://github.com/basecamp/hey-cli/actions/runs/${{ github.run_id }}"
echo "::error::Skills sync to basecamp/skills failed for ${REF_NAME}. See https://github.com/basecamp/hey-cli/actions/runs/${RUN_ID}"
Loading
Loading