Cache per-blob facts by content address for incremental maps and multi-revision history
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 25/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- git, go
- Ambito
- cli, performance, tooling
Direzione di ricerca
Start with pkg/scanner/git_cache_test.go and profile.Profile to understand the existing Git fixture and profiling flow, then review the proposed new pkg/cache/ and tests/history/ entry points. The work is complete when cached and uncached outputs are byte-identical, cache invalidation and integrity tests pass, and history fixtures match uncached per-revision results.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
User outcome
- Re-profiling a repository after a
git pullonly costs the changed files. - Profiling every release tag of a project is a routine command. Example: "Linux language, line-count and component composition across every v-tag."
- CI on a monorepo pays for the diff, not the tree.
Why
- dircue's per-file facts are pure functions of (content bytes, path-derived inputs such as filename, extension and
.gitattributesresults, ruleset version, and coverage-affecting settings). In Git sources the content is already addressed by its blob ID, so memoization is both sound and nearly free to key. - Most blobs are unchanged between neighboring commits (typically well over 95%). Incremental maps and multi-revision history become cheap without any cleverness in the observers.
- A time-lapse of a famous codebase's composition, computed in seconds on a laptop, is a memorable demo. It's also a genuinely useful product feature: architecture evolution, language migration tracking, the growth of generated or vendored code.
- Linguist, scc and BCA have no such mode; each run starts from scratch. This is additive value that doesn't re-implement any of them.
Proposal
- An opt-in cache:
--cache DIR, orDIRCUE_CACHE_DIR. It stays off by default so a plain run keeps zero writes outside stdout and stderr.- The store is content-addressed. Its key is a SHA-256 of: the blob ID (or directory content hash, see #101); the path-dependent inputs actually consulted; the ruleset fingerprint (Linguist data version, Enry and scc and BCA versions, dircue fact-schema version); and the coverage-affecting settings fingerprint (#91).
- Each value holds the ledger row(s) for that file (#97), including function rows.
- Every entry is self-verifying: it stores its key inputs and checksum, so a corrupt or foreign entry is a miss, never a wrong answer.
dircue history(name to be decided). Arguments:--revs A..B,--tags GLOBor--first-parent --every N.- It emits one map summary or ledger aggregate per revision, as JSONL, plus a composition time-series table.
- Blobs are read in pack order and shared across revisions (see #105).
- Bounds: a maximum number of revisions, and budgets that report
partialwhen hit.
- Multi-file facts (components, edges, deployables) are recomputed from the cached per-file facts and the declaration-file contents. Only the per-file extraction stages are cached, not whole-map aggregation. This keeps correctness simple.
- Cache management:
dircue cache stats|prune --max-bytes, with deterministic eviction (LRU by access time is the only nondeterminism, and it never affects output).
Tasks
- Specify the cache key precisely, per observer: which path-derived and attribute inputs each fact consults. Add an audit test that fails if an observer reads an input the key doesn't include.
- Implement the store: a directory of sharded files or a single append-only pack, with atomic writes, a size cap and integrity checks.
- Wire the cache into the ledger row builder, and prove output identity with and without the cache.
-
dircue history: revision selection, pack-order blob scheduling, per-revision aggregation, JSONL and table output. - Documentation: cache semantics, safety (the cache is user-local data, never shared by default) and an invalidation guide.
Test plan
1. Unit tests
New package pkg/cache/ (new); test file pkg/cache/cache_test.go (new).
- Key completeness audit: for each observer (language classifier, scc counter, format inspector, role assigner, BCA summarizer), assert that every input the observer reads is represented in the cache key. Implement as a compile-time or init-time table in
pkg/cache/that enumerates key components; a test verifies the table is non-empty and each component produces a different key when mutated. This is the mandatory audit described in the issue. - Cold / warm / no-cache identity: given a synthetic multi-file Git fixture, assert that
profile.Profileis byte-identical across three runs:--cache DIR(cold),--cache DIR(warm, same dir), and no--cacheflag. Use the existinggitFixturehelper inpkg/scanner/git_cache_test.goas a model. - Invalidation mutation tests: for each key component below, flip it and assert the cache produces a miss (not a wrong hit):
- blob ID (replace content, keep path);
- path (rename file, keep content);
.gitattributesoverride (add alinguist-languageattribute);- ruleset fingerprint (bump
pkg/codemetrics:EngineVersion); - coverage-affecting setting (change
MaxFileBytes).
Five sub-tests, all table-driven.
- Integrity: write a valid cache entry, then corrupt it (flip one byte in the middle). Assert the corrupted entry is treated as a miss and a fresh value is computed and stored.
- Concurrent writers: launch 4 goroutines writing the same key simultaneously on a temp directory. Assert no file corruption, no panic, and the final stored value is one of the valid writes (atomic-write semantics).
- Read-only cache directory: set the cache dir to a directory with mode 0444. Assert that
--cachefalls back to a no-cache run gracefully (non-fatal error, output unaffected). - Full disk simulation: wrap the store's write path with an injected error that returns
ENOSPC. Assert graceful degradation, no partial entries left on disk. - Foreign version entry: place a cache entry written by a future hypothetical version (bumped schema version field). Assert it is a miss, not a crash.
2. Independent oracles
Harness: tests/history/ (new).
- The independent oracle for history correctness is a sequence of single-revision
dircue mapruns (one per tag), each compared against the corresponding entry in the history output. No external tool is needed here; the oracle is dircue itself in uncached single-revision mode. - For the cache store itself: run
sha256sumon every stored entry after a warm run. Assert checksums match the re-computed key hash (self-verifying property). This requires no external tool. - Tie to #84 by noting that the conformance oracle (
dircue-linguist:9.7.0) already gates the underlying language facts; the cache layer does not alter them.
3. Hand-labeled ground truth and fixtures
- Commit 5 small synthetic Git fixtures under
tests/history/fixtures/(new). Each fixture has 3–5 commits, a known composition change between commits, and a hand-verified.expected-history.jsonl(committed as plain text; total size well under 100 KiB). - For the corpus history run: the harness fetches a bare kernel clone via
tests/performance/fetch.py(existing) on demand; never committed. Per #88, only the receipt and expected-aggregate files are committed, not the corpus. - Precision gate: the warm-cache aggregate for every fixture revision must equal the cold-run aggregate exactly (bit-for-bit identity); there is no tolerance.
4. Metamorphic invariants (#85)
- Cold = warm = no-cache: output is byte-identical across all three cache modes for every corpus fixture and source mode. Assert in
pkg/cache/cache_test.go. - Worker-count invariant: cache hit rate and output are identical with
--workers 1and--workers 8for the same warm cache. Assert inpkg/cache/cache_test.go. - Revision independence: profiling revision A after revision B (warm cache) gives the same output as profiling revision A from scratch (cold). Assert in
tests/history/e2e.py. - Shallow clone: running
dircue historyon a shallow clone that lacks some commits emitscoverage: partial, reason: history_unavailablefor the missing range, not a fabricated aggregate. Assert in a dedicatedtests/history/fixtures/shallow/fixture (new).
5. Fuzz targets (#87)
New file pkg/cache/fuzz_test.go (new), on-demand only (never campaigns in CI per #87):
FuzzCacheKeyRoundtrip(f *testing.F): seeds with known key inputs (blob ID, path, settings hash); checks thatMarshalKey(UnmarshalKey(k)) == kand that distinct inputs produce distinct keys.FuzzCacheEntryDecode(f *testing.F): seeds with valid serialized entries; checks that a mutated byte either decodes cleanly as a different value or is rejected as corrupt — never panics.
6. Mutation-testing focus (#86)
Priority mutation targets:
- The
keyIncludes(blobID)check in the key builder — a mutant omitting the blob ID must be killed by the blob-swap invalidation test. - The integrity checksum comparison in the cache reader — a mutant that skips the checksum check must be killed by the corruption test.
- The atomic write path (write to temp file, then rename) — a mutant that writes directly must be killed by the concurrent-writer test.
Use go-mutesting on pkg/cache/ only.
7. Performance and resource checks (#89/#90)
- Cache hit rate counter (PR CI): expose a
CacheHitsandCacheMissescounter inpkg/cache/. In the warm-cache test, assertCacheHits / (CacheHits + CacheMisses) >= 0.95on a fixture where 95%+ of blobs are unchanged across two adjacent revisions. This counter is deterministic and belongs in thetestjob. - Warm-cache speedup (on-demand): in
tests/history/e2e.py, assert that warm-cache re-profiling of a large corpus revision completes within 10% of the cold-run time per the issue's success criterion. Log speedup ratio to JSONL. Run viamake history-perf(new target) orworkflow_dispatchonly. - Disk-use cap: assert that
dircue cache prune --max-bytes Nleaves the cache dir under N bytes with correct eviction ordering. Unit test inpkg/cache/cache_test.go.
8. E2E script
tests/history/e2e.py (new). JSONL log fields per revision:
{"revision": "v6.1", "wall_s": 1.2, "cache_mode": "cold|warm",
"cache_hits": 91000, "cache_misses": 120, "bytes_inflated": 45000000,
"rss_mib": 220, "languages_top3": [...], "mismatch_vs_uncached": false}
Steps:
- Fetch bare kernel clone via
tests/performance/fetch.py; list allv*tags up to a configurable--max-revisionscap. - Run
dircue history --tags 'v*' --cache $TMP(cold); log per-revision stats. - Run
dircue history --tags 'v*' --cache $TMP(warm); log per-revision stats and hit rates. - For each revision, run an uncached single-revision
dircue map; compare aggregates against the history output. Assert exact match. - Emit a summary table: revision, cold wall time, warm wall time, speedup, hit rate.
- Exit non-zero if any
mismatch_vs_uncached == trueor if warm speedup < 3× on a corpus where > 90% of blobs are unchanged.
Invoke: make history-e2e (new target) or workflow_dispatch. Never schedule:.
9. CI placement
| What | Where | Trigger |
|---|---|---|
Unit tests (pkg/cache/...), cold/warm/no-cache identity, invalidation, integrity |
existing test job in ci.yml |
every PR and push |
| Cache hit-rate counter gate (fast, deterministic) | existing test job |
every PR and push |
History correctness on committed fixtures (tests/history/fixtures/) |
new step in test job |
PRs touching pkg/cache/ or pkg/scanner/ |
History E2E + speedup benchmark (e2e.py) |
workflow_dispatch + make history-e2e |
on-demand only |
| Disk-use cap and prune correctness | existing test job |
every PR and push |
No schedule: trigger anywhere.
10. Regression fences
- Key-completeness regression: the audit table in
pkg/cache/is a compile-time list; if an observer gains a new input that affects output, the table must be updated or a test fails. Add aTestKeyCompletenessTableNonEmptytest as a hard guard. - Shallow-clone contract:
tests/history/fixtures/shallow/(new, committed) is a permanent regression fixture for the "never fabricate history" contract. Any future refactor of the history walker must keep this test passing. - Output-identity regression: the existing parity receipts (
tests/conformance/results/latest.json,tests/performance/kernel_git/receipt.json) are the gates. A cache implementation that silently alters output would fail these receipts; they need not be duplicated.
Risks
- Key completeness. Key completeness is the whole game; an incomplete key causes wrong answers. The audit test is mandatory.
- Disk use. Cache disk use on huge histories needs caps.
- Shallow clones. Their history is unavailable; report
unknown, never fabricate.
Success criteria
- Warm-cache re-profiling after a typical pull takes under 10% of the cold time on large repos.
- Kernel history over all major tags completes within a published budget.
- Output is identical with and without the cache.
Related: #10, #39, #89, #90, #27 (churn-aware hotspots can build on history). Related idea issues: #97, #105, #101.
Sequencing: Depends on #97, because cached values are ledger rows. It benefits from #101 (directory-mode keys) and #105 (pack-order reads).
This proposal came out of an idea-wizard session on 2026-09-23. It isn't a release commitment, and the milestone is deliberately unset so the owner can decide per idea. It must preserve dircue's contracts:
- deterministic, offline output, with no execution of inspected content;
- the legacy Linguist-compatible CLI and output unchanged;
unknownnever rendered as absent;- no provider findings or verdicts imported;
- CI event-driven only (no
schedule:).
- Lingua principale
- Go
- Stelle
- 0
- Fork
- 0
- Merge medio
- 5h 8m
- PR unite (30g)
- 54
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di war-and-code/dircue
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
war-and-code/dircue#200 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement follow-up
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
war-and-code/dircue#201 ·
I maintainer di solito rispondono entro 1 giorno
-
follow-up
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
war-and-code/dircue#179 ·
I maintainer di solito rispondono entro 1 giorno
-
follow-up
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
war-and-code/dircue#178 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement follow-up
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
war-and-code/dircue#172 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di war-and-code/dircue
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 81/100
apache/rocketmq-dashboard#6895 ·
I maintainer di solito rispondono entro 3 giorni
-
agent-butler-finding chore
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
jordansmall/spindrift#5466 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 87/100
777genius/agent-notifications#490 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
devantler-tech/ksail#7836 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
keyxmakerx/Chronicle#1469 ·
I maintainer di solito rispondono entro 1 giorno