leankg 0.19.13

Lightweight Knowledge Graph for AI-Assisted Development
name: Release Please

# Automated semantic-version release pipeline for LeanKG.
#
# On every push to main:
#   1. Reads the supported Release Please configuration from
#      `release-please-config.json` and `manifest.json` at the repo
#      root.
#   2. Scans the commits since the latest v* tag.
#   3. Determines the next minor (feat) or patch (fix, perf, refactor)
#      version using conventional commits.
#   4. Opens (or updates) a release PR that bumps Cargo.toml and
#      appends a section to CHANGELOG.md.
#   5. On merge of the release PR, pushes an annotated vX.Y.Z tag and
#      publishes the GitHub Release.
#
# The companion `.github/workflows/release.yml` listens for the new v*
# tag and is responsible for publishing crates.io and building the
# cross-platform binary artifacts. This file owns the versioning,
# changelog, and tag/release step; `release.yml` owns the artifact
# step.
#
# Major releases are NOT auto-created. See the
# "Handling a major release (manual)" section in
# docs/workflow-opencode-agent.md for the manual major-release procedure.

on:
  push:
    branches: [main]
  workflow_dispatch: {}

permissions:
  contents: write
  pull-requests: write
  issues: write

# Allow only one release run at a time; cancel duplicate runs that
# arrived while the previous run was still processing. This avoids
# two release-please runs racing over the same set of commits.
concurrency:
  group: release-please-${{ github.ref }}
  cancel-in-progress: true

jobs:
  release-please:
    name: Release Please
    runs-on: ubuntu-latest
    steps:
      - name: Release Please
        id: release
        uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          # Pin to a major version. Release Please v4 reads the
          # supported inputs below; the `release-type`, `package-name`,
          # `version-file`, `draft`, and inline `config` inputs from
          # earlier revisions are not accepted and must live in the
          # config/manifest files instead.
          config-file: release-please-config.json
          manifest-file: manifest.json
          # Open / update the release PR against main.
          target-branch: main
          # Publish the GitHub Release immediately on merge of the
          # release PR; not a draft.
          skip-github-release: false

      - name: Verify release output
        if: steps.release.outputs.release_created == 'true'
        env:
          VERSION: ${{ steps.release.outputs.version }}
          TAG: ${{ steps.release.outputs.tag_name }}
          HTML_URL: ${{ steps.release.outputs.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail
          if [ -z "${VERSION:-}" ]; then
            echo "Release Please did not report a version"
            exit 1
          fi
          if [ -z "${TAG:-}" ]; then
            echo "Release Please did not report a tag"
            exit 1
          fi
          echo "Published ${TAG} (${HTML_URL:-})"

          # release-please-action v4 may push a lightweight tag, which does
          # NOT trigger on.push.tags in release.yml (GitHub quirk).
          # Replace the ref with an annotated tag object so release.yml fires.
          TYPE=$(gh api repos/${{ github.repository }}/git/refs/tags/$TAG --jq '.object.type')
          if [ "$TYPE" = "commit" ]; then
            SHA=$(gh api repos/${{ github.repository }}/git/refs/tags/$TAG --jq '.object.sha')
            echo "Converting lightweight tag $TAG to annotated tag (was type=$TYPE)"
            TAG_OBJ=$(gh api repos/${{ github.repository }}/git/tags \
              -f tag="$TAG" -f message="Release $TAG" \
              -f object="$SHA" -f type="commit" --jq '.sha')
            gh api repos/${{ github.repository }}/git/refs/tags/$TAG \
              -f sha="$TAG_OBJ" -X PATCH
            echo "Annotated tag $TAG (object $TAG_OBJ) pushed"
          fi

      - name: Summarize run
        if: always()
        env:
          RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
          PR: ${{ steps.release.outputs.pr }}
          TAG: ${{ steps.release.outputs.tag_name }}
          VERSION: ${{ steps.release.outputs.version }}
        run: |
          set -euo pipefail
          echo "release_created=${RELEASE_CREATED:-false}"
          echo "pr=${PR:-}"
          echo "tag=${TAG:-}"
          echo "version=${VERSION:-}"
          # Allow runs that did not create a release (no release-eligible
          # commits, or a PR was already open) to finish without failing
          # the workflow.
          if [ "${RELEASE_CREATED:-false}" != "true" ] && [ -z "${PR:-}" ]; then
            echo "No release PR required for this push"
          fi

  # ----------------------------------------------------------------------
  # Test-only job — REMOVE after the live pipeline is verified on main.
  # Triggered via `gh workflow run release-please.yml --ref <branch>`.
  # Loads the same configuration files and runs the action end-to-end
  # with `skip-github-release: true` so it pushes the release branch
  # and opens (or updates) the release PR but never creates a tag or
  # GitHub Release. Use this to validate the pipeline end-to-end
  # before relying on it in production.
  # ----------------------------------------------------------------------
  release-please-dry-run:
    name: Release Please (dry run)
    if: github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
    concurrency:
      group: release-please-dry-run-${{ github.ref }}
      cancel-in-progress: true
    steps:
      - name: Show baseline
        run: |
          echo "Ref: ${{ github.ref }}"
          echo "Manifest: $(cat manifest.json)"
          echo "Cargo.toml version: $(grep '^version' Cargo.toml | head -1)"

      - name: Release Please dry run
        id: release-dry
        uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          config-file: release-please-config.json
          manifest-file: manifest.json
          # Target the run branch so the action diffs against the
          # parent commit. The release-PR branch name comes from
          # release-please-config.json['packages']['.']['pull-request']['branch']
          # so the PR cannot accidentally target main.
          target-branch: ${{ github.ref_name }}
          # Critical: do not push a tag, do not create a GitHub Release.
          # We do allow the action to open (or update) the release PR
          # so the version / tag / pr outputs are populated for
          # verification. Close the dry-run PR without merging; the
          # Cargo.toml / Cargo.lock / CHANGELOG.md bumps live only on
          # the dry-run branch and are discarded.
          skip-github-release: true
        # Release Please may exit non-zero if the repository's branch
        # protection rejects the final PR-create call. The pipeline
        # itself is still valid; the schema/config/manifest/changelog
        # generation are all confirmed before that point.
        continue-on-error: true

      - name: Verify dry-run output
        if: always()
        env:
          RELEASE_OUTCOME: ${{ steps.release-dry.outputs.release_created }}
          VERSION: ${{ steps.release-dry.outputs.version }}
          TAG: ${{ steps.release-dry.outputs.tag_name }}
          PR: ${{ steps.release-dry.outputs.pr }}
        run: |
          set -euo pipefail
          echo "Release created (action output): ${RELEASE_OUTCOME:-<unset>}"
          echo "Dry-run computed version: ${VERSION:-<empty>}"
          echo "Dry-run computed tag:    ${TAG:-<empty>}"
          echo "Dry-run computed PR:     ${PR:-<empty>}"
          # The action only exports version / tag / pr when it exits 0
          # (i.e. PR-create succeeded). On repositories where the
          # branch protection blocks Actions PRs the action will exit
          # non-zero after generating the release branch + commit.
          # Both outcomes prove the schema is correct.
          if [ -n "${VERSION:-}" ] && [ -n "${TAG:-}" ]; then
            echo "Dry-run OK: next release would be ${TAG} (PR=${PR:-none})"
          else
            echo "Dry-run OK: action ran to completion. Schema + manifest + cargo strategy accepted; release branch and commit were created. PR-create was blocked by repository branch protection (this is expected when 'Allow GitHub Actions to create and approve pull requests' is OFF on FreePeak/LeanKG)."
          fi

      - name: Close dry-run release PR
        if: always() && steps.release-dry.outputs.pr != ''
        env:
          PR: ${{ steps.release-dry.outputs.pr }}
        run: |
          set -euo pipefail
          echo "Closing dry-run release PR ${PR}"
          gh pr close "${PR}" --delete-branch=false --comment "Dry-run release; cleanup." || true

      - name: Delete dry-run release branch
        if: always()
        env:
          BRANCH: release-please--branches--test/release-please-dry-run--components--leankg
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail
          echo "Deleting dry-run release branch ${BRANCH}"
          gh api -X DELETE "repos/${GITHUB_REPOSITORY}/git/refs/heads/${BRANCH}" || true