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
7 changes: 7 additions & 0 deletions .claude/board/PR_ARC_INVENTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 2026-11-02 — lance-graph PR #1920 (merged `5b967b130`, branch `claude/clone-repositories-71a5sw`) — D-CODEGRAPH-0 slice B record; rev2 fixture deletes `area_dyn`

- **Added:** `fixtures/codegraph/rev2` also deletes `cg_lib::area_dyn` and drops its call from `cg_app`, so a refresh that skips the dependent leaves one dangling edge; the slice B section of the gap-map entry; STATUS_BOARD D-CODEGRAPH-0-B LANDED for the offline store.
- **Companion:** ruff PR #130 (merged `95be2e480b`, offline store in `ruff_charon`) and #131 (merged `2033be48fe`, four Codex findings on #129/#130).
- **Deferred:** residency in lance-graph's SoA.
- **Confidence:** incremental refresh byte-identical to a clean import; five disable runs red.

## 2026-11-02 — lance-graph PR #1918 (merged `c11e4262e`, branch `claude/clone-repositories-71a5sw`, head `c0f81a2a8`) — D-CODEGRAPH-0 gap map and slice A evidence; D-DQM M1 timing arms; dt64/xlog CI lines

- **Added:**
Expand Down
2 changes: 1 addition & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Plan: [d-codegraph-0-resident-codegraph-v1.md](../plans/d-codegraph-0-resident-c
|---|---|---|---|
| **D-CODEGRAPH-0-A** | ruff `ruff_charon`: LLBC → Kythe facts with a witness status per call, for one real crate | SHIPPED: ruff #129 (merged `9913e90946`), lance-graph #1918 (merged `c11e4262e`): calls byte-identical to `llbc_calls.py` on the fixture and `mask-risc`; receipt conserved (16,149); Kythe `/xrefs` 3/3 call pins | rust-analyzer oracle (`sag-ra-probe GRAPH=1`) and rustc on `fixtures/codegraph`; `POSSIBLE` never counted as a confirmed caller |
| **D-CODEGRAPH-0-B** | resident rows via the ruff sink; per-file digests; incremental invalidation | LANDED (offline form): ruff #130 (merged `95be2e480b`; offline store in `ruff_charon`; `RefinedTruthSink` not reused, typed to R2IL). Incremental == clean on the fixture, byte-identical; skipped dependent leaves exactly one dangling edge. Follow-up fixes in ruff #131 (open). Lance-graph SoA residency still open | clean import == incremental import |
| **D-CODEGRAPH-0-C** | callers / callees / impact / diagnostics; tiled closure; body → R2IL design check | QUEUED | each guard disable-verified; unused import proven only without macros, traits, globs, re-exports |
| **D-CODEGRAPH-0-C** | callers / callees / impact / diagnostics; tiled closure; body → R2IL design check | IN PR: ruff #132 (merged `ed8fd4194f`, rows v2) + `crates/lance-graph-codegraph` (queries as confirmed / possible / unknown masks over the xlog cube; dead function, unresolved call, dangling call; unused import NOT_RUN). 27 tests, 12 disable runs red. Tiled closure deferred: refusals `ClosureWidth` (> 64) and `TooMany` (> 256) are surfaced as typed UNKNOWN | each guard disable-verified; unused import proven only without macros, traits, globs, re-exports |
| **D-CODEGRAPH-0-D** | local stdio MCP over C | QUEUED | only after B and C gates |
| **D-CODEGRAPH-0-E** | token measurement with and without the Codegraph | QUEUED | same revision, questions, model; nothing claimed before measured |

Expand Down
17 changes: 17 additions & 0 deletions .claude/board/entries/2026-11-02-d-codegraph-0-gap-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,20 @@ Five disable runs each turn their own tests red.
G5 (resident, incremental store) is closed for the offline form. Still open: residency in lance-graph's SoA (a lance-graph-side reader of these artifacts), then the queries of slice C.

ruff #131 (open) corrects four Codex findings on #129/#130: a body Charon left unstructured is still harvested, a call through a known trait impl resolves to the impl's method or the trait default, temporary files are unique per call, and a temporary name left by an interrupted import is skipped instead of aborting the import.

## Slice C: queries and diagnostics (ruff #132 merged `ed8fd4194f`; `crates/lance-graph-codegraph`)

Rows v2 in ruff adds the facts the queries need: `PUBLIC`, `IMPLEMENTS` (impl method → `Trait::method`), `SYNTHETIC`, `REFERENCES` (function pointers), a `CRATE … COMPLETE|PARTIAL` row, and `MAY_DISPATCH` naming the method. Charon marks trait-impl methods public, so cg_lib rev1 has 9 `PUBLIC` rows for its 7 `pub fn`s.

`lance-graph-codegraph` reads the artifacts by column name (no dependency on ruff_charon) and builds one xlog cube over 15 positions (rev1). Answers are masks graded confirmed / possible / unknown; `MAY_DISPATCH` is never confirmed, and a dyn or pointer call is unknown. On the fixture:
- `impact(double)` rev1: confirmed {Square::area, run}, possible {area_of}, unknown {area_dyn}; rev2: confirmed 4 including the 2-hop `checked`.
- `callees(run)` rev2: 3 callees, 4 sites.
- Dead function: rev1 `private_unused`, fnref `unused_private` (the pointer-only `helper` stays silent); rustc's `dead_code` agrees on both.
- Dangling call: mixed store (cg_lib rev2 + cg_app rev1) exactly `run → area_dyn`, status stale; rustc reports E0425 at `cg_app/src/lib.rs:7` on the same tree.
- Unused import: NOT_RUN, reason NO_IMPORT_FACTS (LLBC has no `use` items).
- Release timing, ns per call: callers 5103, callees 3643, references 184, impact 387; open 166 µs and first closure 5 µs (cold, reported, not gated).
- `cube().stats().rows_materialized == 0` after the whole suite.

Twelve disable runs each turn their own test red. The one that first stayed green (dropping the impl-method exclusion from the dead-function check) is now covered by a synthetic store: Charon marks every impl method of a public trait public, so the fixtures never reach that case.

Open: tiled closure for more than 64 positions and multi-tile cubes for more than 256 (mask-risc has 459 functions, so every cube query on it is refused today, as a typed UNKNOWN); import facts for unused imports; body → R2IL (G4, G7).
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ members = [
"crates/lance-graph-hydrate",
]
exclude = [
"crates/lance-graph-codegraph",
"crates/lance-graph-sap",
# D-HOLO-H14c Lance 13 masked-I/O probe: own narrow dependency set.
"crates/holo-lance-masked-io-probe",
Expand Down
16 changes: 16 additions & 0 deletions crates/lance-graph-codegraph/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "lance-graph-codegraph"
version = "0.1.0"
edition = "2021"
publish = false
description = "Codegraph queries and structural diagnostics (D-CODEGRAPH-0 slice C) over the ruff_charon store artifacts, folded through the xlog cube; no dependency on ruff_charon."

# Own workspace root, excluded from the lance-graph workspace (as sag-ra-probe).
[workspace]

[dependencies]
lance-graph-xlog = { path = "../lance-graph-xlog" }
lance-graph-dt64 = { path = "../lance-graph-dt64" }
lance-graph-quack = { path = "../lance-graph-quack" }
lance-graph-mask-risc = { path = "../lance-graph-mask-risc" }
ndarray = { git = "https://github.com/AdaWorldAPI/ndarray", branch = "master", default-features = false, features = ["std"] }
61 changes: 61 additions & 0 deletions crates/lance-graph-codegraph/examples/timing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Per-call timing of the four queries on the rev1 fixture, in nanoseconds.
//! Cold costs are reported in microseconds, not gated.

use std::hint::black_box;
use std::path::PathBuf;
use std::time::Instant;

use lance_graph_codegraph::Codegraph;

const ITERS: u32 = 10_000;

fn dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/rev1")
}

fn per_call_ns(mut f: impl FnMut()) -> f64 {
for _ in 0..ITERS {
f();
}
let t = Instant::now();
for _ in 0..ITERS {
f();
}
t.elapsed().as_nanos() as f64 / f64::from(ITERS)
}

fn main() {
let t = Instant::now();
let mut g = Codegraph::open(&dir()).expect("rev1 opens");
println!(
"open: {} us (cold, reported, not gated)",
t.elapsed().as_micros()
);

let double = g.pos("cg_lib::util::double").expect("double");
let run = g.pos("cg_app::run").expect("run");

let t = Instant::now();
black_box(g.impact(double).expect("impact"));
println!(
"first closure build (impact): {} us (cold, reported, not gated)",
t.elapsed().as_micros()
);

let ns = per_call_ns(|| {
black_box(g.callers(double).unwrap());
});
println!("callers: {ns:.1} ns/call");
let ns = per_call_ns(|| {
black_box(g.callees(run).unwrap());
});
println!("callees: {ns:.1} ns/call");
let ns = per_call_ns(|| {
black_box(g.references(double));
});
println!("references: {ns:.1} ns/call");
let ns = per_call_ns(|| {
black_box(g.impact(double).unwrap());
});
println!("impact: {ns:.1} ns/call");
}
128 changes: 128 additions & 0 deletions crates/lance-graph-codegraph/src/artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! The reader for `*.rows.tsv` artifacts (version 2).
//!
//! Lines stream straight into column lanes: one push per field per line, no
//! per-row struct. Columns are located by name from the `#schema` line, so a
//! file with permuted columns reads identically.

use std::fs;
use std::path::Path;

use crate::graph::IntakeRefusal;

/// The only artifact version this reader accepts.
pub const ROWS_VERSION: u32 = 2;

/// Columns every artifact must carry.
const REQUIRED: [&str; 7] = [
"predicate",
"subject",
"object",
"grade",
"file",
"start",
"end",
];

/// Marks an absent (`-`) span bound in [`Lanes::start`] / [`Lanes::end`].
pub const NO_SPAN: u32 = u32::MAX;

/// All rows of a directory as parallel column lanes; index `i` of every lane
/// is row `i`. Invariant: all lanes have equal length.
#[derive(Debug, Default)]
pub struct Lanes {
/// Relation name of each row.
pub predicate: Vec<String>,
/// Subject symbol of each row.
pub subject: Vec<String>,
/// Object text of each row.
pub object: Vec<String>,
/// Grade (provenance) of each row.
pub grade: Vec<String>,
/// Source file of each row, `-` when none.
pub file: Vec<String>,
/// Span start, [`NO_SPAN`] when `-`.
pub start: Vec<u32>,
/// Span end, [`NO_SPAN`] when `-`.
pub end: Vec<u32>,
}

/// Read every `*.rows.tsv` in `dir`, sorted by file name so the row order is
/// deterministic.
pub fn read_dir(dir: &Path) -> Result<Lanes, IntakeRefusal> {
let mut paths = Vec::new();
for entry in fs::read_dir(dir).map_err(IntakeRefusal::Io)? {
let path = entry.map_err(IntakeRefusal::Io)?.path();
let is_rows = path
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.ends_with(".rows.tsv"));
if is_rows {
paths.push(path);
}
}
paths.sort();
let mut lanes = Lanes::default();
for p in &paths {
let text = fs::read_to_string(p).map_err(IntakeRefusal::Io)?;
read_text(&text, &mut lanes)?;
}
Ok(lanes)
}

fn number(field: &str) -> Option<u32> {
if field == "-" {
Some(NO_SPAN)
} else {
field.parse().ok()
}
}

/// Append one file's rows to `lanes`.
pub fn read_text(text: &str, lanes: &mut Lanes) -> Result<(), IntakeRefusal> {
let mut lines = text.lines();
let found = lines
.next()
.and_then(|l| l.strip_prefix("#version "))
.and_then(|v| v.trim().parse::<u32>().ok())
.unwrap_or(0);
if found != ROWS_VERSION {
return Err(IntakeRefusal::Version { found });
}
let schema = lines
.next()
.and_then(|l| l.strip_prefix("#schema "))
.ok_or_else(|| IntakeRefusal::Schema {
missing: "#schema".to_string(),
})?;
let names: Vec<&str> = schema.split('\t').collect();
let mut at = [0usize; 7];
for (slot, want) in at.iter_mut().zip(REQUIRED) {
*slot = names
.iter()
.position(|n| *n == want)
.ok_or_else(|| IntakeRefusal::Schema {
missing: want.to_string(),
})?;
}
for (i, line) in lines.enumerate() {
// Lines 1 and 2 were the header; data starts at line 3.
let lineno = i + 3;
if line.is_empty() {
continue;
}
let f: Vec<&str> = line.split('\t').collect();
if f.len() != names.len() {
return Err(IntakeRefusal::Arity { line: lineno });
}
let start = number(f[at[5]]).ok_or(IntakeRefusal::Arity { line: lineno })?;
let end = number(f[at[6]]).ok_or(IntakeRefusal::Arity { line: lineno })?;
lanes.predicate.push(f[at[0]].to_string());
lanes.subject.push(f[at[1]].to_string());
lanes.object.push(f[at[2]].to_string());
lanes.grade.push(f[at[3]].to_string());
lanes.file.push(f[at[4]].to_string());
lanes.start.push(start);
lanes.end.push(end);
}
Ok(())
}
Loading
Loading