name: CI

on:
  push:
    branches: [main]
  pull_request:

# Least privilege: none of the jobs write to the repo.
permissions:
  contents: read

# Cancel superseded runs on the same ref to save Actions minutes.
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  python-syntax:
    name: Python syntax (compileall)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6.2.0
        with:
          python-version: "3.11"
      # Byte-compile sources — catches syntax errors without installing deps.
      - run: python -m compileall -q app.py core routes src services scripts tests

  node-syntax:
    name: JS syntax (node --check)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          persist-credentials: false
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e  # v6.4.0
        with:
          node-version: "20"
      # Syntax-check our own JS (skip vendored libs in static/lib).
      - name: node --check
        run: |
          shopt -s globstar nullglob
          for f in static/app.js static/js/**/*.js; do
            node --check "$f"
          done

  python-tests:
    name: Python tests (pytest)
    runs-on: ubuntu-latest
    # Informational for now: the suite has known flaky / environment-dependent
    # failures (test isolation + embedding-model assertions). Tracked under the
    # ROADMAP "fresh install smoke tests" item; make this required once green.
    continue-on-error: true
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10  # v6.0.3
        with:
          fetch-depth: 0
          persist-credentials: false

      # Detect whether this PR only touches documentation files.
      # If so, skip the expensive pytest run while still reporting a passing check.
      - name: Check for docs-only changes
        id: docs-check
        run: |
          if [ "${{ github.event_name }}" = "pull_request" ]; then
            BASE="${{ github.event.pull_request.base.sha }}"
            HEAD="${{ github.event.pull_request.head.sha }}"
          else
            BASE="${{ github.event.before }}"
            HEAD="${{ github.sha }}"
          fi
          # List all changed files; if every file matches docs/markdown patterns, skip pytest.
          changed=$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || git diff --name-only HEAD~1 HEAD)
          non_docs=$(echo "$changed" | grep -Ev '^(docs/|.*\.md$|\.github/[^/]+\.md$)' || true)
          if [ -z "$non_docs" ]; then
            echo "docs_only=true" >> "$GITHUB_OUTPUT"
            echo "Docs-only change detected — skipping pytest."
          else
            echo "docs_only=false" >> "$GITHUB_OUTPUT"
          fi

      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405  # v6.2.0
        if: steps.docs-check.outputs.docs_only != 'true'
        with:
          python-version: "3.11"
          cache: pip
      - run: pip install -r requirements.txt
        if: steps.docs-check.outputs.docs_only != 'true'
      - run: mkdir -p data  # sqlite DB lives at ./data/app.db
        if: steps.docs-check.outputs.docs_only != 'true'
      - run: python -m pytest -q
        if: steps.docs-check.outputs.docs_only != 'true'
