The contracts, package dependencies, and co-changes repowise extracts
(Cross-repo intelligence) are each a flat list. The workspace layer folds them into a single
normalized system graph, the one structure every cross-repo view
reads. It is rebuilt automatically on every
repowise update --workspace
and persisted to .repowise-workspace/system_graph.json.
Nodes are services, not repos
A monorepo with three detected service boundaries (a package.json,
go.mod, or Cargo.toml sub-directory) shows three nodes. The repo is
a grouping attribute on each node. A repo with no sub-boundary collapses
to a single repo-root node. Each node carries its provider/consumer
counts, the contract types it participates in, and flags for orphan or
isolated services.
Edges are typed and honest
Every edge carries:
- a
kind, one ofhttp,grpc,event,package,co_change, ordb; - a
family:runtime,data,buildorhistory; - a
strategy, the rule that made its strongest link (see How each contract and link is made); - a
match_type, one ofexact,candidate,manual, orinferred; - a
confidenceand aweight(how many underlying contracts, deps, or co-changes it aggregates); contract_refsback-pointers, so any view can drill from an edge to its evidence.
Edge direction is uniform: source depends on or calls target. A
consumer points to the provider it calls; a dependent repo points to the
repo it imports. Structural edges (contracts, package deps) are flagged
distinctly from behavioral co-change edges, so repowise never conflates
"these change together" with "these call each other".
Fetch the graph over REST with GET /api/workspace/system-graph, or
explore it visually in the System Map. ?view=map returns a lean copy
(no diagnostics, edges carry ref_count instead of evidence refs).
GET /api/workspace/system-graph/links?a=<service>&b=<service> lists every
contract, service link, package dependency and co-change between two
services, in both directions, with totals per kind, kind and text filters,
paging, and each end's contract origin.
Evidence state
A graph with no cross-service edges would score as perfectly decoupled, so every view states how much evidence the graph rests on:
| State | Meaning |
|---|---|
measured | Every service takes part in a runtime dependency |
partial | Edges exist, but part of the estate is out of sight (build references only, services with no detected dependency, repos that yielded no contracts). Figures carry the reasons |
insufficient | Fewer than two services carry a contract, or no structural edge joins two. Scores, cycle verdicts and impact sets read "too little evidence", not clean |
The state and its reason codes appear in workspace metrics, workspace check, blast radius, get_risk and get_change_risk (when short of
measured), and the System Map.
Service groups
Past 40 services, each service joins at most one group, in this order: a
repo tag group:<name> in .repowise-workspace.yaml (other tags never make
a group), a shared repo-alias prefix over two or more repos and under half
the estate, then community detection over runtime and data edges. Groups
exist only when two or more form. Each node carries an optional group,
and the graph a groups summary.
System Map
The web UI renders the system graph as an always-current diagram on the
System Map tab of a workspace's dashboard (/w/<workspace-id>/system-map).
It is the visual counterpart to the REST endpoint, the same nodes and
edges laid out and explorable, never a hand-drawn picture. This is a
different view from the per-repo Knowledge Graph
at /repos/<id>/knowledge-graph, which renders one repo's
community-detected module layout instead of the cross-repo service
graph. For one repo's C4 views and Structurizr export, see
Architecture diagrams.
- Service nodes, coloured by category (service, frontend, worker, library, external), with a health ring rolled up from the owning repo and small flags for orphan or isolated services.
- Typed edges distinguished by
kind(colour and glyph) and bymatch_type(solid for exact/manual, dashed for candidate, dotted for inferred co-change). Behavioral co-change edges read differently from structural contract and dependency edges. - Levels: on a grouped estate the map opens on group cards joined by bands (link count and kind mix per ordered pair); open a group to see its services, and a tray holds services with no runtime or data link. Without groups the map is one flat estate. Runtime and data links draw by default; build and history are opt-in. The map draws at most 120 nodes and 300 edges and folds the rest into "+N more".
- Focus a service (
f) to see its callers, the service and its dependencies, one or two hops out. - Between two services (
b) lists every route between them, direct or through one other service. - Search (
/or Ctrl/Cmd+K) finds services, groups and routes;lswitches to a sortable list view,?shows the shortcuts and Esc backs out. - Filters to toggle each edge kind on or off, and a service ↔ repo switch that collapses a monorepo's services into one node per repository.
- Drill-down: click a service to inspect its providers/consumers and connected services; click an edge to see its match type, confidence, weight, and the underlying contract evidence, with a jump to the Contracts view.
- Gateway-routed links keep the HTTP line and read "via gateway" on hover. A graph short of measured evidence gets one note above the map naming the reason.
- A legend explaining the edge colours, dash patterns, and the health scale.
The map appears once the workspace has at least two indexed repositories with detected relationships. It shows honest empty states otherwise.
Extraction diagnostics
When the cross-repo link count looks low, diagnostics explain why. Computed alongside contract matching, they report, per repo and contract type, how many providers and consumers were found, which consumers went unmatched (and why), and which providers have no consumer at all.
repowise workspace diagnostics # human-readable report
repowise workspace diagnostics --json # raw JSON
repowise workspace diagnostics --repo api # limit to one repoThe report covers:
- Provider / consumer counts per repo, broken down by contract type.
- Unmatched consumers, grouped by reason:
no_provider, no provider anywhere declares a matching route/service/topic.internal_only, the only matching provider is in the same repo and service, so the call is intra-service and intentionally not surfaced as a cross-repo link.unlinked, a cross-service provider with a matching id exists, but no link formed (a candidate worth inspecting).external_host, the call targets a literal third-party host, so it is excluded from matching.ambiguous_provider, two or more repos serve the call's route and neither its host nor its base says which one it reaches, so it links to none.
- HTTP coverage: calls that linked to another service, calls read,
and where the read but unlinked ones went. Calls served inside their own
service are listed apart (
in_service_consumers), not counted as unmatched. - Dangling calls, named per reason with HTTP, gRPC and topics first; table reads collapse to one line per repo.
- Unresolved names:
http_consumers_unresolved(calls located whose path is not static) andtopic_names_unresolved(broker calls whose topic or queue name the file does not settle). - Unrecognised files: per repo, files that call out over the network or use a messaging SDK but produced no contract, a floor on calls no dialect recognises.
- Orphan providers, endpoints declared but never consumed by any
repo, one row per repo, service and id (
other_fileslists the other declaring files). - Weak links, matched links below the confidence threshold.
The same data is available over REST at GET /api/workspace/diagnostics
and is embedded in the system graph artifact's diagnostics block.
Diagnostics is the first thing to run when a service you expect to be
connected shows up isolated on the map. The unlinked bucket usually
points at a route or topic id that almost matched but normalized
differently on the two sides.