Skip to content

Latest commit

 

History

History
655 lines (457 loc) · 38 KB

File metadata and controls

655 lines (457 loc) · 38 KB

Workspaces, Multi-Repo Support

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.


Table of Contents

  1. When to Use Workspaces
  2. Quick Start
  3. How It Works
  4. Workspace Commands
  5. Cross-Repo Intelligence
  6. Architecture Metrics
  7. Web UI
  8. MCP Integration
  9. File Layout
  10. FAQ

When to Use Workspaces

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.


Quick Start

1. Organize your repos

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

2. Initialize the workspace

cd my-workspace
repowise init .

Repowise will:

  1. Scan for git repositories (up to 3 levels deep)
  2. Prompt you to select which repos to index
  3. Ask you to pick a primary repo (the default for MCP queries)
  4. Walk you through provider setup (LLM provider, model, cost estimate)
  5. Index each repo, parse files, build graphs, index git history
  6. Generate documentation for each repo (unless --no-prose)
  7. Run cross-repo analysis, co-changes, API contracts, package deps
  8. Register MCP servers with Claude Desktop and Claude Code

3. Explore

# 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"

How It Works

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.

Single-Repo vs Workspace

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

Workspace Commands

repowise init .

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/"

repowise workspace list

Show all repos in the workspace with their index status.

repowise workspace list

repowise workspace add <path>

Add a new repo to an existing workspace and index it.

repowise workspace add ../new-service --alias api-gateway

repowise workspace remove <alias>

Remove a repo from the workspace (does not delete files).

repowise workspace remove api-gateway

repowise workspace scan

Re-scan the workspace directory for new repos that haven't been added yet.

repowise workspace scan

repowise workspace set-default <alias>

Change which repo is the default for MCP queries.

repowise workspace set-default backend

repowise workspace diagnostics

Explain 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 repo

repowise workspace check

Architecture 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 JSON

repowise workspace metrics

Architecture-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 JSON

repowise workspace impacted-tests <repo:path>...

Given 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 json

Cross-Repo Intelligence

When you initialize a workspace with 2+ repos, repowise runs three types of cross-repo analysis:

Co-Change Detection

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

API Contract Extraction

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.

Package Dependency Scanning

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.


System Graph

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, or db;
  • a match_type, exact, candidate, manual, or inferred;
  • a confidence and a weight (how many underlying contracts / deps / co-changes it aggregates);
  • contract_refs back-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.

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 repo

The 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.


Web UI

Start the web server:

repowise serve

In 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.).

Live System Map

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 by match_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.


Cross-Repo Blast Radius

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. target is a node id (repo or repo::service/path) or a repo alias (expands to all its services).
  • MCP, the get_blast_radius tool (workspace mode) gives an agent the impacted set before it touches a high-fan-out provider. The get_risk PR-mode directive also gains will_break_consumers and missing_cross_repo_cochanges so 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.

Breaking-Change Guard

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-changes returns the report from the most recent update (filterable by repo or severity). Each change carries its provider, detail, and impacted consumers with both code sides.
  • MCP, the get_risk PR-mode directive's compatibility-named breaking_changes block 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.

Cross-Repo Test Impact

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.py

Three 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 entry field 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>. Repeat file for several changed files in the same repo. The response carries the same fields the --format json output has: recommendations (each with its consumer repo, the consumer_files and bound consumer_symbol_ids that reached the test, basis, via, confidence and the contract ids that produced it), unresolved with a reason per link, files_analyzed with the state each landed on, and the summary counts. Over the API an empty answer's summary.reason is no_contract_data, no_matching_links or lookup_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.

Architecture Conformance

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.

Declaring rules

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: true

A 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.

Dependency cycles

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.

Using it

  • CLI, repowise workspace check prints 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/conformance returns the report from the most recent update (filterable by repo).

  • MCP, get_conformance exposes violations and cycles to an agent; the get_risk PR-mode directive gains conformance_violations and dependency_cycles blocks 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.


Architecture Metrics

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.

What it computes

  • 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-periphery when the core spans a meaningful fraction of the system, else hierarchical.
  • 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.

Using it

  • CLI, repowise workspace metrics prints the score, propagation cost, cyclic core, dependency-cycle count, and the per-role service breakdown. CI-friendly plain output; --json emits the raw metrics.

    repowise workspace metrics          # human-readable summary
    repowise workspace metrics --json    # raw metrics JSON
  • REST, GET /api/workspace/architecture returns 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_architecture gives 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.


MCP Integration

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_radius answers cross-repo downstream impact (see Cross-Repo Blast Radius); get_conformance answers architecture rule violations and dependency cycles (see Architecture Conformance); get_architecture answers whole-system coupling, the cyclic core, and the architecture score (see Architecture Metrics)
  • Repo parameter, most tools accept an optional repo parameter to target a specific repo, or "all" to query across the workspace

File Layout

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/
      ...

What goes in .gitignore

Add these to your .gitignore:

.repowise/
.repowise-workspace/
.repowise-workspace.yaml

The workspace config and data are local, they reference absolute paths and contain generated analysis that should be rebuilt per-machine.


FAQ

Can I add repos that live outside the workspace directory?

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.

What happens if I run repowise init (without .) in a workspace?

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.

Can I have nested workspaces?

No. Repowise searches upward for .repowise-workspace.yaml and uses the first one it finds. Nested workspace configs are not supported.

How do I update a workspace after code changes?

repowise update              # Update the primary repo
repowise update --workspace  # Update all workspace repos

Each 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 --workspace

How do I re-run just the cross-repo analysis?

Currently, 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.

Does the MCP server handle multiple repos?

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.

Can I use repowise with git worktrees?

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.