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
--no-prose) - 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 |
|---|---|
--no-prose |
Parse and analyze, wiki rendered from structure, no LLM (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 JSONGiven one or more changed provider files, list the tests in consumer repos worth running. See Cross-Repo Test Impact.
repowise workspace impacted-tests backend:app/routers/users.py
repowise workspace impacted-tests backend:app/routers/users.py --target-repo frontend --format list | cut -d: -f2- | xargs npx vitest run
repowise workspace impacted-tests backend:app/routers/users.py --format 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 |
| Socket / WebSocket | SignalR MapHub<T>("/path"), FastAPI @app.websocket("/path") |
ClientWebSocket ConnectAsync, SignalR HubConnectionBuilder.WithUrl, NativeWebSocket and WebSocketSharp new WebSocket(...) |
Socket detection is C#/Python only and is toggled by detect_socket in the contracts: block below.
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.
Over REST. GET /api/workspace/contracts lists contracts and links,
filterable by contract_type, repo and role. Each contract carries its
line, its ingestion symbol id and the extractor's meta; each link carries both
symbol ids and both service boundaries. The request/response schema is not on
the list, because it runs to full inline type declarations and only one is ever
needed at a time: fetch it with
GET /api/workspace/contracts/detail?repo=<alias>&file=<path>&id=<contract-id>,
which returns that one contract with its schema, its links, and its unmatched
reason. All three parameters are required, since a contract id alone is not
unique across repos.
Tuning extraction via the contracts: block in .repowise-workspace.yaml:
contracts:
detect_http: true
detect_grpc: true
detect_socket: 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, Cargo.toml, go.mod,
.csproj, and Maven pom.xml) to detect when one repo depends on another as a
package or project.
Maven matching is filesystem-only and coordinate-based. Repowise resolves local
reactor modules, local parents, properties, and dependency-management versions,
then links an active direct compile/runtime dependency only when exactly one
selected workspace project publishes that groupId:artifactId. Test, provided,
system, optional, profile-only, ambiguous, and external dependencies do not create
production package edges. Bounded diagnostics retain the reason for Maven
non-matches. When a repository has a root pom.xml, only that declared reactor is
eligible; unrelated nested example or fixture POMs are not treated as producers.
This does not execute Maven, read user settings, download artifacts, resolve plugins/transitive dependencies/imported BOMs, or infer generated sources. A Maven package edge is also not a published symbol-level code API or a runnable Maven target recommendation; those capabilities are reported separately and remain unsupported.
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.
- Extraction coverage: how many contracts came from the parsed symbol table (
index) versus a text dialect (regex), and how many HTTP client calls were located but could not be resolved to an endpoint. The HTTP coverage percentage is calls resolved over calls located — it is not total recall, because a call no dialect recognises is not in either number. It is the figure that turns a large orphan-provider count from alarming into explained.
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, which downstream services and repos are structurally exposed? It walks the system graph against its edge direction, a consumer → provider edge means changing the provider may impact 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 under the compatibility-named will break field, but mean structural reach rather than certain runtime failure.
- 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 contract change incompatibly? On every repowise update --workspace, freshly extracted contracts are diffed against the previously indexed set. Each finding carries the consumer files linked to the endpoint, but that link proves endpoint exposure only. It does not prove use of the changed field, a runtime failure, or deployment safety.
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 request field became required, or a new required request field was added; legacy proto/signature behavior is preserved |
field_required_relaxed |
breaking | A required OpenAPI response field became optional |
field_nullability_changed |
breaking | An OpenAPI request stopped accepting null, or a response started allowing null |
field_enum_changed |
breaking | An OpenAPI request enum lost values, or a response enum gained values |
schema_comparison_uncertain |
warning | Schema source/fidelity, completeness, or selected response changed, so field compatibility was not inferred |
OpenAPI comparison covers the common supported subset of 3.0.x, 3.1.x, and 3.2.x: JSON/YAML documents; path/query/header/cookie parameters using OpenAPI's default style, explode, and allowReserved behavior; one application/json request body; exactly one explicit JSON 2xx response; recursive objects and arrays; exact primitive types; requiredness; normalized nullability; finite homogeneous scalar enums; and bounded same-document JSON Pointer references. Request rules describe values the provider accepts; response rules describe values consumers may receive, so requiredness, nullability, enum values, and constrained/unconstrained enum transitions reverse between the two sides. Operation removal remains the transport-neutral contract rule.
Only complete sides with the same schema source and comparison-fidelity key are field-diffed. Unsupported/unresolved nodes, extraction-strategy changes, and response-selection changes become warning-level uncertainty rather than shortened schemas or false removals. Remote/cross-file references, composition, additional-properties semantics, arbitrary JSON Schema constraints, non-JSON or ambiguous media, multiple materially different success responses, and OpenAPI 2.0 are outside this boundary. No network dereferencing occurs.
Compatible changes stay quiet: examples include an optional request addition, a request enum widening, a request becoming nullable, a response enum narrowing, a response becoming non-nullable, an additional response field in the supported open-object subset, and a brand-new endpoint. A rename remains a removal plus an addition; no rename inference is attempted.
Endpoint-exposed consumers are resolved from the matched contract links, the same provider↔consumer evidence the system graph's edges are built from. The evidence is direct and endpoint-level: it identifies a consumer file linked to the changed contract, but not the exact field it uses. Transitive structural reach 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's compatibility-namedbreaking_changesblock lists provider incompatibilities and comparison warnings with side, source/fidelity, and endpoint-exposed consumers across repos. - System Map, toggle Breaking changes above the map: changed providers are badged by severity. Consumers and seams are marked exposed only for provider incompatibilities; warning-only uncertainty is never rendered as a consumer failure claim. A side panel lists each finding with both code sides.
Where the breaking-change guard identifies provider incompatibility and endpoint exposure, test impact answers the question you ask before you push: I changed this provider file, which tests in the other repos should I run? It starts from the same matched contract links and then walks each consumer's own index to find tests that exercise the call site. A recommended test validates the exposed consumer path; it is not proof that the changed field is used or that a deployment will fail.
repowise workspace impacted-tests backend:app/routers/users.pyThree output formats: table (the default, grouped by consumer repo), json (the full result, including the counts below), and list (one repo:test-file per line, for piping into a test runner).
Every consumer call site the walk considers ends in one of four states, and the command names the one it landed on:
- measured, a coverage map ingested from that repo says the test actually ran the consumer code. The strongest evidence, and it only exists where coverage has been ingested.
- inferred, no coverage, but the consumer's call graph or import graph reaches the call site from a test. The call graph is entered at the symbol the contract bound to, not the file, so a test that reaches an unrelated function in the same file is not recommended. The import fallback only knows files, so it is entered at the file; every row says which it was, in the
entryfield of its evidence. - none, the consumer was analyzed and nothing reaches the call site. A real answer, not a failure: that code has no test guarding it.
- unresolved, the join could not determine an answer. Four causes, each reported by name: the consumer repo has no index, the contract never bound to a symbol, the bound symbol is no longer in the index, or the lookup itself failed.
An empty answer always says which of these produced it, so "no tests" is never ambiguous between "nothing guards this" and "we could not look". That holds in every format: the table format prints a Could not determine table listing the unresolved links with their reason, json carries the states and the unresolved rows, and list writes the explanation and the unresolved count to stderr so the piped list on stdout stays clean.
Results are capped per consumer and provider pair so one widely-called helper cannot flood the list; when the cap bites, the command prints how many it dropped and --format json carries the exact counts.
Use it three ways:
- CLI,
repowise workspace impacted-tests <repo:path>..., the command above. - REST,
GET /api/workspace/test-impact?repo=<alias>&file=<path>. Repeatfilefor several changed files in the same repo. The response carries the same fields the--format jsonoutput has:recommendations(each with its consumer repo, theconsumer_filesand boundconsumer_symbol_idsthat reached the test,basis,via,confidenceand the contract ids that produced it),unresolvedwith a reason per link,files_analyzedwith the state each landed on, and thesummarycounts. Over the API an empty answer'ssummary.reasonisno_contract_data,no_matching_linksorlookup_failed. - Web UI, a provider contract's page (
/workspace/contracts/detail) ends in a Tests to run section: the tests grouped by consumer repo, each marked measured or inferred and saying whether the coverage map, the call graph or the import graph found it, and a Could not determine list naming the consumer file and the reason. A consumer contract has no such section, since tests are found on the consumer side.
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 reposEach stale repo picks docs vs index-only the same way a single-repo update does, from its own docs_enabled (set at init) plus any override on the command. Repos with docs enabled regenerate their wiki (pages, diagrams, decisions) through the full docs path, so a workspace wiki stays as fresh as one you update repo by repo; the rest just refresh the index. Force docs everywhere with repowise update --workspace --docs (each repo needs an LLM provider/key, or pass --provider), or keep it index-only with --no-docs.
Or 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.