> ## Documentation Index
> Fetch the complete documentation index at: https://f4c7a9e2d8b1-docs.tenzo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CI Selective Testing

> How GitHub CI skips unrelated jobs, and why we do not gate merges on changed-only tests

<Warning>
  **Restricted Access**: This documentation is only accessible to @tenzo.ai and @salv.ai email addresses.
</Warning>

## Summary

GitHub CI (`.github/workflows/ci.yaml`) skips the **server** or **UI** job on pull requests when that tree did not change. Pushes to `main` always run both. The merge gate remains a **full** suite for whichever side ran — we do not select individual pytest cases from the diff in CI.

## What runs when

| Change set (PR)            | Server job                       | UI job                           |
| -------------------------- | -------------------------------- | -------------------------------- |
| `server/**` (or `ci.yaml`) | Full lint + sharded tests        | Skipped (aggregator still green) |
| `ui/**` (or `ci.yaml`)     | Skipped (aggregator still green) | Full lint + test + build         |
| Both                       | Full                             | Full                             |
| Neither (e.g. docs-only)   | Skipped                          | Skipped                          |
| Push to `main`             | Always full                      | Always full                      |

Detection mirrors `codegen.yaml`: `git diff` against the PR base SHA (or the previous commit on push), with a safe fallback to “run everything” when the base SHA is missing.

Required check names stay stable via lightweight aggregators:

* `server (3.10)` — passes when both `server-checks` (lint, typecheck, migrations) and `server-tests` shards succeed **or** server work was skipped
* `ui (22)` — passes when both `ui-lint-typecheck` and `ui-tests` succeed **or** UI work was skipped

Both aggregators **fail closed** if the `changes` job itself did not succeed (empty skip outputs must not greenlight a merge with zero tests).

## Why not “only tests that touch changed code” as the CI gate

Locally, `server/bin/test --testmon` (and `--fail-fast`) can rerun only tests that import changed modules. That is intentional as a **local accelerator**, not a merge gate:

* **testmon** needs a persisted `.testmondata` map; cold CI caches still run everything first
* It is incompatible with `pytest-xdist` / `pytest-cov` as used in the full suite
* Shared helpers, fixtures, migrations, and config can break distant tests that a naive path or import map misses

Safer layers:

1. **Path-level job skip** (what CI does now) — don’t spin server runners for a UI-only PR
2. **Optional local / future pre-check** — `--fail-fast` or cached testmon before the full suite
3. **Full suite for the side that changed** — and always on `main`

## Local fast loop

From `server/`:

```bash theme={null}
./bin/test --testmon      # only tests affected by your edits (needs .testmondata)
./bin/test --fail-fast    # testmon subset first; on pass, run the full suite
```

See the header comments in `server/bin/test` for details and constraints.
