Repository navigation
v2.67.0 — audit through GitHub Action and GitLab CI, audit --coverage, dynamic-import analysis
Highlights
fallow auditis now a first-class command in the GitHub Action and GitLab CI template. No more dropping to a manualnpx fallow auditstep;command: audit(Action) andFALLOW_COMMAND=audit(GitLab) accept the full set of audit inputs (gate,coverage,coverage-root,max-crap, and the three per-analysis baselines), gate on the severity-aware verdict instead of raw counts, and render verdict + per-category attribution tables in PR comments.fallow audit --coverageplus--coverage-rootforward Istanbul data to the embedded health analysis so audit's CRAP threshold check sees measured coverage instead of the static estimate. PRs touching well-tested code stop drawing noisy--max-crapviolations.- Statically analyzable dynamic imports (
import('./foo'),import('@scope/pkg')) now flow through the unresolved, unlisted, member-usage, and external-style analyzers exactly like static imports. Literal dynamic imports to missing files or unlisted packages no longer slip past the per-finding reports.
Features
command: audit in CI wrappers
Wrapping the severity-aware new-only PR gate previously meant ignoring the action's command: validation and writing a custom step. Both wrappers now accept audit as a first-class command and forward:
gate(new-only/all)coverageandcoverage-root(Istanbul JSON path + optional absolute strip prefix)max-crapdead-code-baseline,health-baseline,dupes-baseline
outputs.issues for audit returns the gate-aware introduced-finding count (sums attribution.*_introduced under gate: new-only, sums summary.* totals under gate: all), so downstream workflows comparing outputs.issues > N keep working without changes.
PR comments render the verdict banner, per-category attribution tables (dead-code, complexity, duplication), and a coverage-model footer that hints at --coverage-root when Istanbul match rate is low. Annotations and review comments fan out per category through the existing comment and annotation jq scripts.
GitLab CI mirrors the Action: FALLOW_COVERAGE, FALLOW_AUDIT_GATE, and the three FALLOW_AUDIT_*_BASELINE variables forward to the CLI; vendored CI templates under crates/cli/templates/ stay in lockstep.
Thanks @hc-12 for the report. (Closes #302)
fallow audit --coverage / --coverage-root
fallow audit --coverage coverage/coverage-final.json
fallow audit --coverage <path> --coverage-root /home/runner/work/myapp
FALLOW_COVERAGE=coverage/coverage-final.json fallow auditAudit's CRAP threshold previously saw the static estimate even when the project had real coverage data; --coverage closes that loop. The base-snapshot cache key folds in a content hash of the coverage file so cache entries invalidate when coverage changes.
As a side fix, relative --coverage paths now resolve against --root rather than the process cwd, matching every other path-shaped CLI input (this also covers standalone fallow health --coverage). Empty-string MCP Option<String> params (coverage: "", etc.) are dropped before reaching clap, so agents that emit empty-string-for-no-value no longer end up with --coverage "".
(Closes #300)
Bug fixes
- Statically analyzable dynamic imports flow through every per-finding analyzer. A literal
import('./missing')orimport('@unlisted-pkg')was silently elided from unresolved-import, unlisted-dependency, dependency-usage, unused-class-member, and external-CSS-package reports because those walkers only consumedstatic_imports+requires. A sharedResolvedModule::all_resolved_imports()iterator now unifies the three import shapes; pattern-shaped dynamic imports and graph-edge construction are unchanged. Suppression directives apply uniformly. Thanks @hussainarslan for the patch and @filipw01 for the report. (Closes #304) - Cross-package consumers of a namespace re-exported through an object literal credit the original member. Barrel
import * as foo from './bar'; export const API = { foo }plus downstreamAPI.foo.barno longer reportsbarasunused-export. Star-barrel namespace targets (./foo/index.tsdoingexport * from './bar') reuse the existing synthetic-export helper so credit propagates through Phase 4 chain resolution. Same-file aliases keep working unchanged; nested suffixes such asmotionNet.adEngineare handled via dotted-path matching. Bumps the extractCACHE_VERSIONso warm caches do not retain pre-fix data. Thanks @filipw01 for the report and the clean reproduction repo. (Closes #303) - Structured fallow errors no longer slip past the GitHub Action and GitLab CI wrappers as a green run. Both wrappers now check
jq -e '.error == true' fallow-results.jsonafter capture and before counting, surface the structured error message, and exit with the CLI's reportedexit_code. The same path intercepts theaudit+ genericbaseline/save-baselinemisuse pre-spawn with a message pointing at the three audit-specific baseline inputs (audit's three sub-analyses use incompatible baseline formats). - Threshold gates branch on the audit verdict, not the raw introduced-finding count. A project with
unused-exports: warnand the action defaultfail-on-issues: trueno longer fails CI on every PR that touches a warn-tier finding. The Check threshold step branches oncommand=audit: gates onVERDICT==failfor audit, falls through to the existingISSUES>0logic for every other command. Fail-tier still fails as expected. --coverage-rootrejects relative paths with an actionable error instead of silently no-op'ing.--coverage-rootis a strip-prefix argument applied to absolute paths inside Istanbul coverage data; relative values cannot match those paths and used to absolutise against--rootand proceed, producing zero coverage matches with no signal. The validator now exits 2 with a message that names the expected shape and runs at every entry point that accepts the flag (audit, health, combined-mode bare run, the NAPI binding, plus a defence-in-depth check at the Istanbul load site).
Full Changelog: v2.66.2...v2.67.0