Repowise workspaces let you index and analyze multiple repositories together. You get per-repo documentation, graphs, and search, plus cross-repo intelligence: co-change detection, API contract extraction, and package dependency mapping.
- When to Use Workspaces
- Quick Start
- How It Works
- Workspace Commands
- Cross-Repo Intelligence
- Architecture Metrics
- Web UI
- MCP Integration
- File Layout
- FAQ
Use a workspace when your project spans multiple git repositories that are related:
- A backend + frontend in separate repos
- A monorepo root with standalone service repos alongside it
- Microservices that communicate over HTTP, gRPC, or message topics
- Any set of repos where you want to understand cross-repo dependencies and co-change patterns
If you only have a single repo, repowise init works as before, no workspace needed.
Put related repos under a common parent directory:
my-workspace/
backend/ # git repo
frontend/ # git repo
shared-libs/ # git repo
Or, if your workspace root is itself a git repo (e.g., a monorepo with sub-repos):
my-project/ # git repo (monorepo)
.git/
backend/ # git repo
frontend/ # git repo
cd my-workspace
repowise init .Repowise will:
- Scan for git repositories (up to 3 levels deep)
- Prompt you to select which repos to index
- Ask you to pick a primary repo (the default for MCP queries)
- Walk you through provider setup (LLM provider, model, cost estimate)
- Index each repo, parse files, build graphs, index git history
- Generate documentation for each repo (unless
--index-only) - Run cross-repo analysis, co-changes, API contracts, package deps
- Register MCP servers with Claude Desktop and Claude Code
# Check workspace status
repowise status --workspace
# List workspace repos
repowise workspace list
# Start the web UI
repowise serve
# Search across all repos
repowise search "authentication flow"A workspace is a directory containing multiple git repositories, tied together by a config file (.repowise-workspace.yaml) and a shared data directory (.repowise-workspace/).
Each repo is indexed independently into its own .repowise/wiki.db, the same format as single-repo mode. The workspace layer adds cross-repo analysis on top.
| Feature | Single-Repo | Workspace |
|---|---|---|
| Per-repo docs, graph, search | Yes | Yes (for each repo) |
| Co-change detection | Within repo | Within + across repos |
| API contract extraction | No | Yes (HTTP, gRPC, topics) |
| Package dependency mapping | No | Yes |
| Web UI | Repo pages | Repo pages + workspace dashboard |
| MCP | One server per repo | One server, all repos |
Initialize a workspace in the current directory. Scans for git repos, prompts for selection, and indexes everything.
Options:
| Flag | Description |
|---|---|
--index-only |
Parse and analyze without LLM generation (free) |
-x, --exclude |
Glob patterns to exclude (e.g., -x "node_modules/") |
--yes |
Skip confirmation prompts |
--concurrency N |
Max concurrent file parses (default: auto) |
Example:
repowise init . -x "node_modules/" -x "*.lock" -x "vendor/"Show all repos in the workspace with their index status.
repowise workspace listAdd a new repo to an existing workspace and index it.
repowise workspace add ../new-service --alias api-gatewayRemove a repo from the workspace (does not delete files).
repowise workspace remove api-gatewayRe-scan the workspace directory for new repos that haven't been added yet.
repowise workspace scanChange which repo is the default for MCP queries.
repowise workspace set-default backendExplain the cross-repo contract link count, per-repo provider/consumer counts, unmatched consumers grouped by reason, and orphan providers. See Extraction Diagnostics.
repowise workspace diagnostics # human-readable report
repowise workspace diagnostics --json # raw JSON
repowise workspace diagnostics --repo api # limit to one repoArchitecture lint: check the declared conformance: rules against the system graph and detect dependency cycles. Exits non-zero on any finding, so it gates CI. See Architecture Conformance.
repowise workspace check # human-readable report; exit 1 on findings
repowise workspace check --json # raw report JSONArchitecture-complexity metrics: propagation cost, the cyclic core, per-service roles, and a deterministic 1-10 architecture score. See Architecture Metrics.
repowise workspace metrics # human-readable summary
repowise workspace metrics --json # raw metrics JSONWhen you initialize a workspace with 2+ repos, repowise runs three types of cross-repo analysis:
Analyzes git history across repos to find files that frequently change together. For example, if backend/api/routes.py and frontend/src/api/client.ts are always modified in the same time window, they get a high co-change score.
Useful for:
- Understanding implicit dependencies between repos
- Knowing what frontend files to check when a backend API changes
- Identifying tightly coupled components
Scans source files for HTTP route handlers, gRPC service definitions, and message topic publishers/subscribers. Then matches providers (servers) with consumers (clients) across repos.
Supported patterns:
| Type | Providers | Consumers |
|---|---|---|
| HTTP | Express, FastAPI, Spring, Laravel, Go (gin/echo/chi/net-http), ASP.NET (attribute + minimal API), Rust (Axum routes, Actix/Rocket attribute macros) | fetch/axios/URL-literal wrappers (JS/TS), requests/httpx (Python), HttpClient/UnityWebRequest/Best.HTTP (C#), reqwest (Rust) |
| gRPC | .proto service definitions, plus per-language dialects (Go, Java, Python, C#, TypeScript, NestJS @GrpcMethod) |
gRPC client stubs |
| Data / DB | DDL (CREATE TABLE/VIEW/MATERIALIZED VIEW), ORM dialects (SQLAlchemy, Django, JPA, EF Core, ActiveRecord, Eloquent) |
Raw SQL string literals in app code (verb-anchored: SELECT/INSERT/UPDATE/DELETE/MERGE) |
| Topics | Kafka, RabbitMQ, NATS producers | Corresponding consumers |
Data/DB contracts use the id scheme data::<table> and render as a db edge in the system graph. The consumer side (SQL string matching) is heuristic and lower-confidence than the ORM-based providers; unlike HTTP and gRPC, there is no field-level breaking-change diffing for data contracts, only table/route-level removal.
HTTP routes are matched on their full path: a router mount prefix
(APIRouter(prefix=...), include_router(prefix=...), Express app.use('/x', router),
Go route groups) is stitched onto each handler path before matching. A client call
whose base URL is an unresolved placeholder (fetch(\${API_BASE}/users`)`) matches
on the host-relative path; the link is exact when exactly one workspace service
provides that path and a lower-confidence candidate when the target is ambiguous.
Tuning extraction via the contracts: block in .repowise-workspace.yaml:
contracts:
detect_http: true
detect_grpc: true
detect_topics: true
detect_data: true
# Map a consumer base token or absolute host to the repo it targets, so a
# call whose base is unresolved at parse time links as an exact match.
service_bases:
API_BASE: backend # ${API_BASE}/... -> the "backend" repo
api.example.com: backend # https://api.example.com/... -> "backend"
# Extra globs to skip (added to the built-in test/spec defaults).
exclude_globs:
- "generated/**"Directories named tests/, __tests__/, and __mocks__/ are excluded by name;
test/, spec/, and e2e/ are deliberately not excluded by directory, since those
names double as legitimate product directories in some codebases. Regardless of
directory, filenames matching test_*.py, *_test.py, *_test.go, *.test.*,
*.spec.*, *.e2e.*, or conftest.py are always excluded: a route or topic that
exists only in a test is a fixture, not a service contract. Calls to a literal
third-party host (Stripe, Formspree, ...) that is not a workspace service are
excluded from matching and reported under the external_host diagnostics reason.
Reads package manifests (package.json, pyproject.toml, go.mod, pom.xml, etc.) to detect when one repo depends on another as a package.
The contracts, package dependencies, and co-changes above are each a flat list. repowise 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 / 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/isolated services.
Edges are typed and honest. Every edge carries:
- a
kind,http,grpc,event,package,co_change, ordb; - a
match_type,exact,candidate,manual, orinferred; - a
confidenceand aweight(how many underlying contracts / deps / 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 / 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, repowise never conflates "these change together" with "these call each other".
Fetch it over REST with GET /api/workspace/system-graph, or explore it visually in the Live System Map.
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 + 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 (Stripe, Formspree, ...) that is not a workspace service, so it is intentionally excluded from matching.
- Orphan providers, endpoints declared but never consumed by any repo.
- 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.
Start the web server:
repowise serveIn workspace mode, the web UI adds:
- Workspace Dashboard (
/workspace), aggregate stats across all repos, repo cards with file/symbol/coverage counts, and cross-repo intelligence summary - System Map (
/workspace/system-map), the Live System Map: a code-derived diagram of services and their typed relationships - Contracts View (
/workspace/contracts), all detected API contracts with provider/consumer matching, filterable by type and repo - Co-Changes View (
/workspace/co-changes), cross-repo file pairs ranked by co-change strength
The sidebar shows all workspace repos under Repositories. Click any repo to access its full per-repo pages (overview, docs, graph, search, hotspots, etc.).
The System Map renders the system graph as an always-current diagram. It is the visual counterpart to the REST endpoint, the same nodes and edges, laid out and explorable, never a hand-drawn picture.
- 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 + 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/dependency edges. - 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.
- 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.
Blast radius answers a single question: if I change this service, what downstream services and repos break? It walks the system graph against its edge direction, a consumer → provider edge means changing the provider impacts the consumer, and returns every reachable service ranked by an impact score.
Two edge classes are weighted and labelled distinctly:
- Structural edges (http / grpc / event / package / db) assert a real dependency, a contract or an import. They propagate impact at full weight and surface as will break.
- Behavioral co-change edges only assert that two files historically changed together. They are correlation, not a call, so they propagate at half weight (one named constant,
BEHAVIORAL_EDGE_WEIGHT) and surface as may drift.
Each impacted service carries its distance (hops from the change) and score (0-1, with distance decay and the behavioral weighting baked in). Nearer, structural impact ranks highest.
Use it three ways:
- REST,
GET /api/workspace/blast-radius?target=<node-id-or-repo>&max_depth=3&include_behavioral=true.targetis a node id (repoorrepo::service/path) or a repo alias (expands to all its services). - MCP, the
get_blast_radiustool (workspace mode) gives an agent the impacted set before it touches a high-fan-out provider. Theget_riskPR-mode directive also gainswill_break_consumersandmissing_cross_repo_cochangesso a diff in one repo flags its cross-repo fallout. - System Map, pick a service in the Blast radius control above the map; the reachable set ripples (highlighted, the rest dimmed, badges grading intensity), and a side panel lists the impacted services. Click any impacted service to walk the impact outward from there.
Where blast radius answers what could be affected, the breaking-change guard answers a sharper question: did a provider change in a way that actually breaks its consumers? On every repowise update --workspace, the freshly-extracted contracts are diffed against the previously-indexed set and each incompatible provider change is reported with the exact consumer files that call it.
Detected change kinds (a registry, adding a kind is one new rule, never an if/elif):
| Kind | Severity | Fires when |
|---|---|---|
removed_endpoint |
breaking | A provider route / gRPC method / topic that existed before is gone |
removed_field |
breaking (response) / warning (request) | A request or response field disappeared |
field_type_changed |
breaking | A field's type changed (e.g. string → int64) |
field_number_changed |
breaking | A proto field's wire number changed |
field_required |
breaking | A field became required, or a new required field was added |
Non-breaking changes never flag, an added optional field, a widened set, or a brand-new endpoint produces no record. Field-level diffs need a contract schema; today gRPC carries one (proto message fields, recovered by the existing proto parser), and HTTP gains field-level checks when an OpenAPI spec is present. Route-level removal is detected for every transport from the contract id alone.
Impacted consumers are resolved from the matched contract links, the same provider↔consumer evidence the system graph's edges are built from, so impact is endpoint-precise (the consumer file that calls the changed contract) and direct (the first reachability hop, which is exactly what a contract break endangers; transitive ripple stays the job of blast radius).
Use it three ways:
- REST,
GET /api/workspace/breaking-changesreturns the report from the most recent update (filterable byrepoorseverity). Each change carries its provider, detail, and impacted consumers with both code sides. - MCP, the
get_riskPR-mode directive gains abreaking_changesblock listing the provider contracts that changed incompatibly in the diff's repo and the consumers they endanger, across repos. - System Map, toggle Breaking changes above the map: changed providers are badged with their breaking count, the consumers they endanger are badged at risk, and the seams between them are highlighted (additive overlay, the map stays whole). A side panel lists each change with both the provider and consumer files.
Workspaces let you declare, in .repowise-workspace.yaml, which services are allowed to depend on which others, and then continuously check the live system graph against those rules. This is your team's architecture lint: the intended architecture, expressed as code, verified on every update.
Conformance rules live in a conformance: block in the workspace config (no separate file). Each rule has a source and a target matcher and an allow flag:
repos:
- path: web
alias: frontend
tags: [ui, edge]
- path: services/db
alias: db
tags: [data]
conformance:
rules:
# Deny rules (allow defaults to false): the dependency is a violation.
- source: frontend
target: db
description: The UI must call the API, never the database directly.
- source: "*"
target: legacy-payments
# Tag-based: nothing in the "ui" tier may depend on the "data" tier...
- source: "tag:ui"
target: "tag:data"
# ...except migrations, which are explicitly allowed (an exception).
- source: migrations
target: db
allow: trueA matcher resolves against service nodes in the system graph:
| Matcher form | Matches |
|---|---|
* |
every service |
tag:<name> |
every service whose repo declares that tag (see tags: on each repo) |
| anything else | a glob over the node id, repo alias, and display name (frontend, api::*, *-worker) |
A rule with allow: false (the default) is a deny rule: a structural dependency from a matching source to a matching target is a violation. A rule with allow: true is an exception that whitelists an otherwise-denied edge. Only structural edges (HTTP, gRPC, event, package, db) are evaluated; behavioral co-change is never a dependency.
Independently of any rules, conformance detects circular dependencies among services over structural edges (A → B → … → A). A cycle means the services cannot be built, deployed, or reasoned about independently. Cycle detection runs even with zero rules declared, so every workspace gets it for free.
-
CLI,
repowise workspace checkprints violations and cycles and exits non-zero when any are found, so it gates CI (the architecture lint):repowise workspace check # human-readable report; exit 1 on findings repowise workspace check --json # raw report JSON (still exits 1 on findings)
It recomputes from the persisted system graph, so editing rules and re-running picks them up without a full re-index.
-
REST,
GET /api/workspace/conformancereturns the report from the most recent update (filterable byrepo). -
MCP,
get_conformanceexposes violations and cycles to an agent; theget_riskPR-mode directive gainsconformance_violationsanddependency_cyclesblocks for the findings the diff's repo participates in. -
Conformance view, the web UI's Conformance page renders a dependency-structure matrix (DSM): services on both axes, each filled cell a dependency tinted by transport, with rule violations ringed red and cycle cells amber. Governance panels list the violations and cycles. Violations also badge the offending edges on the Live System Map (toggle Conformance), reusing the same additive overlay as the breaking-change guard.
Conformance and the cycle finder answer per-relationship questions (is this edge allowed, is this loop a cycle). Architecture metrics give the one evaluative read of the whole system: how coupled it is, where its architectural core is, and a single score you can track over time and compare across workspaces. These are the standard MacCormack / Baldwin / Sturtevant architecture-complexity metrics, computed deterministically over the system graph, no LLM. They use structural edges only (http / grpc / event / package / db); co-change is excluded.
- Propagation cost, the share of other services the average service can reach transitively through dependencies (0% = fully decoupled, 100% = everything reaches everything). The headline coupling number; lower is better.
- Cyclic core, the largest cyclic group of services (the largest strongly-connected component of the structural graph). Its size and ratio (core / services) describe how much of the system is tangled together.
- Architecture type,
core-peripherywhen the core spans a meaningful fraction of the system, elsehierarchical. - Per-service role, each service is classified from its visibility profile:
- Core, in the largest cyclic group (the architectural center).
- Shared, high visibility fan-in, low fan-out: many services depend on it, it depends on few (a widely-used utility/library).
- Control, high fan-out, low fan-in: it depends on many, few depend on it (an orchestrator / entry point).
- Peripheral, lightly coupled in both directions.
- Architecture score, a deterministic 1-10 roll-up (matching the Code Health 1-10 convention) from propagation cost, core ratio, dependency-cycle count, and declared-rule violation count. Lower coupling and a smaller core score higher.
-
CLI,
repowise workspace metricsprints the score, propagation cost, cyclic core, dependency-cycle count, and the per-role service breakdown. CI-friendly plain output;--jsonemits the raw metrics.repowise workspace metrics # human-readable summary repowise workspace metrics --json # raw metrics JSON
-
REST,
GET /api/workspace/architecturereturns the workspace metrics plus the per-service roles. Computed at request time from the system graph (no separate artifact); the conformance violation count, if a report exists, is folded into the score. -
MCP,
get_architecturegives an agent the score, propagation cost, core members, and role breakdown in one call, the system-structure read to consult before a cross-service refactor. -
Web, the architecture score appears as a stat on both the Conformance and System Map pages. The DSM header shows score / propagation cost / core size and tints each service's diagonal cell by its role, so the on-diagonal core block stands out. On the Live System Map, toggle Core to highlight the cyclic core, and the inspector shows any selected service's role and visibility profile.
Workspace init automatically registers MCP servers with Claude Desktop and Claude Code. The MCP server is workspace-aware:
- Default repo context, queries go to the primary repo unless you specify otherwise
- Cross-repo tools, MCP tools can query across repos and return enriched context with co-change and contract data;
get_blast_radiusanswers cross-repo downstream impact (see Cross-Repo Blast Radius);get_conformanceanswers architecture rule violations and dependency cycles (see Architecture Conformance);get_architectureanswers whole-system coupling, the cyclic core, and the architecture score (see Architecture Metrics) - Repo parameter, most tools accept an optional
repoparameter to target a specific repo, or"all"to query across the workspace
After workspace init, your directory looks like:
my-workspace/
.repowise-workspace.yaml # Workspace config (repo list, default, settings)
.repowise-workspace/ # Shared cross-repo data
cross_repo_edges.json # Co-change pairs and package deps
contracts.json # Extracted API contracts and links
system_graph.json # Service-granular system graph + diagnostics
breaking_changes.json # Breaking provider changes vs the last index
conformance.json # Architecture rule violations + dependency cycles
.claude/
CLAUDE.md # Workspace-level CLAUDE.md for AI editors
backend/
.repowise/ # Per-repo index data
wiki.db # SQLite database (pages, graph, symbols, git)
lancedb/ # Vector embeddings
config.yaml # Repo-level config
mcp.json # MCP server config
frontend/
.repowise/
wiki.db
lancedb/
...
Add these to your .gitignore:
.repowise/
.repowise-workspace/
.repowise-workspace.yamlThe workspace config and data are local, they reference absolute paths and contain generated analysis that should be rebuilt per-machine.
Yes. Use repowise workspace add /path/to/external-repo. The path will be stored relative to the workspace root if possible, or as an absolute path otherwise.
It runs in single-repo mode for the current directory, ignoring the workspace. Use repowise init . from the workspace root to initialize or re-initialize the workspace.
No. Repowise searches upward for .repowise-workspace.yaml and uses the first one it finds. Nested workspace configs are not supported.
repowise update # Update the primary repo
repowise update --workspace # Update all workspace reposOr use watch mode for automatic updates:
repowise watch --workspaceCurrently, cross-repo analysis runs automatically during repowise init . and repowise update --workspace. To force a re-run, use repowise init . again, it will detect existing indexes and only re-run what's needed.
Yes. A single MCP server instance serves all workspace repos. It uses lazy-loading with LRU eviction (max 5 repos loaded simultaneously) to manage memory. The default repo is always kept in memory.
Yes, and it's automatic. Running repowise init or repowise update inside a linked worktree detects the base checkout, seeds the worktree's index from it, and incrementally updates only the files that differ on your branch. No flags needed; --seed-from <path> and --no-seed exist as overrides. See WORKTREES.md.