docs: add Group I — feedback-quality recall scoring to roadmap #781
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 | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| # Auto-cancel superseded runs on the same ref so a chain of fast-follow | |
| # pushes doesn't pile up zombie in-progress runs that the GitHub backend | |
| # can no longer cancel via API. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| # Pin ruff: an unpinned `pip install ruff` silently upgrades on every | |
| # ruff release, and ruff's lint/format rules drift between minors — | |
| # which spontaneously turns this job red on untouched code. Bump | |
| # deliberately (and run `ruff check --fix && ruff format` once) when raising. | |
| - run: pip install ruff==0.15.15 | |
| - run: ruff check src/ tests/ | |
| - name: Format check | |
| run: python3 -m ruff format --check src/ tests/ | |
| typecheck: | |
| # v3.7.0 M5: typecheck is now blocking (was continue-on-error: true). | |
| # mypy src/ is currently clean — keeping it green is part of the | |
| # release contract. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - run: pip install -e ".[test]" | |
| - run: python -m mypy src/ --ignore-missing-imports | |
| test: | |
| # v3.7.0 M5: every advertised OS/Python combination must pass. | |
| # Python 3.14 was previously continue-on-error; the version is | |
| # now classed as supported in pyproject.toml's classifiers, so the | |
| # CI gate matches. | |
| runs-on: ${{ matrix.os }} | |
| # Python 3.14 is still pre-release as of 2026-05; treat its rows as | |
| # advisory rather than gating until upstream wheels stabilise. The | |
| # 3.10/3.12/3.13 supported rows still gate the workflow. | |
| continue-on-error: ${{ matrix.python-version == '3.14' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.10", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| allow-prereleases: true | |
| - run: pip install -e ".[test]" | |
| - name: Unit tests with coverage (ubuntu-3.12 only) | |
| # v4.0.9: coverage instrumentation across 5000+ tests is the | |
| # single biggest contributor to runner memory pressure. The | |
| # cov-fail-under=70 gate matters once per CI run, not 12× | |
| # across the matrix. Run --cov only on the canonical | |
| # ubuntu-3.12 row; other rows run the same test set without | |
| # the instrumentation overhead so they fit in the 7 GB | |
| # GitHub-hosted runner budget. | |
| # v4.0.6: -m "not stress" excludes stress-marked tests that | |
| # OOM-killed ubuntu runners (test_niah and the 5 files | |
| # marked file-level in v4.0.8). Stress tests still run via | |
| # `make test` for pre-release gating. | |
| if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' | |
| run: python3 -m pytest tests/ --ignore=tests/integration --cov=src --cov-report=xml --cov-report=term-missing --cov-fail-under=70 -x -q -m "not stress" --timeout=120 --timeout-method=thread | |
| env: | |
| PYTHONDONTWRITEBYTECODE: "1" | |
| - name: Unit tests (no coverage, other matrix rows) | |
| if: matrix.os != 'ubuntu-latest' || matrix.python-version != '3.12' | |
| run: python3 -m pytest tests/ --ignore=tests/integration -x -q -m "not stress" --timeout=120 --timeout-method=thread | |
| env: | |
| PYTHONDONTWRITEBYTECODE: "1" | |
| - name: Integration tests | |
| run: python3 -m pytest tests/integration/ -v -m "not stress" | |
| env: | |
| PYTHONDONTWRITEBYTECODE: "1" | |
| - name: Upload coverage report | |
| # Only ubuntu-3.12 generates coverage.xml (see v4.0.9 split above). | |
| if: always() && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: coverage-${{ matrix.os }}-${{ matrix.python-version }} | |
| path: coverage.xml | |
| retention-days: 30 | |
| version-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Check version consistency | |
| run: python src/mind_mem/check_version.py | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - run: pip install -e . | |
| - name: Init test workspace | |
| run: python -m mind_mem.init_workspace /tmp/test-workspace | |
| - name: Validate workspace | |
| run: python -m mind_mem.validate_py /tmp/test-workspace | |
| extras-install: | |
| name: ".[${{ matrix.extra }}] installs" | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| extra: [mcp, api, embeddings, all] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: pip install -e ".[${{ matrix.extra }}]" | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[${{ matrix.extra }}]" | |
| - name: import mind_mem | |
| run: python -c "import mind_mem; print(mind_mem.__name__)" | |
| pinned-requirements: | |
| # v3.7.0 M5 follow-up: requirements-optional.txt only pins three | |
| # top-level packages (onnxruntime / tokenizers / sentence- | |
| # transformers); transitive deps are intentionally resolved by pip. | |
| # ``--require-hashes`` is implicitly enabled for the whole resolve | |
| # whenever ANY line has ``--hash=``, so a plain ``pip install`` of | |
| # this file fails on transitive deps that aren't pinned (the file | |
| # is a "minimum supported set" not a full lockfile). Generating a | |
| # full hashed lockfile (200+ entries) would silently expand the | |
| # file's scope; the audit's M5 intent ("prove the pinned versions | |
| # still resolve on a fresh runner so a yanked wheel breaks the | |
| # release, not prod") is satisfied by: | |
| # 1. ``pip download --no-deps --require-hashes`` — proves all | |
| # three pins still exist on PyPI with the recorded hashes | |
| # 2. ``pip install`` of the same three top-level packages | |
| # *without* the requirements file — proves the version line | |
| # still resolves a working transitive closure | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Verify hashes for pinned wheels (gate against yanked wheels) | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip download --no-deps --require-hashes \ | |
| --dest /tmp/hash-verify \ | |
| -r requirements-optional.txt | |
| - name: Resolve transitive closure (gate against unsatisfiable deps) | |
| run: | | |
| python -m pip install \ | |
| onnxruntime==1.24.2 \ | |
| tokenizers==0.22.2 \ | |
| sentence-transformers==5.2.3 | |
| docker-build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 — Node 24 | |
| - name: Build runtime image | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 — Node 24 | |
| with: | |
| context: . | |
| file: deploy/docker/Dockerfile | |
| push: false | |
| load: true | |
| tags: mind-mem:ci | |
| compose-config: | |
| # v3.7.0 M6: prove docker-compose.yml renders cleanly with both the | |
| # default user/db AND an operator override. Pre-3.7.0 the postgres | |
| # healthcheck hardcoded ``pg_isready -U mindmem -d mindmem``, so an | |
| # operator who set POSTGRES_USER / POSTGRES_DB would see a healthy | |
| # postgres container reported as unhealthy. The new healthcheck | |
| # reads the env at probe time; this job validates the YAML still | |
| # interpolates and that the rendered command keeps the env-var | |
| # references intact under both defaults and overrides. | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| scenario: | |
| - name: defaults | |
| user: "" | |
| db: "" | |
| - name: overrides | |
| user: alice | |
| db: factsdb | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: docker compose config | |
| env: | |
| MIND_MEM_TOKEN: ci-token | |
| MIND_MEM_ADMIN_TOKEN: ci-admin | |
| POSTGRES_PASSWORD: ci-pg-pw | |
| POSTGRES_USER: ${{ matrix.scenario.user }} | |
| POSTGRES_DB: ${{ matrix.scenario.db }} | |
| run: | | |
| docker compose -f deploy/docker-compose.yml config > /tmp/rendered.yml | |
| # ``$$VAR`` in compose YAML renders as a literal ``$VAR`` once the | |
| # container shell expands it at probe time. The check confirms the | |
| # healthcheck still references ``POSTGRES_USER`` / ``POSTGRES_DB`` | |
| # rather than baked-in literals — the M6 regression we are gating. | |
| grep -F 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"' /tmp/rendered.yml | |
| install-smoke: | |
| name: install.sh smoke (${{ matrix.installer }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| installer: [pipx, pip] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install pipx | |
| if: matrix.installer == 'pipx' | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install pipx | |
| python -m pipx ensurepath | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install package via documented one-liner | |
| # The README tells users to run pipx install "mind-mem[mcp]" | |
| # (or pip --user). This step exercises that exact path on a | |
| # clean runner so a regression there is caught before release. | |
| run: | | |
| if [ "${{ matrix.installer }}" = "pipx" ]; then | |
| pipx install --force ".[mcp]" | |
| else | |
| python -m pip install --user ".[mcp]" | |
| fi | |
| - name: mind-mem-mcp --help (smoke) | |
| run: mind-mem-mcp --help | |
| - name: install.sh wires clients without re-installing | |
| # Run with --no-install so the script just resolves | |
| # mind-mem-mcp on PATH and verifies it. The --claude-code flag | |
| # writes to ~/.claude/mcp.json (created on demand) and avoids | |
| # the interactive client picker. | |
| run: ./install.sh --no-install --workspace /tmp/test-workspace --claude-code |