Skip to main content

Incremental scan cache

vulkro-sf antipatterns caches per-file findings under ~/.vulkro/sf-antipattern-cache.json so a second scan against the same source returns instantly for unchanged files. Hot path on managed-package-scale codebases (NPSP at ~50k files): roughly 10x wall-time speedup on a re-run with no source changes.

Cache key

Each entry is keyed by:

  • Source SHA-256 of the file. Any edit invalidates the entry.
  • Detector version. A new vulkro-sf release that materially changes a detector bumps DETECTOR_VERSION and the old entries are silently bypassed (they live under the previous version key in the cache file, and the JSON map grows by one key).
  • Compliance flags (hipaa, pci - sorted, comma-joined). Different flags = different finding set, so a scan with HIPAA and a scan without it cache separately under the same source.
  • Own-namespace set (from sfdx-project.json + the --exclude-namespace flag + the extra_excluded_namespaces config field - sorted, comma-joined). Different namespace filters = different finding set, same reason.

The key is constructed as sha256|compliance_csv|namespaces_csv, hashed once per file at scan time.

Where it lives

LocationDefault
Cache file~/.vulkro/sf-antipattern-cache.json
Override--cache-dir <path> or VULKRO_SF_CACHE_DIR=<path>
Disable--no-cache or VULKRO_SF_NO_CACHE=1

The cache directory is created on first scan. The JSON file is plain text; you can inspect it with jq '.by_version | keys' to see which detector versions still have entries, or delete it outright to force a full re-scan.

What is cached

The pre-filter finding set for each file. That means:

  • Findings disabled via .vulkro-sf.yml's disable: are NOT removed from the cache. Removing the disable: entry re-emits them on the next scan without re-running detectors.
  • Suppress comments (// vulkro: ignore AP-XXX) ARE applied at read time, so deleting the directive re-emits the finding from cache without a re-scan, AND the cache stays accurate when the directive is added.
  • Cross-file detectors (AP-007 no test class, AP-009 multiple triggers per SObject) are NOT cached: they run after the per-file pass and depend on the project as a whole.

What invalidates the cache

TriggerWhat re-runs
Source byte changeThat file's detectors only
vulkro-sf upgrade that bumps DETECTOR_VERSIONEvery file
--compliance set / changed / removedEvery file that has different effective flags
--exclude-namespace set / changedEvery file (the namespace filter is global)
--no-cache flagEvery file (cache bypassed entirely; cache still WRITTEN at scan end so the next no-flag scan sees fresh entries)

CI integration

Persist ~/.vulkro/ (or the directory you pass to --cache-dir) between CI runs and you get the cache speedup on every push that edits a subset of files.

GitHub Actions

- uses: actions/cache@v4
with:
path: .vulkro-cache
key: vulkro-sf-${{ github.run_id }}
restore-keys: vulkro-sf-
- name: vulkro-sf antipatterns
run: vulkro-sf antipatterns . --cache-dir .vulkro-cache --format junit > junit.xml

GitLab CI

vulkro-sf:
cache:
paths:
- .vulkro-cache/
script:
- vulkro-sf antipatterns . --cache-dir .vulkro-cache --format junit > junit.xml
artifacts:
reports:
junit: junit.xml

Where to go next

  • Project config - the .vulkro-sf.yml file whose disable and extra_excluded_namespaces fields participate in cache invalidation.
  • vulkro-sf antipatterns - the full CLI reference including --no-cache and --cache-dir.
  • CI / CD integration - the broader CI surface for vulkro-sf, including the JUnit and SARIF report flows.