Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions crates/compass-files/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use sha2::{Digest, Sha256};

use crate::{FileError, StatHashIndex, file_hash, io_error, write_bytes_atomic, write_json_atomic};

const AST_EXTRACTOR_VERSION: &str = "0.9.21";
const AST_CACHE_VERSION: &str = "1";
const CACHE_ENCODING_VERSION: u32 = 6;
const MESSAGEPACK_EXTENSION: &str = "msgpack";

Expand Down Expand Up @@ -108,7 +108,7 @@ impl CacheKind {
pub struct Cache {
root: PathBuf,
cache_base: PathBuf,
extractor_version: String,
ast_cache_version: String,
hashes: StatHashIndex,
hash_policy: CacheHashPolicy,
session_hashes: HashMap<PathBuf, SessionHash>,
Expand Down Expand Up @@ -141,7 +141,7 @@ impl Cache {
let cache = Self {
root,
cache_base,
extractor_version: AST_EXTRACTOR_VERSION.to_owned(),
ast_cache_version: AST_CACHE_VERSION.to_owned(),
hashes,
hash_policy: options.hash_policy,
session_hashes: HashMap::new(),
Expand All @@ -151,11 +151,6 @@ impl Cache {
Ok(cache)
}

pub fn with_extractor_version(mut self, version: impl Into<String>) -> Self {
self.extractor_version = version.into();
self
}

/// Program-only cache users do not consult the path stat index. Disabling
/// its drop-time flush prevents a parallel Program worker from overwriting
/// hashes recorded by the graph extraction worker.
Expand All @@ -167,7 +162,7 @@ impl Cache {
pub fn directory(&self, kind: &CacheKind, prompt_fingerprint: Option<&str>) -> PathBuf {
let mut directory = self.cache_base.join(kind.directory_name());
if matches!(kind, CacheKind::Ast) {
directory = directory.join(format!("v{}", self.extractor_version));
directory = directory.join(format!("v{}", self.ast_cache_version));
} else if let Some(fingerprint) = prompt_fingerprint {
directory = directory.join(format!("p{fingerprint}"));
}
Expand Down Expand Up @@ -419,7 +414,7 @@ impl Cache {

fn cleanup_stale_ast(&self) {
let base = self.cache_base.join("ast");
let current = format!("v{}", self.extractor_version);
let current = format!("v{}", self.ast_cache_version);
let Ok(entries) = fs::read_dir(&base) else {
return;
};
Expand Down
16 changes: 5 additions & 11 deletions crates/compass-files/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,9 +508,9 @@ fn cache_versions_legacy_fingerprints_pruning_and_cleanup_are_total() -> Result<
let root = directory.path().join("root");
let cache_root = directory.path().join("cache-root");
fs::create_dir_all(&root)?;
fs::create_dir_all(cache_root.join("compass-out/cache/ast/v0.9.20"))?;
fs::create_dir_all(cache_root.join("compass-out/cache/ast/v0.9.21"))?;
fs::write(
cache_root.join("compass-out/cache/ast/v0.9.20/stale.json"),
cache_root.join("compass-out/cache/ast/v0.9.21/stale.json"),
"{}",
)?;
fs::create_dir_all(cache_root.join("compass-out/cache/ast/vold"))?;
Expand All @@ -535,17 +535,11 @@ fn cache_versions_legacy_fingerprints_pruning_and_cleanup_are_total() -> Result<
assert!(
default_cache
.directory(&CacheKind::Ast, None)
.ends_with("ast/v0.9.21/e6")
.ends_with("ast/v1/e6")
);
assert!(!cache_root.join("compass-out/cache/ast/v0.9.20").exists());
assert!(!cache_root.join("compass-out/cache/ast/v0.9.21").exists());

let mut cache = Cache::open(&root, CacheOptions::output_directory(Some(&cache_root)))?
.with_extractor_version("current");
assert!(
cache
.directory(&CacheKind::Ast, None)
.ends_with("ast/vcurrent/e6")
);
let mut cache = Cache::open(&root, CacheOptions::output_directory(Some(&cache_root)))?;
assert!(
cache
.directory(&CacheKind::SemanticMode("deep".to_owned()), Some("abc"))
Expand Down
10 changes: 6 additions & 4 deletions docs/implementation/extraction-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,12 @@ Cases to test:
Performance qualification compares cold, warm unchanged, single-file change,
rename, and delete cases against Compass-owned baselines.

Extractor semantics use a versioned AST cache namespace. The C declarator and
positional-document corrections advance that namespace to `v0.9.21`, so the
first update after upgrading refreshes AST facts. Later unchanged updates return
to the normal warm path.
Extractor semantics use the Compass-owned AST compatibility namespace `v1`.
This cache version is independent of the Compass package release and advances
only when changed extractor behavior makes existing AST entries unsafe to
reuse. The first update after such a version change refreshes AST facts; later
unchanged updates return to the normal warm path. The nested `e6` namespace is
the binary cache encoding version, not part of the AST compatibility version.

## Watch mode

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Compass AST Cache v1 Hard Cutover

**Date:** 2026-07-27

## Goal

Replace the historical Graphify-derived AST cache namespace
`compass-out/cache/ast/v0.9.21/e6` with a Compass-owned compatibility
namespace:

```text
compass-out/cache/ast/v1/e6
```

The `v1` segment identifies Compass AST cache compatibility. It is independent
of the Compass package release and advances only when extractor behavior makes
existing AST entries unsafe to reuse. The `e6` segment remains the binary cache
encoding version.

## Hard-cutover behavior

`Cache::open` recognizes `v1` as the only current AST namespace. Its existing
stale-version cleanup removes every other `v*` directory under
`compass-out/cache/ast/`, including `v0.9.21`.

Compass does not read, copy, migrate, or fall back to entries in the old
namespace. The first build after upgrading is therefore a cold AST-cache build.
Semantic and Program IR caches are outside this cutover and remain untouched.

## Code design

In `compass-files`, rename the internal default and state from extractor-version
terminology to AST-cache-version terminology:

- `AST_EXTRACTOR_VERSION` becomes `AST_CACHE_VERSION`;
- `Cache::extractor_version` becomes `Cache::ast_cache_version`;
- the public extractor-version override is removed so callers cannot create
alternate AST namespaces.

The default compatibility value is the explicit string `"1"`. It must not use
`CARGO_PKG_VERSION`, because normal Compass releases must not discard compatible
AST caches.

Directory selection and stale-cache cleanup continue to use the same version
value, ensuring that the namespace Compass writes is also the only namespace it
preserves.

## Documentation

The extraction-pipeline documentation will describe `v1` as the Compass-owned
AST compatibility namespace and state that incompatible extractor changes
advance it. Historical implementation plans remain historical records and are
not rewritten.

## Verification

A focused contract test will first require `ast/v1/e6` and verify that opening
the cache deletes a populated `ast/v0.9.21` directory. The test must fail before
the production constant and names are changed, then pass afterward.

Verification consists of:

1. focused `compass-files` cache contract testing;
2. the complete `compass-files` test suite;
3. formatting and Clippy for `compass-files`;
4. `graphify update .` after code changes, as required by the parent project.
Loading