Contributing
Quick start
Section titled “Quick start”Clone the repository and install dependencies:
git clone https://github.com/nandan-varma/purgeit.gitcd purgeitnpm installVerify everything
Section titled “Verify everything”Before opening a pull request, run the full verification:
npm run typechecknpm run lintnpm testnpm run buildRunning tests
Section titled “Running tests”Run the full suite:
npm testRun a single test file:
npx vitest run src/rules/merge.test.tsRun a specific test by name:
npx vitest run -t "test name substring"Coverage
Section titled “Coverage”npm run coverageCoverage is enforced at 100% statements/branches/functions/lines for everything except src/ui/** and src/types.ts. src/ui/ is exempt because Ink rendering/keybinding branches don’t map cleanly onto a hard coverage bar — it’s exercised via ink-testing-library behavior tests (App.test.tsx) instead. npm run coverage exits non-zero below the threshold, so run it before opening a PR that touches non-UI code.
Adding a new ecosystem’s rules
Section titled “Adding a new ecosystem’s rules”Built-in rules live in src/rules/catalog/, one file per ecosystem, and this is the recipe for adding one:
- Create
src/rules/catalog/<ecosystem>.tsexporting areadonly RuleDefinition[](seecatalog/types.ts). Each entry iskind: 'always-safe'(deletable anywhere, no proof needed — only for names distinctive enough to be unambiguous) orkind: 'gated'(needs awhen: GateCondition | GateCondition[]proving a sibling manifest makes it real generated output — use this for any name generic enough to plausibly mean something else). - Add it to the spread in
catalog/index.ts. - If it’s a genuinely new ecosystem, add a
RuleCategorymember and aCATEGORY_LABELS/CATEGORY_ORDERentry incatalog/types.ts. If the name is already used unqualified by another ecosystem (likebuildorvendor), add your category to the existing rule inshared.tsinstead of creating a duplicate. - Add tests:
gate-conditions.test.tsfor a gated rule’s condition logic (allow/reject cases), and acontains(...)assertion indefault-rules.test.tsfor an always-safe addition.catalog/index.test.ts’s structural invariants (every category used, no duplicate names, etc.) run automatically — no changes needed there. - Nothing else to touch —
default-rules.ts,gate-conditions.ts, the scan engine, and the/rules/docs page all derive fromRULE_CATALOG, so a correct catalog entry is immediately live everywhere.
Favor always-safe only for names unlikely to mean anything else (.stack-work, zig-out); reach for gated the moment a name could plausibly be a legitimate non-artifact directory (build, bin, pkg) — see the safety model for why this distinction is the whole point of the tool.
Working on the code
Section titled “Working on the code”- Keep
src/ui/as the only directory that imports React or Ink. The CI enforces this with a grep check. - TypeScript is strict:
exactOptionalPropertyTypesmeans an optional field fed by aT | undefinedvalue must be declaredfield?: T | undefined, not justfield?: T.noUncheckedIndexedAccessmeans array/index access producesT | undefined— useas Tonly when a value is genuinely guaranteed (e.g. a regex capture group after a successful match), not a defensive??that 100% branch coverage will then flag as unreachable. - Lint rules
noUnusedImports,noUnusedVariables, anduseExhaustiveDependenciesare errors (not just the Biome recommended preset). Suppress intentionally with// biome-ignore lint/<rule>: <reason>immediately above the offending line — foruseExhaustiveDependenciesthat’s theuseEffect(() => {line itself, not the closing}, [deps]). - Vitest is configured with
fileParallelism: falsebecause tests spawn realduchild processes; parallel file execution can exhaustposix_spawnon macOS. - Real filesystem fixtures, not mocks —
test/fixtures/build-tmp-tree.ts’sbuildTree()/cleanupTree()create/remove real temp directories. Don’t mockfsfor scanner/walk/rule tests. - Do not add a
rootDirto tsconfig;test/fixtures/build-tmp-tree.tslives outsidesrc/. - Never add
#!/usr/bin/env nodeto source files; tsup’sbanner.jsadds the shebang during bundling — adding it in source too produces a duplicate that breaks execution.
GitHub Actions runs the full verification (typecheck, lint, test, build) across a 6-job matrix (ubuntu/macos/windows × Node 20/22), plus two purgeit-specific checks after build:
- A grep check that fails if
react/inkis imported anywhere outsidesrc/ui/. - A headless smoke test that builds a real fixture tree and runs the built
dist/cli.js --json --dry-runagainst it — the only place the actual built CLI output is exercised end-to-end, catching anything that passes unit tests but breaks in the bundled output.
Publishing
Section titled “Publishing”Releases are tag-triggered, not published by hand:
- Make sure the full verification passes, including coverage.
- Bump the version (
npm version patch|minor|major --no-git-tag-version), commit, and push tomain. git tag vX.Y.Z && git push origin vX.Y.Z— pushing the tag triggers.github/workflows/release.yml, which runsnpm publish --provenance --access public.
Documentation
Section titled “Documentation”Docs live in the docs/ directory and are built with Astro Starlight. To edit or preview them:
cd docsnpm installnpm run devWhen adding new CLI flags, config options, or exported APIs, please update the relevant docs page.