From a8d2d2b0db543c62dfe2c48be654c773e040e91f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 01:31:49 +0000 Subject: [PATCH] chore: adopt agent-driven release process - Add docs/release-process.md documenting the release workflow - Add .github/workflows/release.yml for automatic GitHub Release creation - Update publish.yml to trigger on GitHub Release events - Create CHANGELOG.md with project history - Update AGENTS.md to reference new release process --- .github/workflows/publish.yml | 16 ++- .github/workflows/release.yml | 90 ++++++++++++++++ AGENTS.md | 32 ++---- CHANGELOG.md | 22 ++++ docs/release-process.md | 193 ++++++++++++++++++++++++++++++++++ 5 files changed, 323 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 docs/release-process.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0b3ba1f..7cbe767 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,10 +1,8 @@ name: Publish to crates.io on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-*' + release: + types: [published] workflow_dispatch: inputs: dry_run: @@ -49,7 +47,7 @@ jobs: needs: test runs-on: ubuntu-latest if: >- - github.event_name == 'push' || + github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && (github.event.inputs.crate == 'all' || github.event.inputs.crate == 'fetchkit')) steps: @@ -65,7 +63,7 @@ jobs: run: cargo publish -p fetchkit --dry-run - name: Publish fetchkit - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') run: cargo publish -p fetchkit env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} @@ -75,7 +73,7 @@ jobs: needs: [test, publish-fetchkit] runs-on: ubuntu-latest if: >- - github.event_name == 'push' || + github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && (github.event.inputs.crate == 'all' || github.event.inputs.crate == 'fetchkit-cli')) steps: @@ -85,7 +83,7 @@ jobs: # Wait for crates.io index to update after fetchkit publish - name: Wait for crates.io index - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') run: sleep 30 - name: Verify crate can be packaged @@ -96,7 +94,7 @@ jobs: run: cargo publish -p fetchkit-cli --dry-run - name: Publish fetchkit-cli - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'false') run: cargo publish -p fetchkit-cli env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..711ddb1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,90 @@ +name: Release + +on: + push: + branches: + - main + +jobs: + release: + runs-on: ubuntu-latest + # Only run if commit message matches release pattern + if: ${{ startsWith(github.event.head_commit.message, 'chore(release): prepare v') }} + + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from commit message + id: version + run: | + # Extract version from commit message like "chore(release): prepare v0.1.0" + VERSION=$(echo "${{ github.event.head_commit.message }}" | grep -oP 'prepare v\K[0-9]+\.[0-9]+\.[0-9]+') + if [ -z "$VERSION" ]; then + echo "::error::Could not extract version from commit message" + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Check if tag already exists + id: check_tag + run: | + if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then + echo "::error::Tag v${{ steps.version.outputs.version }} already exists" + exit 1 + fi + echo "Tag does not exist, proceeding..." + + - name: Verify Cargo.toml version matches + run: | + CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + if [ "$CARGO_VERSION" != "${{ steps.version.outputs.version }}" ]; then + echo "::error::Cargo.toml version ($CARGO_VERSION) does not match release version (${{ steps.version.outputs.version }})" + exit 1 + fi + echo "Cargo.toml version matches: $CARGO_VERSION" + + - name: Extract release notes from CHANGELOG.md + id: changelog + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Extract the section for this version from CHANGELOG.md + # Matches from "## [X.Y.Z]" until the next "## [" or end of significant content + NOTES=$(awk -v ver="$VERSION" ' + /^## \[/ { + if (found) exit + if (index($0, "[" ver "]")) found=1 + next + } + found && /^## \[/ { exit } + found { print } + ' CHANGELOG.md) + + if [ -z "$NOTES" ]; then + echo "::warning::No changelog entry found for version $VERSION" + NOTES="Release v$VERSION" + fi + + # Write to file to preserve formatting + echo "$NOTES" > release_notes.md + echo "Release notes extracted:" + cat release_notes.md + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "v${{ steps.version.outputs.version }}" \ + --title "v${{ steps.version.outputs.version }}" \ + --notes-file release_notes.md \ + --target "${{ github.sha }}" + + echo "Created release v${{ steps.version.outputs.version }}" diff --git a/AGENTS.md b/AGENTS.md index 34b6c9d..773c434 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,33 +92,23 @@ specs/ # Feature specifications - Clippy runs with `-D warnings` (warnings are errors) - Doc builds must not have warnings -### Publishing to crates.io +### Releasing -Workflow: `.github/workflows/publish.yml` +See `docs/release-process.md` for full release process documentation. -Triggers: -- Push version tag: `v*.*.*` or `v*.*.*-*` (e.g., `v0.1.0`, `v0.2.0-beta.1`) -- Manual workflow dispatch with dry-run option +Quick summary: +1. Human asks agent: "Create release v0.2.0" +2. Agent updates CHANGELOG.md, Cargo.toml version, creates PR +3. Human reviews and merges PR to main +4. CI creates GitHub Release (release.yml) +5. CI publishes to crates.io (publish.yml) -Process: -1. Runs tests, fmt check, and clippy -2. Publishes `fetchkit` library first -3. Waits for crates.io index update -4. Publishes `fetchkit-cli` +Workflows: +- `.github/workflows/release.yml` - Creates GitHub Release on merge +- `.github/workflows/publish.yml` - Publishes to crates.io on GitHub Release Requirements: - `CARGO_REGISTRY_TOKEN` secret must be configured in repo settings -- Crate metadata (name, version, description, license, repository) in Cargo.toml - -Release steps: -```bash -# 1. Update version in Cargo.toml (workspace level) -# 2. Commit version bump -git commit -am "chore: bump version to 0.2.0" -# 3. Create and push tag -git tag v0.2.0 -git push origin main --tags -``` Note: `fetchkit-python` is not published to crates.io (uses PyPI distribution instead). diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3989698 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### What's Changed + +- feat: add pluggable fetcher system for URL-specific handling ([#9](https://github.com/everruns/fetchkit/pull/9)) by @chaliy +- docs: add LangChain example for MCP integration ([#8](https://github.com/everruns/fetchkit/pull/8)) by @chaliy +- refactor(cli): unified md-first output format ([#7](https://github.com/everruns/fetchkit/pull/7)) by @chaliy +- docs: clarify test classification in AGENTS.md ([#6](https://github.com/everruns/fetchkit/pull/6)) by @chaliy +- docs: add cloud agent env and complete AGENTS.md placeholders ([#5](https://github.com/everruns/fetchkit/pull/5)) by @chaliy +- refactor: rename project from webfetch to fetchkit ([#4](https://github.com/everruns/fetchkit/pull/4)) by @chaliy +- docs: add comprehensive README with installation and usage guide ([#3](https://github.com/everruns/fetchkit/pull/3)) by @chaliy +- feat: implement webfetch library, CLI, MCP server, and Python bindings ([#1](https://github.com/everruns/fetchkit/pull/1)) by @chaliy +- feat: add initial webfetch spec and guidance by @chaliy + +[Unreleased]: https://github.com/everruns/fetchkit/compare/HEAD...HEAD diff --git a/docs/release-process.md b/docs/release-process.md new file mode 100644 index 0000000..0dfc5ad --- /dev/null +++ b/docs/release-process.md @@ -0,0 +1,193 @@ +# Release Process + +## Abstract + +This document describes the release process for fetchkit. Releases are initiated by asking a coding agent to prepare the release, with CI automation handling the rest. + +## Versioning + +fetchkit follows [Semantic Versioning](https://semver.org/): + +- **MAJOR** (X.0.0): Breaking API changes +- **MINOR** (0.X.0): New features, backward compatible +- **PATCH** (0.0.X): Bug fixes, backward compatible + +## Release Workflow + +### Overview + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Human asks │ │ Agent creates │ │ GitHub │ │ crates.io │ +│ "release v0.2" │────>│ release PR │────>│ Release │────>│ Publish │ +│ │ │ │ │ (automatic) │ │ (automatic) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +### Human Steps + +1. **Ask the agent** to create a release: + - "Create release v0.2.0" + - "Prepare a patch release" + - "Release the current changes as v0.2.0" + +2. **Review the PR** created by the agent + +3. **Merge to main** - CI handles GitHub Release and crates.io publish + +### Agent Steps (automated) + +When asked to create a release, the agent: + +1. **Determine version** + - Use version specified by human, OR + - Suggest next version based on changes (patch/minor/major) + +2. **Update CHANGELOG.md** + - Move items from `[Unreleased]` to new version section + - Add release date: `## [X.Y.Z] - YYYY-MM-DD` + - Add breaking changes section if applicable (see format below) + - List commits in GitHub-style format with PR links and contributors + - Update comparison links at bottom of file + +3. **Update Cargo.toml** + - Set `version = "X.Y.Z"` in workspace + +4. **Run verification** + - `cargo fmt --check` + - `cargo clippy` + - `cargo test` + +5. **Commit and push** + - Commit message: `chore(release): prepare vX.Y.Z` + - Push to feature branch + +6. **Create PR** + - Title: `chore(release): prepare vX.Y.Z` + - Include changelog excerpt in description + +### CI Automation + +**On merge to main** (release.yml): +- Detects commit message `chore(release): prepare vX.Y.Z` +- Extracts release notes from CHANGELOG.md +- Creates GitHub Release with tag `vX.Y.Z` + +**On GitHub Release created** (publish.yml): +- Runs verification (fmt, clippy, tests) +- Publishes `fetchkit` and `fetchkit-cli` to crates.io + +## Pre-Release Checklist + +The agent verifies before creating a release PR: + +- [ ] All CI checks pass on main +- [ ] `cargo fmt` - code is formatted +- [ ] `cargo clippy` - no warnings +- [ ] `cargo test` - all tests pass +- [ ] CHANGELOG.md has entries for changes since last release + +## Changelog Format + +The changelog follows [Keep a Changelog](https://keepachangelog.com/) with GitHub-style commit listings. + +### Structure + +```markdown +## [X.Y.Z] - YYYY-MM-DD + +### Breaking Changes + +- **Short description**: Detailed explanation of what changed and migration steps. + - Before: `/old/path` + - After: `/new/path` + +### What's Changed + +- Commit message ([#PR](https://github.com/everruns/fetchkit/pull/PR)) by @contributor +- Another commit ([#PR](https://github.com/everruns/fetchkit/pull/PR)) by @contributor +``` + +### Generating Commit List + +Get commits since last release, excluding chore/ci/bench commits: + +```bash +git log --oneline | grep -v -E "^.{7} (chore|ci|bench)" +``` + +Format each commit as: +``` +- ([#](https://github.com/everruns/fetchkit/pull/)) by @ +``` + +### Breaking Changes Section + +Include when the release has breaking changes (typically MINOR or MAJOR versions): + +1. **Bold summary** of the breaking change +2. **Migration guide** showing before/after +3. **Code examples** if helpful + +Example: +```markdown +### Breaking Changes + +- **Renamed crate from webfetch to fetchkit**: All imports need to be updated. + - `webfetch::fetch` → `fetchkit::fetch` + - CLI binary: `webfetch` → `fetchkit` +``` + +## Workflows + +### release.yml + +- **Trigger**: Push to `main` with commit message starting with `chore(release): prepare v` +- **Actions**: Creates GitHub Release with tag and release notes +- **File**: `.github/workflows/release.yml` + +### publish.yml + +- **Trigger**: GitHub Release published +- **Actions**: Verifies and publishes to crates.io +- **File**: `.github/workflows/publish.yml` +- **Secret required**: `CARGO_REGISTRY_TOKEN` + +## Example Conversation + +``` +Human: Create release v0.2.0 + +Agent: I'll prepare the v0.2.0 release. Let me: +1. Update CHANGELOG.md with the v0.2.0 section +2. Update Cargo.toml version to 0.2.0 +3. Run verification checks +4. Create the release PR + +[Agent performs steps...] + +Done. PR created: https://github.com/everruns/fetchkit/pull/XX +Please review and merge to trigger the release. +``` + +## Hotfix Releases + +For urgent fixes: + +1. Ask agent: "Create patch release v0.1.1 for the auth fix" +2. Agent prepares release with patch version +3. Review and merge + +## Release Artifacts + +Each release includes: + +- **GitHub Release**: Tag, release notes, source archives +- **crates.io**: Published crates for `cargo install fetchkit-cli` + +Note: `fetchkit-python` is not published to crates.io (uses PyPI distribution instead). + +Future considerations: +- Pre-built binaries (Linux, macOS, Windows) +- Docker images +- Homebrew formula