Codebase Knowledge Base (11): Cross-Repo Scenarios — When One Service Calls Another

A single-repo knowledge base can answer 'where is this function, who calls it, how should I change it.' But real engineering systems are multiple repositories collaborating over HTTP, message queues, and gRPC. Starting from a real cross-repo-intelligence run — LightRAG × graphrag returns 0 edges — this article explains why that result is correct, and what cross-repo analysis actually finds: integration relationships, not functional similarity.

·8 min read·AI Engineering

An Experiment That Returned Zero

Running cross-repo-intelligence across LightRAG and graphrag:

index_repository(
    repo_path="/path/to/LightRAG",
    mode="cross-repo-intelligence",
    target_projects=["mnt-hdd-...-graphrag"]
)

Output:

status: success
cross_http_calls: 0
cross_async_calls: 0
cross_channel: 0
cross_grpc_calls: 0
total_cross_edges: 0
elapsed_ms: 109

Zero cross-repo edges.

The first instinct is 'the tool found nothing — something failed.' That's wrong. This result is entirely correct, and it's highly informative.

LightRAG and graphrag are two open-source frameworks that do similar things — both graph-based RAG — but they are parallel alternatives, not an integrated system. No LightRAG code calls graphrag's API, and vice versa. cross-repo-intelligence finds integration relationships, not functional similarity. There was never an integration relationship between them; returning 0 is the right answer.

This matters because it draws a clear boundary: cross-repo analysis solves a completely different class of problem than single-repo analysis.


Single-Repo vs Cross-Repo: Two Different Problems

Looking back at the first ten articles, all the questions a single-repo knowledge base can answer live within one repository boundary:

  • 'Where is this function?' (symbol path)
  • 'Who calls it?' (graph path, inbound)
  • 'What does it call?' (graph path, outbound)
  • 'Which functions frequently change together?' (FILE_CHANGES_WITH)

These answers are all in the same codebase. The call graph is complete. BFS from any node works.

But when system scope expands across multiple repositories, a class of questions appears that single-repo analysis physically cannot answer:

  • 'Which interfaces of Service B does Service A call?'
  • 'If I modify Service B's /query endpoint, which upstream services are affected?'
  • 'Service C changed the message format it sends to Service D via message queue — can D detect this?'
  • 'In the entire microservice network, who are the central nodes, who are the islands?'

These answers span repository boundaries. A single-repo call graph stops dead at the service boundary — the graph becomes a map with 'dangling terminal nodes' that call some external URL, but don't know who handles that URL.

cross-repo-intelligence connects those dangling nodes.


How Cross-Repo Edges Are Discovered

The core logic of cross-repo-intelligence mode: match Route nodes against HTTP_CALLS nodes across indexed projects.

Concretely:

  1. Discover server-side routes: Scan all indexed projects for Route nodes (HTTP endpoint definitions). Build a path → project routing table.

  2. Discover client-side calls: Scan HTTP_CALLS edges (HTTP requests in the code). Extract the target URLs or path patterns.

  3. Path matching: For each HTTP_CALLS edge, look for a matching Route in other projects. When found, create a CROSS_HTTP_CALLS edge that crosses the repository boundary, connecting the caller to the callee.

The same logic applies to:

  • CROSS_ASYNC_CALLS: message queues (Kafka topic names, RabbitMQ exchanges, etc.)
  • CROSS_GRPC_CALLS: gRPC service/method names
  • CROSS_CHANNEL: other named channels (WebSocket channels, Redis pub/sub keys)

This explains why LightRAG × graphrag returns 0: no LightRAG code sends HTTP requests to graphrag's routes, and graphrag doesn't call LightRAG's API. The tool didn't fail — it correctly reported that there is no integration relationship between these two systems.


What Each System's Route Profile Actually Looks Like

Even without cross-repo edges, examining each system's routes reveals their roles immediately.

LightRAG: REST API server

Route nodes (lightrag/api/routers/):
  GET  /query
  POST /query
  GET  /query/stream
  POST /query/stream
  GET  /health
  POST /login
  GET  /auth-status
  GET  /graph/label/list
  POST /documents/paginated
  ...

LightRAG exposes a full HTTP service interface — document management, querying, graph browsing, health checks. From the cross-repo perspective, it's a callee: if another service sends requests to http://lightrag-host/query, that request becomes one end of a cross-repo edge.

The corresponding code structure: create_query_routes and create_document_routes are massive route registration functions (1,566 lines, complexity 118, cognitive complexity 266) — among the hardest functions in the project, because they handle all the HTTP boundary cases.

graphrag: LLM client

Route nodes (graphrag/):
  PATCH  /            (API root — to LiteLLM)
  external: https://litellm.ai
  external: https://raw.githubusercontent.com/.../cspell.schema.json

graphrag isn't a service — it's a command-line tool + Python SDK. Its 'routes' are HTTP calls it makes to external LLM services (LiteLLM) and GitHub, not endpoints it exposes. From the cross-repo perspective, it's a pure caller — if you deploy both graphrag and LightRAG in the same system, graphrag might call LiteLLM (creating CROSS_HTTP_CALLS edges), but graphrag won't call LightRAG and LightRAG won't call graphrag.

This role difference is immediately visible from the route profile: one has real REST API routes, the other only has outbound HTTP client calls.


Where Cross-Repo Analysis Has Real Engineering Value

If LightRAG × graphrag has no cross-repo edges, what kind of system does?

A typical microservice architecture:

Frontend App
  └─ CROSS_HTTP_CALLS → API Gateway (gateway-service)
                              └─ CROSS_HTTP_CALLS → User Service
                              └─ CROSS_HTTP_CALLS → Auth Service
                              └─ CROSS_HTTP_CALLS → Order Service
                                    └─ CROSS_ASYNC_CALLS → Kafka: order.created
                                                                └─ CROSS_ASYNC_CALLS → Notification Service

In this architecture, if User Service's GET /users/{id} interface is being modified (say, a returned field is being removed), cross-repo analysis tells you which services have CROSS_HTTP_CALLS edges pointing to that route — and need to be updated in sync. Pure single-repo analysis can't answer this at all.

cross_service mode trace_path:

trace_path(
    function_name="get_user_by_id",
    project="user-service",
    mode="cross_service",
    depth=3
)

This call follows HTTP_CALLS → CROSS_HTTP_CALLS → Route edges across service boundaries, returning the full cross-service call chain — from some frontend handler all the way down to a database query in User Service.


Three Practical Cross-Repo Use Cases

Use case 1: Service dependency map

After indexing all microservice projects, query the cross-repo call graph:

MATCH (a)-[r:CROSS_HTTP_CALLS]->(b)
RETURN a.file_path, r.path, b.file_path

The result is the entire system's service dependency map. Which services are 'hubs' (called by many others), which are 'islands' (no cross-repo calls), whether there are circular dependencies — one query surfaces all of it.

Use case 2: Interface change impact assessment

# 1. Find the interface definition
search_code("GET /api/v2/payments", project="payment-service")
 
# 2. Find cross-repo callers
trace_path("get_payment", mode="cross_service", direction="inbound")
→ returns all upstream services with CROSS_HTTP_CALLS edges to this route

Before modifying /api/v2/payments, know which services depend on it. Without cross-repo analysis, this requires manually grep-ing that URL across every repository in the organization — a process with frequent misses.

Use case 3: Message contract tracing

If services communicate via Kafka:

MATCH (producer)-[r:CROSS_ASYNC_CALLS]->(consumer)
WHERE r.channel = "order.completed"
RETURN producer.file_path, consumer.file_path

Find all producers and consumers of the order.completed message. When the message schema needs to change, the full blast radius is visible immediately.


Zero Cross-Repo Edges Still Has Value: Interface Design Comparison

Back to LightRAG and graphrag — no cross-repo edges, but both indexes exist. Holding two indexed projects simultaneously enables something useful even without integration edges: interface design comparison.

LightRAG's query interface:

# LightRAG
async def aquery(self, query: str, param: QueryParam) -> str

graphrag's query interface:

# graphrag
async def local_search(
    config: GraphRagConfig,
    entities: pd.DataFrame,
    communities: pd.DataFrame,
    community_reports: pd.DataFrame,
    text_units: pd.DataFrame,
    relationships: pd.DataFrame,
    covariates: pd.DataFrame | None,
    community_level: int,
    response_type: str,
    query: str,
) -> tuple[str | dict, str | list[pd.DataFrame] | dict[str, pd.DataFrame]]

Two frameworks doing the same category of thing, with radically different interface philosophies. LightRAG encapsulates all configuration in QueryParam (designed for a service runtime). graphrag expects the caller to manage every DataFrame directly (designed for a batch analytics pipeline).

These interface designs reflect completely different target use cases — and this kind of cross-project interface comparison is one of the most valuable analyses for technology selection decisions. It's also a side-effect of having both projects indexed at the same time, without requiring any cross-repo edges.


Summary

Cross-repo analysis isn't 'single-repo analysis made bigger.' It solves a genuinely new class of problem: connecting knowledge at service boundaries.

Three core conclusions:

  1. Zero cross-repo edges is a meaningful answer: it says two systems have no integration relationship — not that the analysis failed. Don't conflate 'the tool found something' with 'the tool worked correctly.'

  2. Cross-repo analysis works by route matching: it matches Route nodes exposed by one service against HTTP_CALLS / ASYNC_CALLS edges from another. That's what it can find, and only that. Functional similarity, code style comparison, duplicate logic detection — those are different tools' jobs.

  3. Projects with no cross-repo edges still benefit from joint indexing: a cross-repo knowledge base lets you query multiple projects simultaneously. Even without call edges, parallel indexes have analytical value — like interface design comparison.

Next article: we turn to knowledge base evaluation — how do you know your knowledge base is 'good enough'? What metrics matter, how to design evaluation datasets, and what production systems should track when Recall@5 is no longer the only metric that matters.


Follow me at dongqi.dev for more LLM engineering content.

At PrimeSkills, we help engineering teams build cross-repo knowledge bases for multi-service architectures — including service dependency graph construction and cross-service interface change impact assessment. If your team manages multiple microservices, reach out to discuss how to operationalize cross-repo knowledge bases.

primeskills.dev