CI — fix: decouple needs_onboarding from notebook_ready needs_onboarding now reflects only knowledge_ready — the notebook step is independent and its absence should not trigger the full onboarding flow. Fixes CI failure where ~/.devcoach/learning-state.md never exists. #102
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| run-name: >- | |
| ${{ github.event_name == 'workflow_dispatch' | |
| && format('CI — New release ({0})', inputs.version != '' && inputs.version || inputs.bump) | |
| || (startsWith(github.ref, 'refs/tags/v') | |
| && format('CI — New release ({0})', github.ref_name) | |
| || (github.event_name == 'pull_request' | |
| && format('CI - PR {0}', github.event.pull_request.title) | |
| || format('CI — {0}', github.event.head_commit.message))) }} | |
| # Covers three scenarios: | |
| # | |
| # 1. Push to main / pull request → lint + test + build + sonar + pages | |
| # 2. Tag push (v*) → lint + test + build + publish + GitHub Release | |
| # 3. workflow_dispatch (Release) → bump version → tag → lint + test + build + publish + GitHub Release | |
| # | |
| # The bump job only runs on workflow_dispatch. Lint/test/build always run. | |
| # Publish and GitHub Release run whenever a tag is present. | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: "Version bump type" | |
| required: false | |
| default: patch | |
| type: choice | |
| options: [patch, minor, major] | |
| version: | |
| description: "Exact version (overrides bump, e.g. 1.0.0)" | |
| required: false | |
| default: "" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ── Bump ──────────────────────────────────────────────────────────────── | |
| # Only runs on workflow_dispatch. Bumps pyproject.toml, commits, tags, pushes. | |
| bump: | |
| name: Bump version and tag | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.ver.outputs.version }} | |
| tag: ${{ steps.ver.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Compute next version | |
| id: ver | |
| run: | | |
| if [ -n "${{ inputs.version }}" ]; then | |
| NEXT="${{ inputs.version }}" | |
| else | |
| CURRENT=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/') | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" | |
| case "${{ inputs.bump }}" in | |
| major) NEXT="$((MAJOR+1)).0.0" ;; | |
| minor) NEXT="${MAJOR}.$((MINOR+1)).0" ;; | |
| patch) NEXT="${MAJOR}.${MINOR}.$((PATCH+1))" ;; | |
| esac | |
| fi | |
| echo "version=$NEXT" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$NEXT" >> "$GITHUB_OUTPUT" | |
| - name: Bump version in pyproject.toml and sonar-project.properties | |
| run: | | |
| sed -i 's/^version = ".*"/version = "${{ steps.ver.outputs.version }}"/' pyproject.toml | |
| sed -i 's/^sonar.projectVersion=.*/sonar.projectVersion=${{ steps.ver.outputs.version }}/' sonar-project.properties | |
| - name: Commit and tag | |
| run: | | |
| git add pyproject.toml sonar-project.properties | |
| git commit -m "chore: bump version to ${{ steps.ver.outputs.version }}" | |
| git tag "${{ steps.ver.outputs.tag }}" | |
| git push --atomic origin main "${{ steps.ver.outputs.tag }}" | |
| # ── Lint ──────────────────────────────────────────────────────────────── | |
| lint: | |
| name: Lint (ruff) | |
| needs: [bump] | |
| if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.bump.outputs.tag || github.ref }} | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --group dev | |
| - run: uv run ruff check src/ tests/ | |
| - run: uv run ruff format --check src/ tests/ | |
| # ── Tests ─────────────────────────────────────────────────────────────── | |
| test: | |
| name: Test (Python ${{ matrix.python-version }}) | |
| needs: [bump] | |
| if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.bump.outputs.tag || github.ref }} | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - run: uv sync --group dev | |
| - name: Run tests | |
| run: uv run pytest tests/ -v --tb=short | |
| # ── Build ─────────────────────────────────────────────────────────────── | |
| build: | |
| name: Build distribution | |
| needs: [bump, lint, test] | |
| if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') && needs.lint.result == 'success' && needs.test.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.bump.outputs.tag || github.ref }} | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: uv build | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| # ── Coverage comment on PR ────────────────────────────────────────────── | |
| # Posts (and updates) a coverage summary comment on every PR push. | |
| # Reads the .coverage file generated by pytest-cov — no extra deps needed. | |
| coverage: | |
| name: Coverage comment | |
| needs: [bump] | |
| if: > | |
| github.event_name == 'pull_request' | |
| && (needs.bump.result == 'success' || needs.bump.result == 'skipped') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --group dev | |
| - name: Run tests with coverage | |
| run: uv run pytest tests/ --cov=src/devcoach --cov-report=xml | |
| - uses: py-cov-action/python-coverage-comment-action@v3 | |
| with: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| MINIMUM_GREEN: 90 | |
| MINIMUM_ORANGE: 80 | |
| # ── SonarCloud scan + quality gate ───────────────────────────────────── | |
| # CI-based analysis: feeds coverage.xml to SonarCloud. | |
| # Skipped on pull_request events — SONAR_TOKEN is not available to PR | |
| # workflows (GitHub restricts secret access). Runs on main / tags / dispatch | |
| # where the token is available. | |
| # Requires Automatic Analysis to be DISABLED on sonarcloud.io | |
| # (Administration → Analysis Method). | |
| sonar: | |
| name: SonarCloud scan | |
| needs: [bump, test] | |
| if: > | |
| always() | |
| && github.event_name != 'pull_request' | |
| && (needs.bump.result == 'success' || needs.bump.result == 'skipped') | |
| && needs.test.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.bump.outputs.tag || github.ref }} | |
| fetch-depth: 0 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --group dev | |
| - name: Run tests with coverage | |
| run: uv run pytest tests/ -v --tb=short --cov=src/devcoach --cov-report=xml | |
| - uses: sonarsource/sonarqube-scan-action@v8 | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| # ── GitHub Pages ──────────────────────────────────────────────────────── | |
| # Builds MkDocs site and deploys to GitHub Pages on every push to main. | |
| pages: | |
| name: Deploy docs to GitHub Pages | |
| needs: [bump, build] | |
| if: always() && needs.build.result == 'success' && github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pages: write | |
| id-token: write | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deploy.outputs.page_url }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --group docs | |
| - run: uv run mkdocs build | |
| - uses: actions/upload-pages-artifact@v5 | |
| with: | |
| path: site/ | |
| - name: Deploy to GitHub Pages | |
| id: deploy | |
| uses: actions/deploy-pages@v5 | |
| # ── Publish ───────────────────────────────────────────────────────────── | |
| # Runs when a tag is present: either created by the bump job (workflow_dispatch) | |
| # or pushed directly (git push origin v1.2.3). | |
| # Uses PyPI Trusted Publishing (OIDC) — no API tokens needed. | |
| # | |
| # One-time setup on PyPI: project=devcoach, owner=UltimaPhoenix, | |
| # repo=dev-coach, workflow=ci.yml, environment=pypi | |
| publish: | |
| name: Publish to PyPI | |
| needs: [bump, build] | |
| if: always() && needs.build.result == 'success' && (needs.bump.result == 'success' || startsWith(github.ref, 'refs/tags/v')) | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/p/devcoach | |
| permissions: | |
| id-token: write | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - uses: pypa/gh-action-pypi-publish@release/v1 | |
| # ── GitHub Release ────────────────────────────────────────────────────── | |
| github-release: | |
| name: Create GitHub Release | |
| needs: [bump, publish] | |
| if: always() && needs.publish.result == 'success' && (needs.bump.result == 'success' || startsWith(github.ref, 'refs/tags/v')) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.bump.outputs.tag || github.ref }} | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.bump.outputs.tag || github.ref_name }} | |
| files: dist/* | |
| generate_release_notes: true | |
| fail_on_unmatched_files: true |