Repository navigation
[Feature Request] Cross-domain edge generation: AST↔semantic subgraph bridging (+ alignment solution) #1254
Description
Problem Statement
When graphify builds a knowledge graph from a mixed codebase (source code + documentation), it produces two disconnected subgraphs with zero native cross-domain edges:
- AST subgraph (code): densely connected via
import,call,containsedges - Semantic subgraph (docs): densely connected via
references,implementsedges - Cross-domain (docs↔code): 0 edges — the gap
This means graphify cannot answer fundamental questions like:
- "Which code module implements this design spec?"
- "If I change this code, which documents are affected?"
- "Which PRD requirement does this class satisfy?"
Evidence from real-world project graph
| Metric | Value |
|---|---|
| Total nodes | 1,800+ |
| Total edges | 3,000+ |
| Cross-domain edges (doc↔code) | 0 (0.0%) |
| Communities | 200+ |
| Bridging communities | 0 between doc and code domains |
After applying our post-processing alignment script (described below):
| Metric | Before | After |
|---|---|---|
| Cross-domain edges | 0 | 144 |
| Spec→Code coverage | 0% | 90% |
| Trust score A/B grade | — | 100% |
Root Cause Analysis
Three structural limitations in graphify prevent cross-domain edge generation:
1. Node IDs are path-based, not content-based
Source: graphify/extract.py:65-78 — _make_id() function (line numbers based on graphify v0.5.x)
def _make_id(parts: list[str]) -> str:
"""Generate deterministic ID from path components."""
return "_".join(p.lower().replace(" ", "").replace("-", "")
for p in parts if p)AST node IDs are derived from file path + symbol name (e.g., auth_service_authservice). Semantic node IDs are generated by LLM subagents and follow no deterministic pattern (e.g., concept_Authentication_Module, auth_overview). These two ID spaces never collide, so graphify's ID-based merge cannot bridge them.
2. Merge mechanisms require exact matches
Source: graphify/build.py:107-273 — build_from_json() (line numbers based on graphify v0.5.x)
Graphify's three merge mechanisms:
- ID collision — requires exact string match between AST and semantic node IDs
- Ghost-duplicate (bug: AST and semantic extractors generate different node IDs for the same symbol → god nodes duplicated, cluster cohesion degraded #1145) — requires
(basename, label)exact match across domains - Label dedup — removes duplicate nodes, doesn't create cross-domain edges
None of these can match auth_service_authservice (AST) with Authentication Module (semantic) — they share no structural similarity in ID or label space.
3. Semantic subagents are chunk-isolated
Source: Related to issue #65
Semantic extraction dispatches parallel subagents per chunk (20-25 files). Code files and their related documentation files are in different directories and always land in different chunks. Each subagent only sees its own chunk — it cannot create edges to entities in other chunks.
Proposed Solution
We implemented and validated a three-tier post-processing alignment pipeline that bridges the two subgraphs:
Tier 0 — Deterministic matching (confidence: 1.0)
-
Structured ID annotations: Code modules carry
# IMPORTANT: M-XX module_name | E-XX | Scene-Ncomments. graphify's AST rationale extractor (extract.py:3483) captures these as rationale nodes. The post-processor reads module IDs from rationale labels and matches them to documentation containing the same IDs. -
Exact class name matching: When a doc concept label is an exact class name (e.g.,
AuthService), match to the AST node with the same class name. -
Path pattern mapping: Map documentation file paths to code module directories using configurable patterns (e.g.,
docs/spec/01-auth.md→src/auth/).
Tier 1 — Controlled fuzzy matching (confidence: 0.80-0.90)
-
Keyword-to-service matching: Within an identified module, map concept keywords to specific service classes. Critical safety measure: all fuzzy strategies search WITHIN the identified module only, preventing cross-module false positives.
-
Cross-validation: Two independent strategies must agree before an edge is written at the AUTO threshold. Single-strategy matches go through CROSSVAL review.
Tier 2 — Free text (report only, confidence: <0.70)
- Token overlap: Jaccard similarity on normalized tokens, with strict thresholds. Report-only by default.
Quality audit
Every aligned edge receives a TrustScore (0-100):
- Match Score (50%): based on strategy confidence
- Topology Score (20%): cross-module consistency
- Context Score (15%): path module alignment
- Stability Score (15%): node existence verification
Grades: A (≥85), B (≥70), C (50-69), D (<50 → auto-deleted)
Results
| Strategy | Edges | Grade |
|---|---|---|
| S1: ID annotation | 35 | A (100%) |
| S2: Exact class | 12 | A (100%) |
| S3: Path mapping | 74 | A/B |
| S4-S7: Fuzzy | 97 | A/B |
| S8: Token overlap | 0 | (placeholder) |
| Total | 218 | A:38, B:180 |
Note: The 218 total aligned edges include 144 cross-domain (doc↔code) edges and 74 intra-domain (spec↔issue) alignment edges. The 144 cross-domain count is the primary metric reported in the before/after comparison above.
Recommended graphify Enhancement
The ideal long-term fix is a Global Cross-Domain Alignment Pass (as discussed in #65):
- After merging all chunks, collect node labels from both subgraphs
- Apply deterministic matching (normalized label suffix, class name)
- Apply controlled fuzzy matching (token overlap with module isolation)
- Add matched edges with confidence scores and audit trails
This could be implemented as:
- A new
graphify.alignmodule in graphify core, OR - A
--post-alignflag that runs the alignment afterbuild, OR - A built-in plugin that projects can configure via
.graphify.yaml
Configuration example
# .graphify-align.yaml — project-level alignment configuration
modules:
auth:
source_path: "src/auth/"
doc_patterns: ["docs/spec/*-auth*", "docs/requirements/*-auth*"]
service_class: "AuthService"
strategies:
- id_annotation # reads # IMPORTANT: comments
- exact_class # exact class name matching
- path_mapping # doc path → code module
- keyword_service # module-isolated keyword matchingUpstream References
This issue consolidates and provides evidence for:
- Semantic Extraction Chunking May Miss Cross-Chunk Relationships (No Global Linking Pass) #65 — Cross-chunk semantic edges lost due to chunk isolation
- Semantic layer is mostly disconnected from the AST graph #198 — Semantic layer and AST layer produce independent subgraphs
- Semantic extraction node IDs don't align with AST node IDs, causing edge loss during merge #390 — Node ID mismatch between AST and semantic extraction
- AST and semantic extractors generate mismatched node IDs for the same file, splitting it into two disconnected nodes #1033 — AST uses
Path.stem, semantics use relative path prefix → duplicate nodes - graphify update / post-commit hook drops code-file semantic nodes and is then blocked by to_json safety check #653 —
--updatesilently drops semantic nodes - bug: AST and semantic extractors generate different node IDs for the same symbol → god nodes duplicated, cluster cohesion degraded #1145 — Ghost-duplicate merge mechanism (requires exact
(basename, label)match)
What We Can Contribute
We are prepared to contribute:
- Post-processing alignment script (~600 lines, MIT license) — validated on a real-world project with 1,800+ node graph
- Test suite (37 unit tests) — covering all strategies and edge cases
- Best practice guide for structured ID annotations in codebases using graphify
- Real-world validation data — before/after comparison on a mid-size project
Please let us know the preferred contribution format (core module, plugin, or standalone tool).
Activity
Fion9506 commentedon Jun 11, 2026
Our working solution: After hitting this gap in our project, we built a post-processing alignment pipeline that bridges the two subgraphs with claude code. Here's the full writeup in case it helps others or informs a future graphify feature:
Our Solution: Structured ID Traceability for Cross-Domain Alignment
Table of Contents
1. Problem Analysis
The Gap
graphify builds knowledge graphs from mixed codebases using two extraction pipelines:
import,call,containsreferences,implementsThese two pipelines produce structurally isolated subgraphs. The root cause chain:
(Line numbers based on graphify v0.5.x)
Evidence
This is not a bug in extraction quality — both subgraphs are rich and accurate within their domain. The gap is purely in the bridging mechanism.
2. Solution Overview
A three-component architecture that bridges the two subgraphs without modifying graphify:
3. Step 1: Structured ID Annotations
Why
# IMPORTANT:prefix?graphify's AST rationale extractor (
extract.py:3483) only captures comments with specific prefixes (line numbers based on graphify v0.5.x):Using
# IMPORTANT:ensures the annotation is captured as a rationale node in the graph, making it available for post-processing.Annotation format
Place in each module's
__init__.py, after the module docstring, before the first import:Format:
# IMPORTANT: <module-id> <module-name> | <entity-ids> | <scene-ids>M-01M-01throughM-NNauthsrc/auth/directoryE-01,E-02E-01throughE-NNScene-1Scene-1throughScene-NCritical constraint: 80-character label truncation
graphify truncates rationale labels to 80 characters (
extract.py:3544: label = text[:80], based on graphify v0.5.x). Your annotation MUST fit within 80 characters.Good (37 chars):
# IMPORTANT: M-01 auth | E-01,E-02 | Scene-1Bad (>80 chars, will be truncated):
# IMPORTANT: M-01 authentication-module | E-01-User,E-02-Session,E-03-Token | Scene-1-login-flowAnnotation schedule
E-XXlist in the annotation4. Step 2: Post-Processing Alignment Script
Three-tier safety model
The alignment script runs 8 strategies organized into 3 tiers by risk level:
Module isolation: the critical safety measure
All fuzzy strategies (Tier 1-2) search WITHIN the identified module only.
Without module isolation, a concept like "storage module" in a document about the auth system could match
StorageServicein the storage module — a cross-module false positive.With module isolation:
Lesson learned: Before adding module isolation, we observed false positives where storage documentation matched unrelated service classes. After isolation: zero false positives in 218 aligned edges.
Cross-validation for Tier 1
When a single fuzzy strategy produces a match, the confidence is 0.80 (CROSSVAL threshold). When two independent strategies agree on the same match:
This promotes cross-validated matches to the AUTO threshold (≥0.90).
Idempotency
The script is safe to run multiple times:
_aligned: truetag_alignededges are strippedConfiguration tables
5. Step 3: Quality Audit
TrustScore (0-100)
Every aligned edge receives a composite trust score:
Grade thresholds
Write thresholds by tier
Audit report output
{ "total_edges": 218, "total_before_audit": 218, "d_grade_removed": 0, "by_grade": {"A": 38, "B": 180, "C": 0, "D": 0}, "by_method": { "s1_id_annotation": 35, "s2_exact_class": 12, "s3_techspec_path": 74, "s4_keyword_service": 45, "s5_prd_scene": 18, "s7_issue_crossval": 14 }, "average_trust_score": 82.4 }6. Results
Before alignment
After alignment
Validation queries
Key metrics
7. Design Decisions
Why post-processing instead of modifying graphify source?
The post-processing approach is the safest short-term solution. If graphify adopts a Global Linking Pass (as discussed in #65), the script can gracefully retire.
Why
# IMPORTANT:prefix?graphify's AST rationale extractor only captures specific prefixes.
# IMPORTANT:is semantically appropriate ("this annotation is important metadata") and guaranteed to be captured.Why is module isolation critical?
Without module isolation, global keyword matching produces false positives:
"storage"in auth documentation matchesStorageServicein the storage module"session"in storage documentation matchesSessionHandlerin the auth moduleModule isolation restricts search to 5-15 candidate nodes per module, making false positives nearly impossible.
Why TrustScore weights at 50/20/15/15?
Initial weights (35/25/20/20) underweighted the strategy confidence. A weak edge (confidence 0.5) could score 62.5 — passing the C-grade threshold — if topology/context/stability were all perfect. Rebalancing to 50% Match Score ensures that a low-confidence match is properly penalized regardless of other factors.
8. Reference Implementation
Project structure
Workflow
Integration with graphify update cycle
Appendix A: Upstream Issues
Appendix B: Glossary
# IMPORTANT:,# NOTE:etc. commentsValidated on a real-world project with 1,800+ nodes, 3,000+ edges, 200+ communities. Results: 0→144 cross-domain edges, 0→90% spec coverage, 100% A/B grade quality.