Skip to content

Incremental scanning: content-hash parse/adapter cache and --changed-from diff-scoped scans #115

Description

@bjcorder

Problem

Every scan re-discovers, re-reads, re-parses, and re-analyzes the entire tree, even when one file changed. On CI (the primary deployment surface per the GitHub Action) consecutive runs on a PR typically touch a handful of files; the 2-minute max_runtime_ms default budget is spent re-doing identical work, and large monorepos brush against max_files/max_total_bytes limits unnecessarily.

Current behavior

  • Pipeline: authmap-discoveryparse_files_in_parallel[_selective] (crates/authmap-parsers/src/lib.rs) → adapters → analysis. No cache exists at any stage; there is no scan-scoping flag beyond the target path (crates/authmap-cli/src/main.rs).
  • authmap diff/baseline compare completed scan outputs but still require full re-scans to produce them.

Proposed implementation

Cache at the parse/adapter-output boundary; always re-run analysis. Cross-file features (Django include resolution, Next.js re-exports, mount/prefix composition, route→mutation linking) make caching final analysis results unsafe, but per-file parse trees are expensive and per-file adapter raw output (routes/evidence/mutations/diagnostics keyed to a single file) is deterministic given (file content, config, version).

  1. Cache layer
    • .authmap/cache/ (gitignored; document in docs/DATA_HANDLING.md) keyed by (content_hash, config_hash, authmap_version, adapter_set). Store serialized per-file adapter output (FrameworkAdapter raw emissions are already serde-serializable types in authmap-core).
    • On hit: skip read+parse+adapter for that file, splice cached output into the analysis input. On miss: normal path, write-through.
    • Invalidation caveat: adapters that read other files (Django URLconf include chains, Next.js re-export resolution) must either declare cross-file inputs (hash the resolved dependency set into the key) or be excluded from caching in v1 — start by caching only the leaf-file extraction stages and measuring, rather than promising full correctness for cross-file resolution.
    • --no-cache flag and cache = false config escape hatch; cache misses/corruption degrade silently to full scan.
  2. Diff-scoped scanning
    • authmap scan --changed-from <git-ref> (and --changed-files <path>... for non-git callers): resolve the changed set via git diff --name-only, force cache-miss for those files, and warm everything else from cache. This is sugar over the cache, not a separate mechanism — a diff-scoped scan without a warm cache just performs a full scan.
  3. Measurement
    • Land after (or with) the benchmark harness so the win is quantified; report cache hit-rate in the scan diagnostics/summary so users can see it working.

Acceptance criteria

  • Second scan of an unchanged fixture repo produces byte-identical JSON output with >90% cache hit-rate and measurably lower wall time.
  • Editing one file invalidates exactly that file (plus declared dependents); output equals a cold full scan (determinism test: cold vs warm outputs compared in CI).
  • --changed-from produces output identical to a full scan on the same tree (correctness first).
  • Version/config changes invalidate the whole cache; --no-cache bypasses it.
  • docs/USAGE.md, docs/CONFIGURATION.md, docs/DATA_HANDLING.md (what is stored on disk) updated; CHANGELOG entry.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliCommand-line interface and user commandsarea:discoveryRepository walking, file discovery, ignores, and project hintsarea:performanceLarge repository performance, scan limits, and reliability safeguardsenhancementNew feature or requestrustRust implementation work

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions