Skip to content

v2.77.0: review envelope v2, re-export-cycle, silent-fail hardening

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 21 May 17:47
· 573 commits to main since this release
v2.77.0
fed4b48

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 matching summary.fingerprint against existing comments instead of invoking fallow a second time. The legacy top-level body stays for v1 consumers (byte-identical to summary.body).
  • marker_regex plus marker_regex_flags at envelope root, both fields shipped explicitly because JavaScript RegExp rejects standalone (?m) inline groups. Construct with new RegExp(env.marker_regex, env.marker_regex_flags) on JS or RegexBuilder::new(pat).multi_line(true).build() on Rust.
  • Same-(path, line) merging in comments[] 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_path for 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 NamedTempFile and 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::MixedLineEndings instead 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 version at entry and skips both publish and pull-model cache update when the live version has advanced past the snapshot. Every publish now carries Some(version) in PublishDiagnosticsParams.version for client-side belt-and-braces. (Closes #450.)
  • Hover content escapes user-controlled identifiers via CommonMark code spans, and the remove unused export quick fix re-validates the live declaration shape before producing an edit, so a mid-stream did_change cannot land a wrong-line strip. (Closes #480.)
  • fallow fix (and any writer through fallow_config::atomic_write) now preserves the target file's Unix mode instead of silently downgrading to 0600.

Silent-fail hardening family

A sweep through every silent-discard-and-continue site in the config / discovery / plugin pipeline:

  • Misspelled rule names in .fallowrc.json warn with a Levenshtein "did you mean?" suggestion (phase 1 of staged deny_unknown_fields migration). (Closes #467.)
  • Invalid, absolute, or ..-bearing glob patterns in config exit 2 at config load instead of silently no-op'ing; covers entry, 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 their root prefix, exit 2 at config load with a rendered diagnostic listing every offending tuple. (Closes #468.)
  • fallow migrate warns on input rule / exclude / include keys it cannot translate, and prints a glob-drift caveat when entry or ignorePatterns are migrated. (Closes #457.)
  • Plugin system surfaces collisions on config_patterns, Levenshtein-typo enabler diagnostics, and invalid regexes in PathRule.exclude_regexes as tracing::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-suppression finding with kind_known: false and a Levenshtein hint. (Closes #449.)
  • Markers for a rule that is currently off no longer surface as stale-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.maxSizeMb or FALLOW_CACHE_MAX_SIZE. (Closes #466.)
  • Monorepo workspace discovery surfaces malformed package.json, unreachable glob matches, and missing tsconfig references via a new workspace_diagnostics: WorkspaceDiagnostic[] field, with severity per-site (root malformed exits 2; declared-workspace malformed warns and continues). A new fallow list --workspaces flag renders the discovered workspace table plus diagnostics. (Closes #473.)
  • fallow check --regression-baseline <path> exits 2 with an actionable regenerate hint on schema_version mismatch instead of silently loading default-zero fields. (Closes #451.)
  • GitHub Action and GitLab CI templates surface gh api failures 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 .sig files; the postinstall (npm) and install.sh (action) verify all three binaries via a public key embedded in the verifier, then cross-check against the matching GitHub Release asset.digest. (Closes #465.)
  • api_key and license JWTs are masked in Debug output; Bearer <token> substrings are sanitized in user-facing network-error strings reaching stderr. (Closes #476.)

Other fixes

  • unused-class-members no longer false-positives when a Playwright fixture created with base.extend<MyFixtures>(...) is returned from a helper function. Thanks @vethman for the report. (Closes #491.)
  • Barrel re-export member propagation through extends / implements is 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 against FallowConfig::json_schema() on every cargo test run. (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-cycle findings appear at default warn severity. Opt into hard failures via rules.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=-Dwarnings consumers of fallow-core need to allow deprecated on call sites (deprecation announced in v2.76.0).

Full Changelog: v2.76.0...v2.77.0