github/spec-kit

7 workflows · maturity 67% · 8 patterns · GitHub ↗

Security 22.62/100

Practices

✓ Matrix✓ Permissions✓ Security scan○ AI review○ Cache✓ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
14.3
security scan
8.3
supply chain
0
secret handling
0
harden runner
0

Tools: github/codeql-action/analyze, github/codeql-action/init

Workflows (7)

codeql matrix security .github/workflows/codeql.yml
Triggers
push, pull_request
Runs on
ubuntu-latest
Jobs
analyze
Matrix
language→ actions, python
Actions
github/codeql-action/init, github/codeql-action/analyze
View raw YAML
name: "CodeQL"

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    strategy:
      fail-fast: false
      matrix:
        language: [ 'actions', 'python' ]
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v4
        with:
          languages: ${{ matrix.language }}

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v4
        with:
          category: "/language:${{ matrix.language }}"
docs perms .github/workflows/docs.yml
Triggers
push, workflow_dispatch
Runs on
ubuntu-latest, ubuntu-latest
Jobs
build, deploy
Actions
actions/configure-pages, actions/upload-pages-artifact, actions/deploy-pages
Commands
  • dotnet tool install -g docfx
  • cd docs docfx docfx.json
View raw YAML
# Build and deploy DocFX documentation to GitHub Pages
name: Deploy Documentation to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]
    paths:
      - 'docs/**'

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0 # Fetch all history for git info

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'

      - name: Setup DocFX
        run: dotnet tool install -g docfx

      - name: Build with DocFX
        run: |
          cd docs
          docfx docfx.json

      - name: Setup Pages
        uses: actions/configure-pages@v5

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: 'docs/_site'

  # Deploy job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v5

lint perms .github/workflows/lint.yml
Triggers
push, pull_request
Runs on
ubuntu-latest
Jobs
markdownlint
Actions
DavidAnson/markdownlint-cli2-action
View raw YAML
name: Lint
permissions:
  contents: read

on:
  push:
    branches: ["main"]
  pull_request:

jobs:
  markdownlint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Run markdownlint-cli2
        uses: DavidAnson/markdownlint-cli2-action@v23
        with:
          globs: |
            '**/*.md'
            !extensions/**/*.md
release .github/workflows/release.yml
Triggers
push
Runs on
ubuntu-latest
Jobs
release
Commands
  • VERSION=${GITHUB_REF#refs/tags/} echo "tag=$VERSION" >> $GITHUB_OUTPUT echo "Building release for $VERSION"
  • chmod +x .github/workflows/scripts/check-release-exists.sh .github/workflows/scripts/check-release-exists.sh ${{ steps.version.outputs.tag }}
  • chmod +x .github/workflows/scripts/create-release-packages.sh .github/workflows/scripts/create-release-packages.sh ${{ steps.version.outputs.tag }}
  • chmod +x .github/workflows/scripts/generate-release-notes.sh # Get the previous tag for changelog generation PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "") # Default to v0.0.0 if no previous tag is found (e.g., first release) if [ -z "$PREVIOUS_TAG" ]; then PREVIOUS_TAG="v0.0.0" fi .github/workflows/scripts/generate-release-notes.sh ${{ steps.version.outputs.tag }} "$PREVIOUS_TAG"
  • chmod +x .github/workflows/scripts/create-github-release.sh .github/workflows/scripts/create-github-release.sh ${{ steps.version.outputs.tag }}
View raw YAML
name: Create Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract version from tag
        id: version
        run: |
          VERSION=${GITHUB_REF#refs/tags/}
          echo "tag=$VERSION" >> $GITHUB_OUTPUT
          echo "Building release for $VERSION"

      - name: Check if release already exists
        id: check_release
        run: |
          chmod +x .github/workflows/scripts/check-release-exists.sh
          .github/workflows/scripts/check-release-exists.sh ${{ steps.version.outputs.tag }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create release package variants
        if: steps.check_release.outputs.exists == 'false'
        run: |
          chmod +x .github/workflows/scripts/create-release-packages.sh
          .github/workflows/scripts/create-release-packages.sh ${{ steps.version.outputs.tag }}

      - name: Generate release notes
        if: steps.check_release.outputs.exists == 'false'
        id: release_notes
        run: |
          chmod +x .github/workflows/scripts/generate-release-notes.sh
          # Get the previous tag for changelog generation
          PREVIOUS_TAG=$(git describe --tags --abbrev=0 ${{ steps.version.outputs.tag }}^ 2>/dev/null || echo "")
          # Default to v0.0.0 if no previous tag is found (e.g., first release)
          if [ -z "$PREVIOUS_TAG" ]; then
            PREVIOUS_TAG="v0.0.0"
          fi
          .github/workflows/scripts/generate-release-notes.sh ${{ steps.version.outputs.tag }} "$PREVIOUS_TAG"

      - name: Create GitHub Release
        if: steps.check_release.outputs.exists == 'false'
        run: |
          chmod +x .github/workflows/scripts/create-github-release.sh
          .github/workflows/scripts/create-github-release.sh ${{ steps.version.outputs.tag }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

release-trigger .github/workflows/release-trigger.yml
Triggers
workflow_dispatch
Runs on
ubuntu-latest
Jobs
bump-version
Commands
  • git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
  • if [[ -n "$INPUT_VERSION" ]]; then # Manual version specified - strip optional v prefix VERSION="${INPUT_VERSION#v}" # Validate strict semver format to prevent injection if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)" exit 1 fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "tag=v$VERSION" >> $GITHUB_OUTPUT echo "Using manual version: $VERSION" else # Auto-increment patch version LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") echo "Latest tag: $LATEST_TAG" # Extract version number and increment VERSION=$(echo $LATEST_TAG | sed 's/v//') IFS='.' read -ra VERSION_PARTS <<< "$VERSION" MAJOR=${VERSION_PARTS[0]:-0} MINOR=${VERSION_PARTS[1]:-0} PATCH=${VERSION_PARTS[2]:-0} # Increment patch version PATCH=$((PATCH + 1)) NEW_VERSION="$MAJOR.$MINOR.$PATCH" echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT echo "Auto-incremented version: $NEW_VERSION" fi
  • if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!" exit 1 fi
  • BRANCH="chore/release-${{ steps.version.outputs.tag }}" git checkout -b "$BRANCH" echo "branch=$BRANCH" >> $GITHUB_ENV
  • sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}"
  • if [ -f "CHANGELOG.md" ]; then DATE=$(date +%Y-%m-%d) # Get the previous tag by sorting all version tags numerically # (git describe --tags only finds tags reachable from HEAD, # which misses tags on unmerged release branches) PREVIOUS_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1) echo "Generating changelog from commits..." if [[ -n "$PREVIOUS_TAG" ]]; then echo "Changes since $PREVIOUS_TAG" COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release") else echo "No previous tag found - this is the first release" COMMITS="- Initial release" fi # Create new changelog entry — insert after the marker comment NEW_ENTRY=$(printf '%s\n' \ "" \ "## [${{ steps.version.outputs.version }}] - $DATE" \ "" \ "### Changed" \ "" \ "$COMMITS") awk -v entry="$NEW_ENTRY" '/<!-- insert new changelog below this comment -->/ { print; print entry; next } {print}' CHANGELOG.md > CHANGELOG.md.tmp mv CHANGELOG.md.tmp CHANGELOG.md echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG" else echo "No CHANGELOG.md found" fi
  • if [ -f "CHANGELOG.md" ]; then git add pyproject.toml CHANGELOG.md else git add pyproject.toml fi if git diff --cached --quiet; then echo "No changes to commit" else git commit -m "chore: bump version to ${{ steps.version.outputs.version }}" echo "Changes committed" fi
  • git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" git push origin "${{ env.branch }}" git push origin "${{ steps.version.outputs.tag }}" echo "Branch ${{ env.branch }} and tag ${{ steps.version.outputs.tag }} pushed"
View raw YAML
name: Release Trigger

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (e.g., 0.1.11). Leave empty to auto-increment patch version.'
        required: false
        type: string

jobs:
  bump-version:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
          token: ${{ secrets.RELEASE_PAT }}

      - name: Configure Git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

      - name: Determine version
        id: version
        env:
          INPUT_VERSION: ${{ github.event.inputs.version }}
        run: |
          if [[ -n "$INPUT_VERSION" ]]; then
            # Manual version specified - strip optional v prefix
            VERSION="${INPUT_VERSION#v}"
            # Validate strict semver format to prevent injection
            if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
              echo "Error: Invalid version format '$VERSION'. Must be X.Y.Z (e.g. 1.2.3 or v1.2.3)"
              exit 1
            fi
            echo "version=$VERSION" >> $GITHUB_OUTPUT
            echo "tag=v$VERSION" >> $GITHUB_OUTPUT
            echo "Using manual version: $VERSION"
          else
            # Auto-increment patch version
            LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
            echo "Latest tag: $LATEST_TAG"

            # Extract version number and increment
            VERSION=$(echo $LATEST_TAG | sed 's/v//')
            IFS='.' read -ra VERSION_PARTS <<< "$VERSION"
            MAJOR=${VERSION_PARTS[0]:-0}
            MINOR=${VERSION_PARTS[1]:-0}
            PATCH=${VERSION_PARTS[2]:-0}

            # Increment patch version
            PATCH=$((PATCH + 1))
            NEW_VERSION="$MAJOR.$MINOR.$PATCH"

            echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
            echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
            echo "Auto-incremented version: $NEW_VERSION"
          fi

      - name: Check if tag already exists
        run: |
          if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
            echo "Error: Tag ${{ steps.version.outputs.tag }} already exists!"
            exit 1
          fi

      - name: Create release branch
        run: |
          BRANCH="chore/release-${{ steps.version.outputs.tag }}"
          git checkout -b "$BRANCH"
          echo "branch=$BRANCH" >> $GITHUB_ENV

      - name: Update pyproject.toml
        run: |
          sed -i "s/version = \".*\"/version = \"${{ steps.version.outputs.version }}\"/" pyproject.toml
          echo "Updated pyproject.toml to version ${{ steps.version.outputs.version }}"

      - name: Update CHANGELOG.md
        run: |
          if [ -f "CHANGELOG.md" ]; then
            DATE=$(date +%Y-%m-%d)

            # Get the previous tag by sorting all version tags numerically
            # (git describe --tags only finds tags reachable from HEAD,
            #  which misses tags on unmerged release branches)
            PREVIOUS_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n 1)

            echo "Generating changelog from commits..."
            if [[ -n "$PREVIOUS_TAG" ]]; then
              echo "Changes since $PREVIOUS_TAG"
              COMMITS=$(git log --oneline "$PREVIOUS_TAG"..HEAD --no-merges --pretty=format:"- %s" 2>/dev/null || echo "- Initial release")
            else
              echo "No previous tag found - this is the first release"
              COMMITS="- Initial release"
            fi

            # Create new changelog entry — insert after the marker comment
            NEW_ENTRY=$(printf '%s\n' \
              "" \
              "## [${{ steps.version.outputs.version }}] - $DATE" \
              "" \
              "### Changed" \
              "" \
              "$COMMITS")

            awk -v entry="$NEW_ENTRY" '/<!-- insert new changelog below this comment -->/ { print; print entry; next } {print}' CHANGELOG.md > CHANGELOG.md.tmp
            mv CHANGELOG.md.tmp CHANGELOG.md

            echo "✅ Updated CHANGELOG.md with commits since $PREVIOUS_TAG"
          else
            echo "No CHANGELOG.md found"
          fi

      - name: Commit version bump
        run: |
          if [ -f "CHANGELOG.md" ]; then
            git add pyproject.toml CHANGELOG.md
          else
            git add pyproject.toml
          fi

          if git diff --cached --quiet; then
            echo "No changes to commit"
          else
            git commit -m "chore: bump version to ${{ steps.version.outputs.version }}"
            echo "Changes committed"
          fi

      - name: Create and push tag
        run: |
          git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}"
          git push origin "${{ env.branch }}"
          git push origin "${{ steps.version.outputs.tag }}"
          echo "Branch ${{ env.branch }} and tag ${{ steps.version.outputs.tag }} pushed"

      - name: Open pull request
        env:
          GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
        run: |
          gh pr create \
            --base main \
            --head "${{ env.branch }}" \
            --title "chore: bump version to ${{ steps.version.outputs.version }}" \
            --body "Automated version bump to ${{ steps.version.outputs.version }}.

          This PR was created by the Release Trigger workflow. The git tag \`${{ steps.version.outputs.tag }}\` has already been pushed and the release artifacts are being built.

          Merge this PR to record the version bump and changelog update on \`main\`."

      - name: Summary
        run: |
          echo "✅ Version bumped to ${{ steps.version.outputs.version }}"
          echo "✅ Tag ${{ steps.version.outputs.tag }} created and pushed"
          echo "✅ PR opened to merge version bump into main"
          echo "🚀 Release workflow is building artifacts from the tag"
stale perms .github/workflows/stale.yml
Triggers
schedule, workflow_dispatch
Runs on
ubuntu-latest
Jobs
stale
Actions
actions/stale
View raw YAML
name: 'Close stale issues and PRs'

on:
  schedule:
    - cron: '0 0 * * *' # Run daily at midnight UTC
  workflow_dispatch: # Allow manual triggering

permissions:
  issues: write
  pull-requests: write

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v10
        with:
          # Days of inactivity before an issue or PR becomes stale
          days-before-stale: 150
          # Days of inactivity before a stale issue or PR is closed (after being marked stale)
          days-before-close: 30
          
          # Stale issue settings
          stale-issue-message: 'This issue has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
          close-issue-message: 'This issue has been automatically closed due to inactivity (180 days total). If you believe this issue is still relevant, please reopen it or create a new issue.'
          stale-issue-label: 'stale'
          
          # Stale PR settings
          stale-pr-message: 'This pull request has been automatically marked as stale because it has not had any activity for 150 days. It will be closed in 30 days if no further activity occurs.'
          close-pr-message: 'This pull request has been automatically closed due to inactivity (180 days total). If you believe this PR is still relevant, please reopen it or create a new PR.'
          stale-pr-label: 'stale'
          
          # Exempt issues and PRs with these labels from being marked as stale
          exempt-issue-labels: 'pinned,security'
          exempt-pr-labels: 'pinned,security'
          
          # Only issues or PRs with all of these labels are checked
          # Leave empty to check all issues and PRs
          any-of-labels: ''
          
          # Operations per run (helps avoid rate limits)
          operations-per-run: 250
test matrix perms .github/workflows/test.yml
Triggers
push, pull_request
Runs on
ubuntu-latest, ubuntu-latest
Jobs
ruff, pytest
Matrix
python-version→ 3.11, 3.12, 3.13
Actions
astral-sh/setup-uv, astral-sh/setup-uv
Commands
  • uvx ruff check src/
  • uv sync --extra test
  • uv run pytest
View raw YAML
name: Test & Lint Python

permissions:
  contents: read

on:
  push:
    branches: ["main"]
  pull_request:

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v7

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.13"

      - name: Run ruff check
        run: uvx ruff check src/

  pytest:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v7

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: uv sync --extra test

      - name: Run tests
        run: uv run pytest