fix: tighten anonymous vertex filter for edges and file paths Edges where EITHER endpoint is a $N anonymous node are now filtered (was: only filtered when BOTH were anonymous). File paths like "crates/foo/bar.rs" are no longer considered meaningful vertices (they're just the file-level root node, not a program element). This eliminates noise like "Removed edge structural.rs -> $392" which has no semantic value for developers.

Author: Aaron Steven White
Commit 3017d114e49b92d70285c91a3239e68de32f851c
Parent: 6ce78f3ef5
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
1 file changed +27 -22
@@ -363,8 +363,8 @@ pub fn structural_diff_to_json(diff: &StructuralDiff) -> Value {
363363         .collect();
364364 
365365     // Filter raw vertex lists to only named vertices
366-    let added_vertices: Vec<&String> = diff.raw_diff.added_vertices.iter().filter(|v| has_named_segment(v)).collect();
367-    let removed_vertices: Vec<&String> = diff.raw_diff.removed_vertices.iter().filter(|v| has_named_segment(v)).collect();
366+    let added_vertices: Vec<&String> = diff.raw_diff.added_vertices.iter().filter(|v| is_meaningful_vertex(v)).collect();
367+    let removed_vertices: Vec<&String> = diff.raw_diff.removed_vertices.iter().filter(|v| is_meaningful_vertex(v)).collect();
368368 
369369     let compatible = breaking.is_empty();
370370 
@@ -381,17 +381,17 @@ pub fn structural_diff_to_json(diff: &StructuralDiff) -> Value {
381381         "addedVertices": added_vertices,
382382         "removedVertices": removed_vertices,
383383         "kindChanges": diff.raw_diff.kind_changes.iter()
384-            .filter(|kc| has_named_segment(&kc.vertex_id))
384+            .filter(|kc| is_meaningful_vertex(&kc.vertex_id))
385385             .map(|kc| json!({
386386                 "vertexId": kc.vertex_id,
387387                 "oldKind": kc.old_kind,
388388                 "newKind": kc.new_kind,
389389             })).collect::<Vec<_>>(),
390390         "addedEdges": diff.raw_diff.added_edges.iter()
391-            .filter(|e| has_named_segment(&e.src) || has_named_segment(&e.tgt))
391+            .filter(|e| is_meaningful_vertex(&e.src) || is_meaningful_vertex(&e.tgt))
392392             .map(edge_json).collect::<Vec<_>>(),
393393         "removedEdges": diff.raw_diff.removed_edges.iter()
394-            .filter(|e| has_named_segment(&e.src) || has_named_segment(&e.tgt))
394+            .filter(|e| is_meaningful_vertex(&e.src) || is_meaningful_vertex(&e.tgt))
395395             .map(edge_json).collect::<Vec<_>>(),
396396         "addedNsids": diff.raw_diff.added_nsids,
397397         "removedNsids": diff.raw_diff.removed_nsids,
@@ -403,43 +403,48 @@ pub fn structural_diff_to_json(diff: &StructuralDiff) -> Value {
403403     })
404404 }
405405 
406-/// Check if a vertex ID has at least one named (non-$N, non-file-path) segment.
407-fn has_named_segment(id: &str) -> bool {
406+/// Check if a vertex ID represents a meaningful named program element
407+/// (not an anonymous AST node like $N, and not just a file path).
408+fn is_meaningful_vertex(id: &str) -> bool {
408409     if id.contains("::") {
410+        // Tree-sitter style: "file.rs::FunctionName::field"
411+        // Must have at least one named segment that's not a file path
409412         id.split("::").any(|s| {
410413             !s.starts_with('$') && !s.is_empty() && !s.contains('/') && !s.contains('.')
411414         })
412-    } else if id.contains(':') {
413-        // Lexicon-style IDs are always named
415+    } else if id.contains(':') && !id.contains("::") {
416+        // Lexicon-style: "dev.cospan.repo:body.field" - always meaningful
414417         true
415418     } else {
416-        !id.starts_with('$') && !id.is_empty()
419+        // Bare ID: only meaningful if not $N and not a file path
420+        !id.starts_with('$') && !id.is_empty() && !id.contains('/') && !id.contains('.')
417421     }
418422 }
419423 
420-/// Check if a breaking change references only anonymous vertices.
424+/// Check if a breaking change should be hidden (references anonymous/internal vertices).
421425 fn is_anonymous_change_breaking(b: &BreakingChange) -> bool {
422426     match b {
423-        BreakingChange::RemovedVertex { vertex_id } => !has_named_segment(vertex_id),
427+        BreakingChange::RemovedVertex { vertex_id } => !is_meaningful_vertex(vertex_id),
424428         BreakingChange::RemovedEdge { src, tgt, .. } => {
425-            !has_named_segment(src) && !has_named_segment(tgt)
429+            // Filter if either end is anonymous (an edge from a file to $392 is noise)
430+            !is_meaningful_vertex(src) || !is_meaningful_vertex(tgt)
426431         }
427-        BreakingChange::KindChanged { vertex_id, .. } => !has_named_segment(vertex_id),
428-        BreakingChange::ConstraintTightened { vertex_id, .. } => !has_named_segment(vertex_id),
429-        BreakingChange::ConstraintAdded { vertex_id, .. } => !has_named_segment(vertex_id),
430-        _ => false, // keep other change types
432+        BreakingChange::KindChanged { vertex_id, .. } => !is_meaningful_vertex(vertex_id),
433+        BreakingChange::ConstraintTightened { vertex_id, .. } => !is_meaningful_vertex(vertex_id),
434+        BreakingChange::ConstraintAdded { vertex_id, .. } => !is_meaningful_vertex(vertex_id),
435+        _ => false,
431436     }
432437 }
433438 
434-/// Check if a non-breaking change references only anonymous vertices.
439+/// Check if a non-breaking change should be hidden.
435440 fn is_anonymous_change_non_breaking(nb: &NonBreakingChange) -> bool {
436441     match nb {
437-        NonBreakingChange::AddedVertex { vertex_id } => !has_named_segment(vertex_id),
442+        NonBreakingChange::AddedVertex { vertex_id } => !is_meaningful_vertex(vertex_id),
438443         NonBreakingChange::AddedEdge { src, tgt, .. } => {
439-            !has_named_segment(src) && !has_named_segment(tgt)
444+            !is_meaningful_vertex(src) || !is_meaningful_vertex(tgt)
440445         }
441-        NonBreakingChange::ConstraintRelaxed { vertex_id, .. } => !has_named_segment(vertex_id),
442-        NonBreakingChange::ConstraintRemoved { vertex_id, .. } => !has_named_segment(vertex_id),
446+        NonBreakingChange::ConstraintRelaxed { vertex_id, .. } => !is_meaningful_vertex(vertex_id),
447+        NonBreakingChange::ConstraintRemoved { vertex_id, .. } => !is_meaningful_vertex(vertex_id),
443448         _ => false,
444449     }
445450 }
cospan · schematic version control on atproto built on AT Protocol