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_VERSIONand 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-namespaceflag + theextra_excluded_namespacesconfig 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
| Location | Default |
|---|---|
| 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'sdisable:are NOT removed from the cache. Removing thedisable: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
| Trigger | What re-runs |
|---|---|
| Source byte change | That file's detectors only |
vulkro-sf upgrade that bumps DETECTOR_VERSION | Every file |
--compliance set / changed / removed | Every file that has different effective flags |
--exclude-namespace set / changed | Every file (the namespace filter is global) |
--no-cache flag | Every 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.ymlfile whosedisableandextra_excluded_namespacesfields participate in cache invalidation. - vulkro-sf antipatterns - the full CLI
reference including
--no-cacheand--cache-dir. - CI / CD integration - the broader CI surface for vulkro-sf, including the JUnit and SARIF report flows.