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
2 changes: 2 additions & 0 deletions .github/workflows/compass-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ jobs:
checks: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- name: Install locked cargo-audit
run: cargo install cargo-audit --version 0.22.2 --locked
- name: Audit Rust dependencies
uses: rustsec/audit-check@858dc40f52ca2b8570b7a997c1c4e35c6fc9a432 # Node 24 update
with:
Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
witness relationships without printing opaque graph-node IDs. Canonical JSON
and SARIF retain every exact identity and remain unchanged.

- Replace production community detection for typed graphs with deterministic
native Leiden over a versioned typed-evidence topology. Publish strict,
digest-bound `compass.community-quality/1` evidence, preserve frozen
influence and full-quality fallback during incremental updates, and retain
fixed resolution as the default because the bounded three-candidate selector
did not meet its clustering-time gate. Community membership and graph-local
IDs may change; Base Graph nodes, relationships, direction, multiplicity,
provenance, and `compass.graph/1` remain unchanged.

## 0.3.24 - 2026-09-11

- Add `compass ensure` as an idempotent agent-session and linked-worktree
Expand All @@ -28,7 +37,6 @@
edge-ordered adjacency capability; older sidecars remain valid recovery
inputs but directional store queries fail with an explicit rebuild
instruction instead of returning a backend-dependent truncated subset.

- Refactor universal language metadata around `UniversalEvidenceProducer` and
`UniversalEvidencePipeline`. `UniversalCandidate`/`UniversalComplete` are
now the clearer lifecycle states `Qualifying`/`Qualified`; the serialized
Expand Down
32 changes: 32 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,38 @@ The `extract --code-only` profile excludes document extractors from structural
node and edge publication while retaining the scanned file inventory and its
status records.

## Community detection profile cutover

Typed clustered graphs use the complete profile
`seeded-leiden-modularity/v1` + `typed-evidence-undirected/v1` +
`community-quality/v1` + `fixed-resolution/v1`, seed `42`, and
`community-limits/v1`. The default resolution is fixed at `1`; an explicit
`--resolution N` remains a single fixed positive finite resolution. The
bounded three-candidate selector has identity `bounded-multiresolution/v1` but
remains qualification-only because it exceeded the clustering-time acceptance
gate.

This is a compatibility-sensitive membership cutover without a
`compass.graph/1` schema change. Community numeric IDs, membership, labels,
reports, and architecture groupings may change. Base Graph node and edge
identity, direction, multiplicity, anchors, provenance, and canonical encoding
do not change as a consequence of clustering. The complete profile enters the
configuration digest and current/history build profiles, so old output is
rebuilt coherently rather than partially reused.

Clustered typed builds add strict `compass.community-quality/1` at
`community-quality.json`. Readers must validate its self-digest, graph
generation, exact canonical graph digest, and profile identity and reject
unknown majors or fields. Missing evidence on an older, schema-less legacy, or
unclustered graph means unavailable. Direct reclustering of a schema-less
legacy graph retains `seeded-louvain/v1` compatibility and publishes no quality
sidecar.

Historical realizations and their sidecars are immutable. Compass never
substitutes Louvain results under a Leiden profile or interprets one profile's
member IDs as another profile's result. Existing `cohesion` remains the public
density projection, now calculated by the shared quality evaluator.

## Compass Store release contract

The first supported local store line is `0.3.x`. Its logical machine formats
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,33 @@ Use `--engine json` until the rebuild completes. Do not edit or copy SQLite
tables to add the capability marker; the directional index key order must be
rebuilt from the validated graph.

## Rebuild communities for the Leiden profile

Typed clustered graphs now use `seeded-leiden-modularity/v1` over
`typed-evidence-undirected/v1` with the fixed-resolution selector. Run a normal
forced build after upgrading:

```bash
compass update --force
```

Community membership, numeric IDs, labels, architecture groupings, and derived
reports may change. Do not copy old `community` attributes or label signatures
into the new graph. Base Graph node and relationship identity, direction,
multiplicity, anchors, and provenance are unchanged by clustering.

Omitting `--resolution` uses fixed resolution `1`; an explicit
`--resolution N` uses exactly one positive finite value. Automatic
multi-resolution selection is not a production default. Clustered typed builds
add `community-quality.json`, bound to the exact `graph.json`. Upgrade strict
artifact readers to accept `compass.community-quality/1` and reject unknown
majors, unknown fields, digest mismatch, or profile mismatch. Missing evidence
on an older or legacy graph means unavailable, not zero quality.

Published historical realizations remain immutable. New materializations use
the complete Leiden profile fingerprint; Compass does not rewrite or silently
reinterpret older memberships.

## Frontend graph vocabulary

Recent pre-release builds can add React-oriented `renders` edges and UI/server
Expand Down
25 changes: 25 additions & 0 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,31 @@ three-sample median of about 2,353 MiB. These native-volume measurements avoid
the multi-second publication variance observed on the mounted workspace, but
remain runner-specific rather than a cross-platform guarantee.

## Community detection qualification

The version-1 community-quality runner evaluates 15 deterministic fixture
families, including input permutations, planted partitions, articulation, hub,
direction, confidence, containment, isolate, and resolution-limit cases:

```bash
./scripts/qualify_code_graph_v1.sh --community-quality
```

On 2026-09-12, an aarch64 macOS debug build ran the complete compact fixture
set 50 times per process. Seven-process medians were 1.25 seconds for
compatibility Louvain, 1.82 seconds for fixed-resolution typed Leiden, and
3.01 seconds for three-candidate typed Leiden. Compact setup-heavy debug
timings are diagnostic, not the pinned real-repository release oracle, and no
RSS or cold-build claim is derived from them.

The fixture quality gates pass, including deterministic equality, connected
communities, exact required recovery, and improved ring-of-cliques and
articulation recovery. Production nevertheless uses fixed resolution; the
automatic selector remains qualification-only until pinned-corpus clustering,
cold-build, RSS, and incremental gates pass. See the
[qualification report](docs/implementation/community-detection-quality-qualification.md)
for exact fixture results and omissions.

## Versioned history qualification

Build a release binary, then measure a clean real repository:
Expand Down
107 changes: 92 additions & 15 deletions crates/compass-cli/src/history_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use compass_core::{
build_graph_with_layers_retained,
};
use compass_files::{DetectOptions, IgnorePolicy, Manifest, ManifestKind, ProjectConfig, detect};
use compass_graph::{
COMPATIBILITY_CLUSTER_SEED_TEXT, COMPATIBILITY_CLUSTER_SELECTOR, QUALITY_CLUSTER_ALGORITHM,
QUALITY_CLUSTER_LIMITS, QUALITY_CLUSTER_QUALITY, QUALITY_CLUSTER_TOPOLOGY,
};
use compass_history::{
BuildProfile, CompletedGraphArtifacts, CompletionEvidence, GraphArtifacts,
HISTORY_GRAPH_SCHEMA, HistoryError, MAX_DIAGNOSTIC_BYTES,
Expand Down Expand Up @@ -91,13 +95,15 @@ impl HistoryBuildOptions {
"--token-budget",
"default",
);
push_profile_option(
&profile,
&mut forwarded,
"resolution",
"--resolution",
"none",
);
if profile.value("cluster_resolution_policy") == Some("fixed/v1") {
push_profile_option(
&profile,
&mut forwarded,
"resolution",
"--resolution",
"none",
);
}
push_profile_option(
&profile,
&mut forwarded,
Expand Down Expand Up @@ -139,7 +145,10 @@ impl HistoryBuildOptions {
));
}
};
insert_current_engine_profile(&mut profile, deep)?;
let excludes_hubs = profile
.value("exclude_hubs")
.is_some_and(|value| value != "none");
insert_current_engine_profile(&mut profile, deep, excludes_hubs)?;
if profile.value("ocr_mode").is_none() {
profile.insert("ocr_mode", "off")?;
}
Expand Down Expand Up @@ -199,7 +208,7 @@ impl HistoryBuildOptions {
resolve_provider(&mut values)?;
}
let mut profile = BuildProfile::default();
insert_current_engine_profile(&mut profile, values.deep)?;
insert_current_engine_profile(&mut profile, values.deep, values.exclude_hubs.is_some())?;
for (key, value) in [
("gitignore", values.gitignore.to_string()),
("code_only", values.code_only.to_string()),
Expand Down Expand Up @@ -307,6 +316,9 @@ impl HistoryBuildOptions {
for exclude in &values.excludes {
forwarded.extend(["--exclude".to_owned(), exclude.clone()]);
}
// The qualified production profile is fixed-resolution Leiden even
// when the user omitted the flag. Forward the resolved value so a
// historical subprocess materializes the exact persisted profile.
forwarded.extend([
"--resolution".to_owned(),
normalized_float(values.resolution),
Expand All @@ -332,6 +344,7 @@ impl HistoryBuildOptions {
fn insert_current_engine_profile(
profile: &mut BuildProfile,
deep: bool,
excludes_hubs: bool,
) -> Result<(), HistoryError> {
for (key, value) in [
("compass_version", env!("CARGO_PKG_VERSION").to_owned()),
Expand Down Expand Up @@ -428,8 +441,25 @@ fn insert_current_engine_profile(
),
("enabled_features", "workspace-default".to_owned()),
("direction", "native-source-semantics".to_owned()),
("cluster_algorithm", "seeded-louvain/v1".to_owned()),
("cluster_seed", "42".to_owned()),
("cluster_algorithm", QUALITY_CLUSTER_ALGORITHM.to_owned()),
("cluster_seed", COMPATIBILITY_CLUSTER_SEED_TEXT.to_owned()),
("cluster_topology", QUALITY_CLUSTER_TOPOLOGY.to_owned()),
("cluster_quality", QUALITY_CLUSTER_QUALITY.to_owned()),
(
"cluster_selector",
COMPATIBILITY_CLUSTER_SELECTOR.to_owned(),
),
("cluster_resolution_policy", "fixed/v1".to_owned()),
(
"cluster_hub_policy",
if excludes_hubs {
"exclude-percentile/v1"
} else {
"none/v1"
}
.to_owned(),
),
("cluster_limits_version", QUALITY_CLUSTER_LIMITS.to_owned()),
(
"semantic_prompt_sha256",
compass_semantic::extraction_prompt_sha256(deep),
Expand Down Expand Up @@ -477,6 +507,12 @@ fn validate_persisted_profile(profile: &BuildProfile) -> Result<(), HistoryError
| "direction"
| "cluster_algorithm"
| "cluster_seed"
| "cluster_topology"
| "cluster_quality"
| "cluster_selector"
| "cluster_resolution_policy"
| "cluster_hub_policy"
| "cluster_limits_version"
| "gitignore"
| "code_only"
| "cargo"
Expand Down Expand Up @@ -515,15 +551,41 @@ fn validate_persisted_profile(profile: &BuildProfile) -> Result<(), HistoryError
("program_provider_policy", "offline-artifacts-first"),
("enabled_features", "workspace-default"),
("direction", "native-source-semantics"),
("cluster_algorithm", "seeded-louvain/v1"),
("cluster_seed", "42"),
("cluster_algorithm", QUALITY_CLUSTER_ALGORITHM),
("cluster_seed", COMPATIBILITY_CLUSTER_SEED_TEXT),
("cluster_topology", QUALITY_CLUSTER_TOPOLOGY),
("cluster_quality", QUALITY_CLUSTER_QUALITY),
("cluster_limits_version", QUALITY_CLUSTER_LIMITS),
] {
if profile.value(key) != Some(expected) {
return Err(HistoryError::InvalidFingerprint(format!(
"persisted {key} is incompatible with {expected}"
)));
}
}
if profile.value("cluster_resolution_policy") != Some("fixed/v1") {
return Err(HistoryError::InvalidFingerprint(
"persisted cluster_resolution_policy is incompatible with fixed/v1".to_owned(),
));
}
if profile.value("cluster_selector") != Some(COMPATIBILITY_CLUSTER_SELECTOR) {
return Err(HistoryError::InvalidFingerprint(format!(
"persisted cluster_selector is incompatible with {COMPATIBILITY_CLUSTER_SELECTOR}"
)));
}
let expected_hub_policy = if profile
.value("exclude_hubs")
.is_some_and(|value| value != "none")
{
"exclude-percentile/v1"
} else {
"none/v1"
};
if profile.value("cluster_hub_policy") != Some(expected_hub_policy) {
return Err(HistoryError::InvalidFingerprint(format!(
"persisted cluster_hub_policy is incompatible with {expected_hub_policy}"
)));
}
for (key, expected) in [
(
"document_raw_bytes_limit",
Expand Down Expand Up @@ -849,6 +911,7 @@ struct HistoryBuildValues {
ocr_languages: Vec<String>,
token_budget: Option<usize>,
resolution: f64,
resolution_explicit: bool,
exclude_hubs: Option<f64>,
gitignore: bool,
excludes: Vec<String>,
Expand All @@ -872,6 +935,7 @@ impl Default for HistoryBuildValues {
ocr_languages: Vec::new(),
token_budget: None,
resolution: 1.0,
resolution_explicit: false,
exclude_hubs: None,
gitignore: true,
excludes: Vec::new(),
Expand Down Expand Up @@ -956,7 +1020,10 @@ pub(crate) fn parse_build_command(
values.ocr_profile = value.to_owned();
}
"--token-budget" => values.token_budget = Some(positive_usize(name, value)?),
"--resolution" => values.resolution = positive_float(name, value)?,
"--resolution" => {
values.resolution = positive_float(name, value)?;
values.resolution_explicit = true;
}
"--exclude-hubs" => values.exclude_hubs = Some(finite_float(name, value)?),
"--format" => format = Some(value.to_owned()),
"--profile-from" => profile_from = Some(nonempty(name, value)?.to_owned()),
Expand Down Expand Up @@ -1418,6 +1485,7 @@ impl NativeCompleteGraphBuilder {
.value("resolution")
.and_then(|value| value.parse().ok())
.unwrap_or(1.0);
options.resolution_explicit = true;
options.exclude_hubs = self
.profile
.value("exclude_hubs")
Expand Down Expand Up @@ -1451,12 +1519,21 @@ impl NativeCompleteGraphBuilder {
})?;
let manifest = serde_json::from_slice(&manifest_bytes)
.map_err(|error| MaterializeError::Builder(error.to_string()))?;
let artifacts = GraphArtifacts::from_trusted(
let mut artifacts = GraphArtifacts::from_trusted(
retained.document,
retained.program,
retained.analysis,
Some(manifest),
)?;
artifacts.authoritative_sidecars.insert(
"community-quality.json".to_owned(),
fs::read(result.output_dir.join("community-quality.json")).map_err(|source| {
compass_files::FileError::Io {
path: result.output_dir.join("community-quality.json"),
source,
}
})?,
);
let code_files = result
.detection
.files
Expand Down
Loading
Loading