feat: fallow CI gate#1440
Conversation
|
There was a problem hiding this comment.
Pull request overview
This PR adds a new CI quality gate using the fallow tool to detect newly introduced dead code, unused dependencies, duplication, and complexity regressions, while avoiding failures on pre-existing (inherited) findings.
Changes:
- Add
fallowas a root dev dependency (and update the lockfile accordingly). - Introduce a new GitHub Actions workflow to run
fallow auditon pull requests using base-branch attribution. - Document how to use and suppress
fallowfindings inCONTRIBUTING.md.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
pnpm-lock.yaml |
Adds fallow and its platform-specific optional CLI packages to the lockfile. |
package.json |
Adds a fallow script and fallow devDependency to the repo root. |
CONTRIBUTING.md |
Documents the new gate, suppression directives, and local/manual cleanup commands. |
.github/workflows/fallow.yml |
New PR workflow that installs deps and runs fallow audit --base origin/<base> to gate on introduced findings. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ndling of false positives in CONTRIBUTING.md
🌱 Fallow Health: 🟢 B · 77.1 / 100What is costing points, repo-wide:
Repo-wide score for trend visibility — not a merge gate. Watch the grade climb as PRs clean up touched files. Updated each push ( |
Fallow audit reportFound 1 finding. Details
Generated by fallow. |
- pin fallow-rs/fallow to commit SHA (holds write token) - extract inline node -e to scripts/fallow-health-comment.js, drop dead GITHUB_OUTPUT write - scope sticky-comment lookup to bot author; skip on find failure (no dup comment) - drop redundant actions/cache step (setup-node cache:pnpm covers it) - correct portSuffix number-branch comment (unreachable for https/http2, not a no-op)
|
|
||
| it('adds the beforeShutdown lifecycle', () => { | ||
| const handler: Hook<'beforeShutdown'> = async (gasket) => { | ||
| // await shutdownTheThing(); |
There was a problem hiding this comment.
Did this example code comment get caught because it looks like code?
Instead of leaving this with an empty body, could we drop a simple non-code sentence comment?
Something like:
// perform shutdown logic|
|
||
| it('adds the onShutdown lifecycle', () => { | ||
| const handler: Hook<'onShutdown'> = async (gasket) => { | ||
| // await cleanupStuff(); |
There was a problem hiding this comment.
Same a previous comment
|
|
||
| on: | ||
| workflow_dispatch: | ||
| pull_request: |
There was a problem hiding this comment.
Let's limit fallow to the next branch to limit churn on main right now
| pull_request: | |
| pull_request: | |
| branches: | |
| - next |
| `Watch the grade climb as PRs clean up touched files. Updated each push (\`${sha}\`).</sub>` | ||
| ].join("\n"); | ||
| } | ||
| fs.writeFileSync("fallow-comment.md", md + "\n"); |
There was a problem hiding this comment.
To be safe, gitignore this temp fallow-comment.md file, or perhaps write it to the .fallow/ directory which you've already gitignored.
Summary
Pull requests now run a Fallow gate that catches dead code, unused dependencies, duplication, and complexity creep before they land. The gate is changeset-scoped: it fails only on issues a PR introduces. The ~340 pre-existing findings (mostly plugin-architecture false positives from dynamic loading, generator templates, and 316 plugin entry points) register as inherited and never block a PR until someone edits the file that carries them. No baseline file to maintain — the codebase drains gradually as edit traffic flows through it.
Every PR also gets a sticky health-score comment, so the repo-wide grade is visible and trends upward as touched files get cleaned. The
gasket-plugin-httpschanges here are a worked example of that drain-on-edit model: a real package taken to zero findings while passing through the gate.How the gate works
fallow auditruns new-only attribution against the PR base: it diffs the changeset, marks each finding introduced or inherited, and gates only on introduced error-severity findings. Verified againstnext— a PR reportsdead_code_introduced: 0, dead_code_inherited: 151, verdictpass.Three non-obvious things the workflow gets right:
fetch-depth: 0— the default shallow clone lacks the base ref, so--basecan't resolve.origin/-prefixed base —github.base_refis the bare namenext, which doesn't resolve in a CI checkout; onlyorigin/nextexists. Passed viaenv:so a branch name can never be read as shell.fallow auditexits 0 (clean / warns only), 1 (introduced error-severity → fail), or 2 (runtime error → fail loudly). No JSON parsing.Health-score comment
A second, non-gating step posts one sticky comment per PR, updated in place on every push. It renders the letter grade with a status emoji and a penalty breakdown table — non-zero penalties only, largest first — so reviewers see what's costing points repo-wide and watch the grade climb as cleanup lands.
if: always()+|| true— reports even when the gate fails; a health hiccup never flips the job outcome.<!-- fallow-health-score -->marker: find-and-PATCH the existing comment, else POST a new one.GITHUB_TOKEN); usespull_request, neverpull_request_target.False positives
Suppressed at the source, closest to the cause:
// fallow-ignore-next-line <issue-type>generator/)ignorePatternsin.fallowrc.json@gasket/plugin-metadata)ignoreDependenciesin.fallowrc.json.fallowrc.jsonover scattered inline commentsCONTRIBUTING.md documents the decision tree plus a manual
fallow fixcleanup loop for draining existing debt on demand.gasket-plugin-https cleanup
The first package run through the gate, as a reference for the drain-on-edit pattern:
@gasket/plugin-metadatafalse positive ignored at source.Function, bare statements) cleared.internal.d.ts;terminusopts andstartServertyped concretely against the publicActionHandler.vitest.config.js(lib/**/*.jsonly —index.d.tshas no runtime to exercise).Notes
fallowadded as a root dev-dependency (^2.91.0, caret per repo convention).create-gasket-app,gasket-plugin-docs,gasket-plugin-swagger,gasket-react-intl, and others.pnpm install's postinstall runs a full monorepo build on each gate run (same baseline asci.yml). Acceptable for now; revisitable with--ignore-scriptsif fallow proves it doesn't need build artifacts.Test plan
Validate by opening throwaway PRs against the workflow: