docs: fork-changes entries for the CLI wave (836d1a1, 2848e96, ff9ee2… #807
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: Tests | |
| # Path-filter: skip the full workflow when only docs change (markdown | |
| # under any path + the docs/ tree). Mixed PRs (docs + code) still run. | |
| # workflow_dispatch lets us manually re-run the full matrix when needed. | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths-ignore: | |
| - '**/*.md' | |
| - 'docs/**' | |
| pull_request: | |
| branches: [main, develop] | |
| paths-ignore: | |
| - '**/*.md' | |
| - 'docs/**' | |
| workflow_dispatch: | |
| jobs: | |
| test-linux: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: 'pip' | |
| - run: pip install -e ".[dev]" | |
| - run: python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace --cov-report=xml --cov-report=term-missing --cov-fail-under=0 --durations=10 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-linux-${{ matrix.python-version }} | |
| path: .coverage | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| # Windows + macOS run only on manual dispatch (Actions budget — Windows | |
| # is 2x Linux billing, macOS is 10x). Trigger via Actions tab → "Tests" | |
| # → Run workflow when a release or platform-sensitive change needs the | |
| # cross-platform pass. | |
| test-windows: | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.13" | |
| cache: 'pip' | |
| - run: pip install -e ".[dev]" | |
| # ChromaDB's rust HNSW core intermittently fails compaction or reader | |
| # initialization on Windows ("Failed to apply logs to the hnsw segment | |
| # writer" / "Error creating hnsw segment reader: Nothing found on disk") | |
| # regardless of our code — long-standing, non-reproducible-on-Linux/macOS | |
| # flakes. Retry ONLY those specific transient errors (via --only-rerun) so real, | |
| # deterministic failures still fail on the first run. Linux/macOS jobs | |
| # deliberately run with no reruns so genuine regressions surface there. | |
| - run: >- | |
| python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace | |
| --cov-report=term-missing --cov-fail-under=0 --durations=10 --reruns 2 | |
| --reruns-delay 5 --only-rerun | |
| "Failed to apply logs to the hnsw segment writer|Error creating hnsw segment reader: Nothing found on disk" | |
| test-macos: | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.13" | |
| cache: 'pip' | |
| - run: pip install -e ".[dev]" | |
| - run: python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace --cov-report=term-missing --durations=10 | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| cache: 'pip' | |
| # Keep this pin identical to the `ruff==` pin in pyproject.toml | |
| # ([project.optional-dependencies].dev and [dependency-groups].dev) | |
| # so CI and `pip install -e ".[dev]"` format/lint identically. | |
| # This drifted once (CI on 0.15.14 against pyproject's 0.15.20) and the | |
| # drift was invisible: the lint job never installs from pyproject, so a | |
| # dependabot bump to the pin there passes CI without the new version | |
| # ever running. test_ruff_pins_match asserts the two stay equal. | |
| - run: pip install "ruff==0.16.1" | |
| - run: ruff check . | |
| - run: ruff format --check . | |
| # Postgres-backed tests run against `apache/age:release_PG16_1.6.0`, the | |
| # same image production (`mempalace-db` on familiar) is built on. The AGE | |
| # image is just `postgres:16` with the AGE extension compiled in, so we | |
| # apt-install `postgresql-16-pgvector` into the running service container | |
| # at job start. That matches the substrate documented in | |
| # `tests/test_backends_postgres.py`. The integration suite | |
| # `tests/test_knowledge_graph_age.py` was previously silently skipping | |
| # on the `pgvector/pgvector:pg16` image (no AGE) because every test in | |
| # that file gates on `TEST_POSTGRES_DSN`; promoting the image to AGE | |
| # lets the KG-AGE integration tests actually exercise. | |
| # | |
| # `tests/test_palace_graph.py::TestPostgresFastPath` (#164) carries a | |
| # `@pgmark`-gated test that exercises the postgres-direct aggregate | |
| # path against a live AGE+pgvector instance. The rest of the file is | |
| # mock-based and already runs in `test-linux`; invoking the whole | |
| # file here is the simplest way to unlock the gated case, since | |
| # pytest skips it silently in test-linux otherwise. The unit suite | |
| # `tests/test_age_kg_units.py` and other mock-based KG/queue/worker | |
| # suites (`test_backfill_kg_triples.py`, `test_kg_extraction_queue.py`, | |
| # `test_kg_triple_worker.py`, `test_palace_graph_tunnels.py`, | |
| # `test_searcher_stopwords.py`) are DSN-free and already covered by | |
| # `test-linux`, so we don't repeat them here. | |
| test-postgres: | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: apache/age:release_PG16_1.6.0 | |
| env: | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: mempalace_test | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: 'pip' | |
| - run: pip install -e ".[dev,postgres]" | |
| - name: Install pgvector into AGE service container | |
| # The apache/age image ships AGE but not pgvector. The production | |
| # substrate apt-installs `postgresql-16-pgvector` on top; mirror | |
| # that here by exec-ing into the running service container. | |
| env: | |
| PG_CONTAINER: ${{ job.services.postgres.id }} | |
| run: | | |
| docker exec "$PG_CONTAINER" bash -c ' | |
| set -e | |
| apt-get update -qq | |
| apt-get install -y --no-install-recommends postgresql-16-pgvector | |
| ' | |
| - name: Create vector + age extensions | |
| env: | |
| PGURL: postgresql://postgres:postgres@localhost:5432/mempalace_test | |
| run: | | |
| python - <<'PY' | |
| import os, psycopg | |
| conn = psycopg.connect(os.environ["PGURL"]) | |
| conn.autocommit = True | |
| cur = conn.cursor() | |
| cur.execute("CREATE EXTENSION IF NOT EXISTS vector") | |
| cur.execute("CREATE EXTENSION IF NOT EXISTS age") | |
| print("vector + age extensions ready") | |
| PY | |
| - name: Run postgres-backed tests (backends + AGE knowledge graph) | |
| env: | |
| TEST_POSTGRES_DSN: postgresql://postgres:postgres@localhost:5432/mempalace_test | |
| run: | | |
| python -m pytest \ | |
| tests/test_backends_postgres.py \ | |
| tests/test_knowledge_graph_age.py \ | |
| tests/test_palace_graph.py \ | |
| -v --cov=mempalace --cov-report=xml --cov-report=term-missing --cov-fail-under=0 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-postgres | |
| path: .coverage | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| # Combined coverage gate. Each test job runs pytest with --cov but no | |
| # --cov-fail-under; this job downloads every coverage artifact, merges | |
| # them with `coverage combine`, and enforces the 80% threshold once. | |
| # That keeps the bar meaningful even when new code lives in a backend | |
| # only one job exercises (e.g. postgres-only paths covered by | |
| # test-postgres but not by test-linux). See #99. | |
| check-coverage: | |
| runs-on: ubuntu-latest | |
| needs: [test-linux, test-postgres] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: 'pip' | |
| - run: pip install -e ".[dev]" | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: coverage-* | |
| path: coverage-artifacts | |
| - name: Combine coverage and enforce threshold | |
| run: | | |
| # Each artifact directory contains a single .coverage file. | |
| # Rename them to .coverage.<artifact-name> so `coverage combine` | |
| # picks them all up rather than overwriting on collision. | |
| mkdir -p combined | |
| for d in coverage-artifacts/*/; do | |
| name=$(basename "$d") | |
| cp "$d/.coverage" "combined/.coverage.$name" | |
| done | |
| cd combined | |
| coverage combine | |
| # 78% reflects the current combined floor. Low-coverage modules: | |
| # searcher.py (63%), palace_graph_age.py (30%), mcp_server.py (64%). | |
| # Raise back to 80% as test coverage improves for those paths. | |
| coverage report --fail-under=78 |