{"id":"991e0aa5-c9e2-4e41-ac71-1efabfe0ec99","slug":"shortlist","kind":"skill","name":"shortlist","summary":"Run only the tests your diff can reach. Import-graph test selection for JS/TS and Python, with a GitHub Action and an agent skill; falls back to the full suite when unsure.","owner":"codegobrrrr9","repoUrl":"https://github.com/codegobrrrr9/shortlist","license":"MIT","licenseTier":"permissive","trust":"community","stars":0,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-09-28T14:59:49.000Z","addedAt":"2026-10-21T10:29:03.183Z","logoUrl":"https://avatars.githubusercontent.com/u/201159374?u=14b449aa3e3dd49a5da45c94d6d187081db8a839&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":["ci","github-actions","jest","monorepo","pytest","test-selection","testing","vitest"],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T11:40:15.923Z","sample":false,"url":"https://conduits.dev/tools/shortlist","language":"JavaScript","transports":["stdio"],"install":{"npm":"shortlist-tests"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<div align=\"center\">\n\n# ✂️ shortlist\n\n**Your CI runs the whole suite. Your diff can reach seven test files. shortlist runs the seven.**\n\nImport-graph test selection for JS/TS and Python. One zero-dependency file, a GitHub Action, and an agent skill. When it cannot be sure, it runs everything and says why.\n\n[![test](https://github.com/codegobrrrr9/shortlist/actions/workflows/test.yml/badge.svg)](https://github.com/codegobrrrr9/shortlist/actions/workflows/test.yml)\n[![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)\n![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)\n\n</div>\n\n## In CI\n\n```yaml\n- uses: actions/checkout@v4\n  with:\n    fetch-depth: 0          # shortlist needs the merge base\n- uses: actions/setup-node@v4\n- run: npm ci\n- uses: codegobrrrr9/shortlist@v1\n```\n\nOn a pull request that runs only the test files the diff can reach, with the right runner per\nworkspace package, and writes a summary to the job page. A real run on\n[hono](https://github.com/honojs/hono) after editing one utility file:\n\n```\nshortlist  base HEAD (d7fb697)  ·  1 file changed  ·  756ms\nRUN 7 of 137 test files  (95% skipped)\n  src/helper/dev/index.test.ts          ← imports src/utils/filepath.ts\n  src/hono.test.ts                      ← imports src/utils/filepath.ts\n  src/middleware/logger/index.test.ts   ← imports src/utils/filepath.ts\n  src/preset/quick.test.ts              ← imports src/utils/filepath.ts\n  src/preset/tiny.test.ts               ← imports src/utils/filepath.ts\n  src/utils/color.test.ts               ← imports src/utils/filepath.ts\n  src/utils/filepath.test.ts            ← imports src/utils/filepath.ts\n\nnpx vitest run src/helper/dev/index.test.ts src/hono.test.ts src/middleware/logger/index.test.ts ...\n```\n\nKeep the full suite where it belongs: on `main` and on a schedule. The pattern people are moving to\nis *affected tests on every PR, everything hourly*.\n\n```yaml\non:\n  pull_request:            # shortlist\n  schedule: [{ cron: '0 * * * *' }]   # full suite, hourly\n```\n\n## For your coding agent\n\nPaste this into Claude Code, Codex, Cursor or Gemini CLI:\n\n```\nInstall the shortlist skill from https://github.com/codegobrrrr9/shortlist, refer to the repo's AGENTS.md.\n```\n\nFrom then on the agent runs the tests its change can reach instead of the full suite after every\nedit, and says \"ran 7 of 142 test files selected by shortlist\" instead of \"all tests pass\".\n\nNo install at all:\n\n```bash\nnpx --yes --allow-git=all github:codegobrrrr9/shortlist\n```\n\n## Why\n\nCI is the new bottleneck. Agents open more pull requests than any team did, and every one runs the\nwhole suite. Teams are moving CI on-prem, buying faster runners, and asking their agents to pick\nwhich tests to run. Most of the suite cannot reach a three-file diff. The work is knowing which part\ncan, and not guessing.\n\n## How it decides\n\n1. **What changed**: commits on the branch since the merge base, plus uncommitted edits and new files.\n   Renames and deletions included.\n2. **The graph**: every import, re-export, `require`, dynamic `import()`, `jest.mock`/`vi.mock`,\n   tsconfig `paths` alias, workspace package name, and Python import (absolute, relative,\n   parenthesised, package `__init__`, `conftest.py` scope).\n3. **Walk it backwards** from each changed file to every test file that can reach it.\n\n| Changed | shortlist runs |\n|---|---|\n| A source file | every test that imports it, directly or through any chain |\n| A test file | that test |\n| A fixture no one imports | the tests living next to it |\n| `package.json` / `tsconfig` / runner config in a package | that package's tests |\n| A version bump, a lint config, docs, a workflow that doesn't run tests | nothing |\n| A lockfile, root config, the test workflow, `.nvmrc`, a `.go`/`.rs`/… file, an unknown base | **the full suite, with the reason** |\n\nCode that loads modules by a computed name (`require(name)`, `import.meta.glob`) is treated as\ndepending on its whole directory. Every rule is in\n[`references/how-it-decides.md`](skills/shortlist/references/how-it-decides.md).\n\n```bash\nshortlist                 # summary: which tests, and why each one\nshortlist --cmd           # the command that runs them\nshortlist --why src/api.test.ts   # the import chain that selected it\nshortlist --list | --json | --github\n```\n\n## Does it work\n\n**How much it skips.** Replaying the last 40 commits of real repos, no configuration:\n\n| Repo | Kind | Test files | Skipped over 40 commits | Median files run | Full-suite fallbacks | Needed no tests | Graph time |\n|---|---|---|---|---|---|---|---|\n| [TanStack/query](https://github.com/TanStack/query) | TS monorepo · Vitest | 282 | **90%** | 1 | 3 | 3 | 1.8s |\n| [fastify/fastify](https://github.com/fastify/fastify) | JS library · node:test | 194 | **81%** | 62 | 0 | 15 | 0.5s |\n| [honojs/hono](https://github.com/honojs/hono) | TS framework · Vitest | 138 | **61%** | 28 | 9 | 6 | 0.7s |\n| [date-fns/date-fns](https://github.com/date-fns/date-fns) | TS library, shared helpers · Vitest | 264 | **45%** | 231 | 6 | 12 | 1.6s |\n| [pallets/click](https://github.com/pallets/click) | Python library · pytest | 34 | **21%** | 34 | 7 | 6 | 0.4s |\n\nThe spread is the honest part. A monorepo, where a change in one package cannot reach the others, skips almost everything. A library whose tests all go through one shared helper, like date-fns, or through one package `__init__`, like click, skips far less, because every change really does reach most tests. Fallbacks are dependency bumps, lockfiles, tool-version files and the CI workflow that runs the tests.\n\n**Whether it skips anything it shouldn't.** The number that matters. For sampled source files in a\nreal repo, the benchmark injects a runtime failure, runs the full suite, and checks that every test\nfile that actually failed was on the shortlist:\n\n| Target | Break-it runs | Test files that really failed | Missed by shortlist | Avg selected |\n|---|---|---|---|---|\n| hono · random source files | 25 | 341 | **0** | 31.9 of 138 |\n| hono · JSX runtime, targeted | 3 | 43 | **0** | 27.3 of 138 |\n| TanStack query-core · random source files | 20 | 314 | **0** | 40.1 of 41 |\n\n**48 break-it runs, 698 test files that really failed, 0 missed.** Every file is broken on import, the real suite runs, and the benchmark compares what failed with what shortlist picked.\n\nThis benchmark earned its keep before it passed. Its first honest run on hono missed 31 failing test files, which exposed two bugs, both now fixed and covered by unit tests:\n\n- an import of a directory with a trailing slash (`from '../../'`) resolved to nothing, and\n- the automatic JSX runtime is injected by the compiler, so no source file imports it. shortlist now reads `jsxImportSource` from tsconfig, `importSource` from Vite/Vitest config, and `@jsxImportSource` pragmas.\n\nOne note on query-core: its tests import through the package index, so every change reaches 40 of 41 test files. Safe, and no faster within that package. The savings in that repo come from the other packages a change cannot reach.\n\n28 unit tests build real git repos for every rule above: transitive imports, index resolution,\n`.js`→`.ts` specifiers, tsconfig aliases, workspaces, deletions, renames, Python packages and\nconftest scope, the JSX runtime, metadata-only edits, every fail-safe.\n\n## Runners\n\n| Runner | Command shortlist writes |\n|---|---|\n| Vitest | `npx vitest run <files>` |\n| Jest | `npx jest --runTestsByPath <files>` |\n| node:test | `node --test <files>` |\n| Mocha · AVA · tap · Bun | `npx mocha` · `npx ava` · `npx tap` · `bun test` `<files>` |\n| pytest | `python -m pytest <files>` |\n\nIn a monorepo each package gets its own runner and working directory. Override with\n`\"command\": { \"js\": \"pnpm vitest run {files}\" }` in `.shortlist.json`.\n\n## `.shortlist.json`\n\n```json\n{\n  \"tests\": [\"tests/**/*.e2e.ts\"],\n  \"ignore\": [\"examples/**\"],\n  \"full\": [\"scripts/codegen/**\"],\n  \"always\": [\"tests/smoke.test.ts\"],\n  \"command\": { \"js\": \"pnpm vitest run {files}\", \"py\": \"uv run pytest {files}\" }\n}\n```\n\n## Honest limits\n\n- It follows imports. A test that reads a source file as text, spawns a script by path, or depends\n  on a database schema or env var is invisible to it. Put that test in `always`.\n- JS/TS and Python only. Any other language in the diff means the full suite, not a guess.\n- Libraries whose tests all import one root entry (`import x from '../index'`) gain the least:\n  every change reaches every test. Apps and monorepos gain the most.\n- It is a pull-request speedup, not a replacement for running everything on `main`.\n\n## Install by agent\n\n| Agent | How |\n|---|---|\n| **Claude Code** | `/plugin marketplace add codegobrrrr9/shortlist` then `/plugin install shortlist@shortlist`. Adds `/shortlist`. |\n| **Codex** | Copy `skills/shortlist/` to `~/.codex/skills/shortlist/` and append the Rules from [`AGENTS.md`](AGENTS.md). |\n| **Cursor** | Copy [`.cursor/rules/shortlist.mdc`](.cursor/rules/shortlist.mdc) and `shortlist.js` into the project. |\n| **Gemini CLI** | `gemini extensions install https://github.com/codegobrrrr9/shortlist` |\n| **GitHub Copilot** | Copy [`.github/copilot-instructions.md`](.github/copilot-instructions.md). |\n| **Anything else** | The Rules from [`AGENTS.md`](AGENTS.md) plus `shortlist.js` in the project. |\n\n## Credits\n\nThe idea is old: Bazel, Nx `affected`, Jest `--findRelatedTests`, pytest-impacted and\nvitest-affected all do it inside their own worlds. shortlist is the zero-config version that works\non a plain repo, across runners, in one file. Same family as\n[seatbelt](https://github.com/codegobrrrr9/seatbelt), [cheapskate](https://github.com/codegobrrrr9/cheapskate)\nand [alibi](https://github.com/codegobrrrr9/alibi).\n\n## License\n\nMIT. Star ⭐ if it cut your CI bill.\n"}