main gained six PRs (#1-#6) while the audit fixes and the state-map seam were in progress. Four of main's commits fixed the same bugs this branch did, independently; where they overlap, the more complete version wins: - Fingerprints: main's (a0844bb) - catchSite (`member#index` among all catches), union/owner as identity, a legacy fingerprint so version-1 baselines still match, baseline "version": 2. This branch's `catch` ordinal is dropped; its fingerprint_test still guards the result. - verify --rule: main's verifyAnalysis (cf9e0b7), which also reports findings in skipped files as NOT SCANNED. - Unreadable SDK: both kept - this branch's up-front probe gives the early, specific error; main's catch around the analyzer (d6b6bcb) is the backstop. - Riverpod: main's "a ref.listen is not a render" (f6411dc) and this branch's AsyncError patterns / stored-value tracking compose; the fixture keeps both sets of cases. Ported onto the model-only rules (the seam), since main edited code this branch had moved into front-end facts: - excerpt.dart's literal-free, bounded quoting (a prompt-injection guard) now also covers front-end facts: SwallowedFailure.handler and NonSubscribingRead.excerpt, and the C++ front end elides string literals in handler evidence the same way. - ScanCoverage: runChecks stays source-free; the model carries coverage (`scanCoverage` in the state JSON, written by both front ends), so `check --model` states coverage like a source check. - main's softened wording (rendered-not-emitted "probably unreachable", the remove-policy dead-handler text, notifier-field-unread) and the backticked flag message. Docs: main's README grew the Firestore query inventory and the output contract; the README stays short, the inventory moves to docs/queries.md, and the contract, exit codes, verify and baseline details join docs/usage.md. Checked against origin/main on resolved mirrors of the dogfood apps: nuwayze (102), dex (0) and word_alive (0) identical; tick differs only by the intended riverpod stored-value fix. On the fixture every difference is one of this branch's fixes. 214 tests pass.
1.9 KiB
Development
Building
dart pub get
tool/build_release.sh # `dart build cli`, falling back to `dart compile exe`
Use the native binary rather than dart run: JIT startup is paid on every
invocation. On macOS arm64:
| Scenario | dart run |
AOT binary |
|---|---|---|
| 2-file project | 6.4s | 0.2s |
| 814-file Flutter app, end to end | ~23-29s | 10.4s |
| per-file p50 on that app | ~50-75ms | 16ms |
The binary is about 18MB and self-contained. On a large project the first file costs about 3s of analyzer warm-up (SDK and package summaries). Cutting that would need a persistent analysis cache.
Tests
dart test
The rule suite runs against test/fixtures/stuck_loading, a dependency-free
project with hand-written stand-ins for BLoC, Riverpod and provider, so it
resolves with no pub get and no network. Do not add a package: import to
it. The query inventory runs against test/fixtures/firestore_queries, which
carries a hand-written cloud_firestore skeleton and a gateway adapter.
What the suite holds pipe to:
- every rule has a positive and a negative control (
rule_contract_test.dart); - no fingerprint depends on position, and none collide (
fingerprint_test.dart); - the state model survives JSON unchanged, with identical findings
(
state_map_roundtrip_test.dart); - partial, unreadable and unresolved scans are reported, never passed silently.
test/cpp_front_test.dart runs the C++ front end on test/fixtures/cpp_state
and is skipped without Homebrew LLVM.
Hooks and CI
git config core.hooksPath .githooks enables the hooks. pre-commit runs
scripts/lint.sh --hook (format and analyze); pre-push runs
scripts/pre-push-ci-gate.sh (format, analyze and the full test suite).
Commit messages follow Conventional Commits (commitlint.config.js).
Measuring precision and recall
See rules.md for tool/adjudicate.py and
pipe recall.