# Check 4 — CI test coverage

Question: can a change land without the tests that cover it running in CI?
Inputs: `workspace.json` (`ciFiles`), `scan.json`. Output: `findings/ci-coverage.json`.

## Rules

| id | severity | detect |
|---|---|---|
| `ci-coverage/undeclared-workspace-dependency` | critical (medium when Nx is an orchestrator, since Nx infers edges from imports) | `scan.imports` entry with `declared: false`, `isTest: false` and `fromSource: "package.json"`. Affected/filtered runs and `^` caching won't treat the importer as depending on the target, so changing the target skips the importer's tests. One finding per (from, target) pair. |
| `ci-coverage/missing-test-script` | high | package has `scan.packages[p].testFiles.length > 0` but no package-level test entry point is covered. Accept `test`, `e2e`, or `test:*` package scripts; an Nx `test` target; inferred Nx test plugins (`@nx/vite/plugin`, `@nx/jest/plugin`, `@nx/vitest`); root `vitest.workspace.*` / root `vitest.config.*` `test.projects` or root Jest `projects`, counted only when one of its project roots or globs matches the package's dir; or a root test script whose globs explicitly include the package path. Do **not** treat `pnpm -r test`, `npm --workspaces test`, or similar recursive root scripts as coverage for packages without their own test script (generic-pnpm deliberately still flags `@acme/app`). |
| `ci-coverage/tests-not-run-in-ci` | high | at least one CI file exists, but no job that runs before a change can land (triggered by `pull_request`/`merge_group` or the CI system's equivalent, or by `push` only when its branch filter shows it runs before landing, e.g. unfiltered or matching feature/topic branches — `push` limited to the protected default branch, scheduled-only, or manual-only workflows don't count) has a step matching `/\b(npm\|pnpm\|yarn\|bun\|npx\|nx\|turbo)\b[^\n]*\btest\b\|\b(vitest\|jest\|mocha)\b\|\b(make\|task\|just)\s+test\b/` (each `\|` is a Markdown table escape for a regex alternation bar). If `ciFiles` is empty, set this check status to `partial` with reason `no CI configuration found` and emit no `tests-not-run-in-ci` finding. |
| `ci-coverage/shallow-checkout-affected` | high | an affected/filtered CI job lacks full base history. GitHub Actions: job runs `nx affected`, `turbo … --affected`, or `--filter=…[<ref>]`, uses `actions/checkout`, and does **not** set `fetch-depth: 0`. GitLab: affected logic is used while `GIT_DEPTH` is unset/nonzero (default 20) or the MR target branch is not fetched. Azure: `checkout: self` lacks `fetchDepth: 0`. Bitbucket: `clone: depth` is set while affected logic is used. |
| `ci-coverage/nx-affected-base-missing` | high | `nx affected` in CI with no `--base`, no `nrwl/nx-set-shas` step, no `NX_BASE` env, and no `nx.json#defaultBase`. |
| `ci-coverage/nx-test-inputs-ignore-deps` | high | Nx `test` target (targetDefaults or project) is cacheable and explicitly sets `inputs` with no `^` dependency entry after resolving named inputs. Omitted `inputs` are ok because Nx defaults to `default` + `^default`. |
| `ci-coverage/turbo-test-missing-dependson` | high | Turbo `test` (or `*:test`) task is cacheable and its `dependsOn` graph has no transitive `^` entry. Follow local task indirections such as `"transit": { "dependsOn": ["^transit"] }`, `"test": { "dependsOn": ["transit"] }`; that pattern satisfies the rule. |

## Steps

1. Read every file in `ciFiles`. Also follow GitLab `include:` entries that point to local files and local reusable GitHub workflows referenced with `uses: ./.github/workflows/x.yml`. For GitHub Actions, reason per job: a checkout step's `with.fetch-depth` applies to the steps after it in the same job. If there are no CI files, write `status: "partial"`, `reason: "no CI configuration found"`, and do not emit `ci-coverage/tests-not-run-in-ci`.
2. Resolve Nx targets as in caching (targetDefaults merged with project config). Resolve Turbo tasks from `turbo.json`/`pipeline` and package overrides.
3. Apply the rules; quote the CI step / config object as `evidence`.
4. Write `findings/ci-coverage.json`.

## Fix guidance

- undeclared-workspace-dependency: add `"<target>": "workspace:*"` (pnpm/yarn/bun) or `"*"` (npm) to the importer's dependencies, and import via the package name.
- shallow-checkout-affected: `with: { fetch-depth: 0 }` on `actions/checkout`.
- nx-affected-base-missing: add `- uses: nrwl/nx-set-shas@v4` before `nx affected`.
- nx-test-inputs-ignore-deps: `"test": { "inputs": ["default", "^production"] }`.
- turbo-test-missing-dependson: `"test": { "dependsOn": ["^build"] }` (or `["^test"]` when tests run on sources).
