v2.77.0: review envelope v2, re-export-cycle, silent-fail hardening
A minor release focused on a v2 envelope for review-github / review-gitlab, a new re-export-cycle finding, an ignoreDecorators opt-out for unused-class-members, and a wide sweep of silent-fail hardening across config load, audit lifecycle, fix safety, LSP delivery, and Action / CI templates.
Highlights
Review envelope v2 for --format review-github / --format review-gitlab
Five additive contract changes designed for consumer-side posting in regulated CI environments (no remote includes, no jq, no token delegation):
summary: { body, fingerprint }at envelope root, so one fallow invocation emits both the sticky summary and the inline comments with reconciliation primitives. Consumers upsert the summary by matchingsummary.fingerprintagainst existing comments instead of invoking fallow a second time. The legacy top-levelbodystays for v1 consumers (byte-identical tosummary.body).marker_regexplusmarker_regex_flagsat envelope root, both fields shipped explicitly because JavaScript RegExp rejects standalone(?m)inline groups. Construct withnew RegExp(env.marker_regex, env.marker_regex_flags)on JS orRegexBuilder::new(pat).multi_line(true).build()on Rust.- Same-
(path, line)merging incomments[]with a hashed-composite fingerprint (merged:<16-char hash>). Identity shifts when constituents change, so skip-if-fingerprint-exists logic naturally re-posts on content change. - UTF-8-safe body truncation at 65,536 bytes under the conservative floor of both vendors. Three co-present signals: typed
truncated: bool, inline<!-- fallow-truncated -->HTML marker, and a> Body truncated by fallow.blockquote breadcrumb. - GitLab
position.old_pathfor renamed files, so inline comments on renames anchor correctly via the discussion-position API.
The bundled fallow ci reconcile-review subcommand and both vendor scripts (action/scripts/review.sh, ci/scripts/review.sh) accept any fallow-review-envelope/v<N> schema and recognize both v1 and v2 marker shapes during the migration window. Thanks @OmerGronich for the detailed consumer-side-posting writeup. (Closes #528.)
New finding: re-export-cycle
The Tarjan SCC pass added in #442 already detected cycles in the re-export edge subgraph; this release surfaces them as a first-class AnalysisResults.re_export_cycles[] entry with a kind discriminator (multi-node for two-or-more-file cycles, self-loop for single-file rename leftovers) and a typed actions[] envelope. Default severity is Warn so projects with latent cycles do not get sudden CI failures on upgrade; opt into hard failures via rules.re-export-cycle: error. New CLI flag --re-export-cycles filters to the new finding type only. The rule appears in --explain, SARIF, CodeClimate, compact / markdown / human formats, the LSP, the GitHub Action, the GitLab CI summary, and the MCP analyze tool description. (Closes #515.)
ignoreDecorators config opt-out for unused-class-members
Class members carrying decorators are skipped by default (NestJS @Get(), Angular @Input(), TypeORM @Column()). The new option lets you list non-reflective utility decorators (Playwright @step, internal @measure, @log) so methods decorated with ONLY those names are checked for usage like undecorated methods. Conservative composition: a method carrying any decorator NOT in the list stays skipped. Thanks @vethman for the report. (Closes #471.)
fallow fix safety: content-hash precondition + batch atomicity + UTF-8 BOM preservation
Two structural gaps closed:
- Every parsed source file's xxh3 content hash is now threaded into each fixer; per-file entry recomputes the hash and skips with a clear diagnostic on mismatch. Each fixer stages its rewrite into a
NamedTempFileand the orchestrator commits the plan only after every stage has succeeded, so an OOM / disk-full / transient I/O error mid-run leaves the project untouched. Cross-fixer same-file edits compose: the second fixer reads the first fixer's pending staged content instead of original disk bytes. (Closes #454.) - The UTF-8 BOM survives the read-extract-write round trip across single and multi-fixer runs, and mixed CRLF/LF files are now skipped with
SkipReason::MixedLineEndingsinstead of silently mangled. The skip is surfaced on stderr, in the JSON envelope (skipped_mixed_line_endings), and via exit code 2. (Closes #475.)
fallow audit lifecycle hardening
Three structural fragilities fixed in one pass: panic-cleanup leaks (new WorktreeCleanupGuard rolls back partial git worktree add registrations), Windows orphan accumulation (process_is_alive now uses OpenProcess + WaitForSingleObject), and parallel-CI cache races (kernel-level advisory lock on <cache>.lock via std::fs::File::try_lock). (Closes #472.)
Persistent base-snapshot worktree caches under <tmp>/fallow-audit/ no longer accumulate forever. Each invocation runs sweep_old_reusable_caches at the top of the pipeline, walks git-registered worktrees, and removes reusable cache entries whose sidecar .last-used mtime exceeds the configured age (default 30 days, overridable via audit.cacheMaxAgeDays or FALLOW_AUDIT_CACHE_MAX_AGE_DAYS). (Closes #498.)
LSP / fix correctness
- The language server no longer publishes diagnostics that reference lines you no longer wrote: every analysis run snapshots the per-URI document
versionat entry and skips both publish and pull-model cache update when the live version has advanced past the snapshot. Every publish now carriesSome(version)inPublishDiagnosticsParams.versionfor client-side belt-and-braces. (Closes #450.) - Hover content escapes user-controlled identifiers via CommonMark code spans, and the
remove unused exportquick fix re-validates the live declaration shape before producing an edit, so a mid-streamdid_changecannot land a wrong-line strip. (Closes #480.) fallow fix(and any writer throughfallow_config::atomic_write) now preserves the target file's Unix mode instead of silently downgrading to0600.
Silent-fail hardening family
A sweep through every silent-discard-and-continue site in the config / discovery / plugin pipeline:
- Misspelled rule names in
.fallowrc.jsonwarn with a Levenshtein "did you mean?" suggestion (phase 1 of stageddeny_unknown_fieldsmigration). (Closes #467.) - Invalid, absolute, or
..-bearing glob patterns in config exit 2 at config load instead of silently no-op'ing; coversentry,ignorePatterns,dynamicallyLoaded,duplicates.ignore,health.ignore,overrides[].files,ignoreExports[].file,boundaries.zones[].patterns, and every glob-bearing field on inline + external plugin definitions. (Closes #463.) boundaries.rules[]references to undeclared zones, and zone patterns redundantly repeating theirrootprefix, exit 2 at config load with a rendered diagnostic listing every offending tuple. (Closes #468.)fallow migratewarns on input rule /exclude/includekeys it cannot translate, and prints a glob-drift caveat whenentryorignorePatternsare migrated. (Closes #457.)- Plugin system surfaces collisions on
config_patterns, Levenshtein-typo enabler diagnostics, and invalid regexes inPathRule.exclude_regexesastracing::warn!at config load instead of failing silently at matcher-use time. (Closes #479.) - Suppression markers carrying an unknown token keep the recognized tokens working and surface the unknown one as a
stale-suppressionfinding withkind_known: falseand a Levenshtein hint. (Closes #449.) - Markers for a rule that is currently
offno longer surface asstale-suppression; the suppression is documenting dormant intent. (Closes #482.) - Cache size cap is now enforced on every save (not just load), and the cache invalidates automatically when extraction-affecting config changes. Overridable via
cache.maxSizeMborFALLOW_CACHE_MAX_SIZE. (Closes #466.) - Monorepo workspace discovery surfaces malformed
package.json, unreachable glob matches, and missing tsconfig references via a newworkspace_diagnostics: WorkspaceDiagnostic[]field, with severity per-site (root malformed exits 2; declared-workspace malformed warns and continues). A newfallow list --workspacesflag renders the discovered workspace table plus diagnostics. (Closes #473.) fallow check --regression-baseline <path>exits 2 with an actionable regenerate hint onschema_versionmismatch instead of silently loading default-zero fields. (Closes #451.)- GitHub Action and GitLab CI templates surface
gh apifailures and pagination errors via three new composite outputs (changed-files-unavailable,post-skipped-reason,dedup-lookup-failed) and parallel sidecar artifacts on GitLab, instead of running unscoped analysis or posting duplicate PR/MR comments. (Closes #470.)
Security
- npm install and the GitHub Action installer now verify Ed25519 signatures and GitHub Release SHA-256 digests on every platform binary before running it. Each
@fallow-cli/<platform>package ships.sigfiles; the postinstall (npm) andinstall.sh(action) verify all three binaries via a public key embedded in the verifier, then cross-check against the matching GitHub Releaseasset.digest. (Closes #465.) api_keyand license JWTs are masked inDebugoutput;Bearer <token>substrings are sanitized in user-facing network-error strings reaching stderr. (Closes #476.)
Other fixes
unused-class-membersno longer false-positives when a Playwright fixture created withbase.extend<MyFixtures>(...)is returned from a helper function. Thanks @vethman for the report. (Closes #491.)- Barrel re-export member propagation through
extends/implementsis no longer order-sensitive. Thanks @M-Hassan-Raza for the patch. (Closes #427.) - Re-export chain resolution propagates references through barrel chains of arbitrary depth, and tags multi-hop
export type *synthetic stubs as type-only. (Closes #442.) - Ctrl+C and SIGTERM now reap fallow's spawned subprocesses (sidecar,
git log,git worktree,npm install -g) instead of leaking them. (Closes #477.)
Internal
schema.json(the JSON Schema for.fallowrc.json) is now drift-gated againstFallowConfig::json_schema()on everycargo testrun. (Closes #440.)
Upgrade notes
- The extraction cache (
CACHE_VERSION) bumped multiple times during this release ladder; the first run after upgrade is uncached and slightly slower than usual. Subsequent runs are warm. - Projects with latent re-export cycles will see new
re-export-cyclefindings appear at defaultwarnseverity. Opt into hard failures viarules.re-export-cycle: error. - Projects relying on silent-discard behavior in config (invalid globs, unknown rule names, undeclared boundary zones) will now exit 2 at config load. The dropped patterns were never doing what their author intended; fix the typo to upgrade.
RUSTFLAGS=-Dwarningsconsumers offallow-coreneed to allowdeprecatedon call sites (deprecation announced in v2.76.0).
Full Changelog: v2.76.0...v2.77.0