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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Unreleased

- Make query failures and paths more trustworthy: exact-looking missing symbols
now return structured `no_match` signals across discovery and typed natural
queries; `compass path` requires exact endpoints, reports unreachable targets,
and uses deterministic relation-weighted routing with a visible shorter weak
alternative.

- Make plain query output concise by default, add `--evidence` for full
provenance, raise the default text-page budget to 8,000 tokens, and move text
cursors to `compass.query.discovery-text-page/2`. Generic relationship words
no longer dominate discovery seeding.

## 0.3.25 - 2026-09-13

- Raise the immutable-history aggregate authoritative-evidence limit from
Expand Down
16 changes: 14 additions & 2 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,20 @@ now defaults to `compass.query.discovery/1`; `--dfs` and `--context` compose
with discovery. Explicit `--traverse` or legacy-only `--budget`/`--page`
preserve the established text traversal and reject discovery controls.
CompassQL and explicit typed query commands remain unchanged. Discovery text
pagination uses the versioned `compass.query.discovery-text-page/1` cursor;
JSON rejects those presentation-only controls.
pagination now uses the versioned `compass.query.discovery-text-page/2` cursor.
Text is concise by default, `--evidence` restores full provenance detail, and
the selected tier is bound into the cursor. Version-1 cursors fail explicitly
rather than resuming into a different representation. The default text-page
budget is 8,000 approximate tokens. JSON rejects those presentation-only
controls and the discovery JSON schema remains `compass.query.discovery/1`.

Exact-looking discovery or typed-query operands that have only fuzzy or lexical
candidates now carry a structured `no_match` diagnostic before any fallback
content or bounded natural-query execution. Dedicated `compass path` endpoints
require unique exact identities;
weighted path selection prefers structural evidence over reference/document
shortcuts and reports an eligible shorter-but-weaker alternative separately.
These are human-query semantic changes, not graph or JSON schema changes.
`compass ask --at REV` uses the same immutable trusted `compass.graph/1`
realization selection as revision discovery. The response remains the unchanged
`compass.query/1` contract; an older realization without that trusted graph is
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

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

18 changes: 18 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ sidecars. Its output root now preserves the familiar flat artifact shape so
file-based workflows can transition while Compass's snapshot and store
layout remains visible and clearly owned.

## Query text and path resolution

Plain `compass query` output is now concise by default and its page budget is
8,000 approximate tokens. Scripts or review workflows that need the previous
expanded provenance should pass `--evidence`. Existing
`compass.query.discovery-text-page/1` cursors cannot be resumed; start the query
again to receive a `/2` cursor, and keep the concise/evidence tier unchanged
while paging. Discovery JSON remains `compass.query.discovery/1`.

Natural structural queries now mark every fuzzy execution or suggestion with
`NO EXACT MATCH`/the typed `no_match` diagnostic. Handle that signal and retry
with a suggested exact ID when exact identity is required. `compass path` no
longer promotes a fuzzy symbol candidate into an endpoint; it is
weighted toward structural relations, defaults to an eight-hop bound, and
reports `NO PATH FOUND` separately when both endpoints exist but are
unreachable. Consumers that parsed the prior human path prose should migrate to
these explicit signals; machine-query schema versions are unchanged.

## Rebuild SQLite adjacency sidecars

Store snapshots now declare edge-ID-ordered directional adjacency so bounded
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Run `compass watch` in a second terminal. For a focused task, an installed assis

```bash
compass query "where is authentication enforced?"
compass query "where is authentication enforced?" --text-budget 8000
compass query "where is authentication enforced?" --evidence
compass query "where is authentication enforced?" --cursor 'query_cursor_token'
compass explain TokenVerifier
compass path ApiHandler TokenVerifier
Expand All @@ -201,9 +201,9 @@ compass affected TokenVerifier --depth 3

These commands read the saved graph locally. Results are bounded, read-only, and tied to graph evidence instead of a model-generated guess.

Plain-language `query` uses bounded structured discovery. Its text projection returns complete deterministic entries within the `--text-budget` value, which defaults to 2,000. Follow `next=query_cursor_token` with the unchanged question and options until `next=none`.
Plain-language `query` uses bounded structured discovery. Its default text is a concise map of seeds, nodes, edges, and source locations; pass `--evidence` for full provenance and semantic digests. Each page contains complete deterministic entries within the `--text-budget` value, which defaults to 8,000. Follow `next=query_cursor_token` with the unchanged question and evidence tier until `next=none`.

The cursor fails when semantic inputs, the selected graph, or the semantic result changes. The `--traverse`, `--budget`, and `--page` options retain the legacy traversal contract; CompassQL uses its own versioned contract.
The cursor fails when semantic inputs, evidence tier, the selected graph, or the semantic result changes. Exact-looking symbol operands that do not resolve are reported as `NO EXACT MATCH` before any fuzzy or lexical fallback candidates. The `--traverse`, `--budget`, and `--page` options retain the legacy traversal contract; CompassQL uses its own versioned contract.

### Open the workbench

Expand Down
65 changes: 63 additions & 2 deletions crates/compass-cli/src/code_query_commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::path::PathBuf;

use compass_model::query_contract::{
Expand Down Expand Up @@ -223,13 +224,23 @@ fn required<'a>(values: &'a [String], index: usize, usage: &str) -> Result<&'a s
}

fn render_text(response: &CodeQueryResponse) -> String {
let mut lines = vec![format!(
let no_match = response.diagnostics.iter().find(|diagnostic| {
diagnostic.code == compass_model::query_contract::QueryDiagnosticCode::NoMatch
});
let mut lines = Vec::new();
if let Some(diagnostic) = no_match {
lines.push("match_confidence: none".to_owned());
lines.push(diagnostic.message.clone());
} else if !response.nodes.is_empty() {
lines.push("match_confidence: exact".to_owned());
}
lines.push(format!(
"{:?}: {} node(s), {} edge(s), {} path(s)",
response.operation,
response.nodes.len(),
response.edges.len(),
response.paths.len()
)];
));
lines.extend(response.nodes.iter().map(|node| {
format!(
"{} [{}] {}",
Expand All @@ -241,10 +252,60 @@ fn render_text(response: &CodeQueryResponse) -> String {
.unwrap_or_default()
)
}));
let node_labels = response
.nodes
.iter()
.map(|node| (node.id.as_str(), node.qualified_name.as_str()))
.collect::<BTreeMap<_, _>>();
let edges = response
.edges
.iter()
.map(|edge| (edge.id.as_str(), edge))
.collect::<BTreeMap<_, _>>();
for (index, path) in response.paths.iter().enumerate() {
if let Some(target) = path.node_ids.last() {
lines.push(format!("Target resolved: {target}"));
}
let mut segments = path
.node_ids
.first()
.map(|node| {
node_labels
.get(node.as_str())
.copied()
.unwrap_or(node.as_str())
.to_owned()
})
.into_iter()
.collect::<Vec<_>>();
for (edge_id, nodes) in path.edge_ids.iter().zip(path.node_ids.windows(2)) {
let Some(edge) = edges.get(edge_id.as_str()) else {
continue;
};
let right = node_labels
.get(nodes[1].as_str())
.copied()
.unwrap_or(nodes[1].as_str());
if edge.source == nodes[0] && edge.target == nodes[1] {
segments.push(format!("--{}--> {right}", edge.kind.as_str()));
} else {
segments.push(format!("<--{}-- {right}", edge.kind.as_str()));
}
}
lines.push(format!(
"{} path (weighted, {} hops): {}",
if index == 0 { "Best" } else { "Alternative" },
path.edge_ids.len(),
segments.join(" ")
));
}
lines.extend(
response
.diagnostics
.iter()
.filter(|diagnostic| {
diagnostic.code != compass_model::query_contract::QueryDiagnosticCode::NoMatch
})
.map(|diagnostic| format!("! {:?}: {}", diagnostic.code, diagnostic.message)),
);
lines.join("\n")
Expand Down
4 changes: 2 additions & 2 deletions crates/compass-cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ const PAGES: &[Page] = &[
"compass query <QUESTION> --text-budget <N>",
"compass query <QUESTION> --cursor <TOKEN>"
],
"Arguments:\n <QUESTION> Natural-language graph question\n <QUERY> Inline CompassQL query\n\nOptions:\nNatural discovery:\n --direction <VALUE> Direction: auto, incoming, outgoing, or both [default: auto]\n --scope <KIND:VALUE> Repeatable OR scope: community, source, package, or node\n --context <VALUE> Repeatable strict relationship-context filter\n --dfs Use depth-first expansion [default: breadth-first]\n --include-heuristic Include heuristic evidence [default: excluded]\n --format <text|json> Discovery output [default: text]\n --text-budget <N> Approximate tokens in one text page [default: 2000]\n --cursor <TOKEN> Continue the same immutable semantic result (text only)\n --max-depth <N> Traversal depth [default: 2; hard maximum: 8]\n --max-seeds <N> Ranked seed count [default: 3; hard maximum: 3]\n --max-candidates <N> Ranked candidate count [default/hard maximum: 256]\n --max-nodes <N> Returned node count [default/hard maximum: 500]\n --max-edges <N> Returned edge count [default/hard maximum: 1000]\n --max-expanded-relationships <N> Examined relationships [default/hard maximum: 10000]\n --max-response-bytes <N> Serialized response bytes [default/hard maximum: 8388608]\n --timeout-ms <N> Discovery deadline in milliseconds [default/hard maximum: 30000]\n\nLegacy traversal:\n --traverse Force legacy relevance traversal\n --budget <N> Approximate tokens per page [default: 2000]\n --page <N> Result page, starting at 1 [default: 1]\n\nGraph selection:\n --graph <PATH> Read a graph JSON file\n --at <REV> Query an exact immutable realization; conflicts with --graph\n\nCompassQL:\n --cql Use CompassQL mode\n --file <PATH> Read CompassQL from a file\n --stdin Read CompassQL from standard input\n --repl Start the interactive CompassQL shell\n --param <NAME=VALUE> Bind a parameter; repeatable\n --params-file <PATH> Read parameters from JSON\n --format <table|json|jsonl> CompassQL result format [default: table]\n --output <PATH> Write CompassQL results to a file\n --timeout-ms <N> CompassQL execution timeout\n --max-rows <N> CompassQL row limit [default: 10000]\n --max-path-depth <N> CompassQL path-depth limit [default: 32]\n --max-expanded-relationships <N> CompassQL relationship expansion limit\n --max-memory-bytes <N> CompassQL memory limit [default: 268435456]\n\nExamples:\n compass query \"who calls PaymentService.charge?\"\n compass query \"authentication flow\" --direction both --scope package:auth\n compass query \"what uses charge?\" --direction incoming --context call --format json\n compass query \"authentication flow\" --text-budget 8000\n compass query \"authentication flow\" --cursor '<TOKEN>'\n compass query \"authentication flow\" --budget 8000 --page 2\n compass query --cql \"MATCH (n) RETURN n LIMIT 10\" --format json\n compass query --cql --file report.cql --params-file params.json\n\nTips:\n Direction, scope, context, and DFS compose through the bounded typed discovery contract. Scope is repeatable OR and never guesses a kind. Discovery limits must be positive; values above a hard maximum are rejected rather than clamped. Legacy --traverse, --budget, and --page cannot be mixed with discovery controls.\n Historical discovery requires an immutable realization retaining trusted compass.graph/1 data."
"Arguments:\n <QUESTION> Natural-language graph question\n <QUERY> Inline CompassQL query\n\nOptions:\nNatural discovery:\n --direction <VALUE> Direction: auto, incoming, outgoing, or both [default: auto]\n --scope <KIND:VALUE> Repeatable OR scope: community, source, package, or node\n --context <VALUE> Repeatable strict relationship-context filter\n --dfs Use depth-first expansion [default: breadth-first]\n --include-heuristic Include heuristic evidence [default: excluded]\n --evidence Include full provenance and typed detail in text\n --format <text|json> Discovery output [default: text]\n --text-budget <N> Approximate tokens in one text page [default: 8000]\n --cursor <TOKEN> Continue the same immutable semantic result (text only)\n --max-depth <N> Traversal depth [default: 2; hard maximum: 8]\n --max-seeds <N> Ranked seed count [default: 3; hard maximum: 3]\n --max-candidates <N> Ranked candidate count [default/hard maximum: 256]\n --max-nodes <N> Returned node count [default/hard maximum: 500]\n --max-edges <N> Returned edge count [default/hard maximum: 1000]\n --max-expanded-relationships <N> Examined relationships [default/hard maximum: 10000]\n --max-response-bytes <N> Serialized response bytes [default/hard maximum: 8388608]\n --timeout-ms <N> Discovery deadline in milliseconds [default/hard maximum: 30000]\n\nLegacy traversal:\n --traverse Force legacy relevance traversal\n --budget <N> Approximate tokens per page [default: 2000]\n --page <N> Result page, starting at 1 [default: 1]\n\nGraph selection:\n --graph <PATH> Read a graph JSON file\n --at <REV> Query an exact immutable realization; conflicts with --graph\n\nCompassQL:\n --cql Use CompassQL mode\n --file <PATH> Read CompassQL from a file\n --stdin Read CompassQL from standard input\n --repl Start the interactive CompassQL shell\n --param <NAME=VALUE> Bind a parameter; repeatable\n --params-file <PATH> Read parameters from JSON\n --format <table|json|jsonl> CompassQL result format [default: table]\n --output <PATH> Write CompassQL results to a file\n --timeout-ms <N> CompassQL execution timeout\n --max-rows <N> CompassQL row limit [default: 10000]\n --max-path-depth <N> CompassQL path-depth limit [default: 32]\n --max-expanded-relationships <N> CompassQL relationship expansion limit\n --max-memory-bytes <N> CompassQL memory limit [default: 268435456]\n\nExamples:\n compass query \"who calls PaymentService.charge?\"\n compass query \"authentication flow\" --direction both --scope package:auth\n compass query \"what uses charge?\" --direction incoming --context call --format json\n compass query \"authentication flow\" --evidence\n compass query \"authentication flow\" --cursor '<TOKEN>'\n compass query \"authentication flow\" --budget 8000 --page 2\n compass query --cql \"MATCH (n) RETURN n LIMIT 10\" --format json\n compass query --cql --file report.cql --params-file params.json\n\nTips:\n Direction, scope, context, and DFS compose through the bounded typed discovery contract. Scope is repeatable OR and never guesses a kind. Plain text is concise; pass --evidence for provenance. Discovery limits must be positive; values above a hard maximum are rejected rather than clamped. Legacy --traverse, --budget, and --page cannot be mixed with discovery controls.\n Historical discovery requires an immutable realization retaining trusted compass.graph/1 data."
),
page!(
"program",
Expand Down Expand Up @@ -425,7 +425,7 @@ const PAGES: &[Page] = &[
"path",
"Find the shortest relationship path between two graph nodes",
["compass path <SOURCE> <TARGET> [OPTIONS]"],
"Arguments:\n <SOURCE> Source node name or identifier\n <TARGET> Target node name or identifier\n\nOptions:\n --graph <PATH> Read a graph JSON file\n --at <REV> Use an immutable Git revision; conflicts with --graph\n\nExamples:\n compass path CheckoutHandler PaymentGateway\n compass path api route --at v1.2.0"
"Arguments:\n <SOURCE> Exact source node name, qualified name, or ID\n <TARGET> Exact target node name, qualified name, or ID\n\nOptions:\n --max-depth <N> Maximum hops examined [default: 8]\n --graph <PATH> Read a graph JSON file\n --at <REV> Use an immutable Git revision; conflicts with --graph\n\nExamples:\n compass path CheckoutHandler PaymentGateway\n compass path api route --max-depth 5 --at v1.2.0\n\nNotes:\n Resolution completes before traversal. The final path node is always the resolved target ID. Relations are weighted so structural chains beat weak shared-reference shortcuts; a close shorter-but-weaker alternative is reported separately."
),
page!(
"explain",
Expand Down
Loading
Loading