Who owns each part of the codebase, does that ownership match who is actually working on it, and where is a given package used? Repowise reads components from the catalog you already keep (or derives them), rolls every signal up to them, and compares declared owners with the last 90 days of commits. Findings name components and teams. Individuals are counted, never named.
repowise components list # declared and derived components
repowise components check # validate the catalog; exits 1 on error
repowise components ownership # declared owners against recent activity
repowise events --since 30d # what moved between index runs
repowise packages where log4j-core # where a third-party package is importedDeclaring components
Components follow the Backstage model (Component, System, Group on
backstage.io/v1alpha1). Declare them in .repowise/components.yaml, or let
repowise read the catalog-info.yaml your developer portal already uses.
repowise.dev/* annotations add paths, criticality, tags and capability.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payments
annotations:
repowise.dev/paths: "services/payments/**"
repowise.dev/criticality: critical # low, medium, high, critical
repowise.dev/tags: "pci, customer-facing"
spec:
type: service
owner: group:default/payments-team
lifecycle: productionrepowise.dev/pathstakes gitignore-style globs. Where two components match a file, the first declared wins.- With no file, components are derived from workspace services, then module
pages, then top-level directories, so every repository has components from
its first index. Derived names skip route segments such as
[id], and two that would collide are qualified by their parent directory. repowise components checkvalidates the files against what git tracks, so it fits a CI step.
Every rollup is served per component: get_context(targets=["component:payments"])
returns health, hotspots, bus factor, test reach and dead code, and
get_overview(include=["components"]) lists them all.
Declared owners against recent activity
Owners come from the catalog's spec.owner, CODEOWNERS (GitHub, and GitLab
with sections) and an optional .repowise/teams.yaml that lists each team's
members for matching commits. Members are used only for matching and never
appear in output.
repowise components ownership compares them with the indexed commits of the
last 90 days and raises four findings:
| Finding | Fires when |
|---|---|
| No active owner | A high or critical component has no declared owner with recent commits on its files |
| Inactive owner | A declared owner has no recent changes on the component's files |
| Bus factor of 1 | A high or critical component depends on one person's knowledge |
| Empty claim | A catalog entry claims no files |
An owner none of whose identities appears in the indexed history is
unknown, never inactive. Individuals match only through an exact git
email or a forge noreply login, and a bare @name that names no declared
team stays unresolved (on GitLab it may be a group). The same answer is served
by get_context(targets=["component:<name>"], include=["ownership"]).
Snapshots and events
Every init and update stores a snapshot per component. When two
consecutive runs share an analyzer stamp, what moved between them is stored as
a typed event:
| Kind | Meaning |
|---|---|
ownership_shift | The component's main owners changed |
owner_inactive | A declared owner stopped committing to it |
bus_factor_drop | A critical component's bus factor fell |
new_hotspot | A file became a hotspot (off by default) |
contract_added | The component provides a new contract |
contract_broken | A provided contract changed incompatibly |
decision_stale | A governing decision went stale |
dead_code_growth | Dead code in the component grew |
test_reach_drop | Fewer of its files are reached by tests |
Events are written to .repowise/events.jsonl as CloudEvents 1.0.2, served by
repowise events and GET /api/repos/{id}/events, and listed newest first in
the Events view of the Commits page. Delivery goes through a pluggable
event-sink seam (jsonl by default, stdout for CI).
# .repowise/config.yaml
events:
enabled: true
retention_days: 365
compact_after_days: 30 # older runs keep one per ISO week
kinds: {new_hotspot: true}
sinks: [jsonl]Owners are compared as keyed digests (a random per-repository key in
.repowise/events.key), and a declared owner is named in an event only when
it is a group. Severity follows the component's criticality.
Package usage
repowise packages where <name> lists the files that import a third-party
package, with the import line, the component, its declared owner teams and
whether the file is reachable, each with its basis.
| Field | Basis |
|---|---|
mapping | declared when indexing linked the import to the manifest declaration; inferred when the module only starts with the name asked for |
owners | Declared teams only. Individuals and unresolved handles are counted, never named |
reachability | reachable from an entry point, unreachable per an open dead-code finding, test, or unknown |
Packages come from the manifests the index already parses (package.json,
pyproject.toml, requirements*.txt, Cargo.toml, go.mod, .csproj,
pom.xml). The same answer is served by
get_context(targets=["package:<name>"]) and
GET /api/repos/{id}/external-systems/{package}/usage.
repowise packages export --format cyclonedx -o usage.cdx.json
repowise packages import sbom.spdx.json # or Syft, CycloneDX, OSV JSON
repowise report inventory --format csv --out inventory.csvpackages export --format cyclonedxwrites CycloneDX 1.6 JSON with one component per declared package andevidence.occurrencesfor each import site. It records where packages are used. It is not an SBOM: no hashes, licenses, transitive dependencies or resolved versions.packages importjoins a Syft, SPDX 2.x, CycloneDX or OSV file you supply to the import sites by package name, advisories first. Repowise fetches no advisory feed and matches no vulnerability itself; OSV version ranges are not evaluated.report inventorylists components with owner teams, languages, entry points and packages. See Evidence reports.
Limits
- Ownership reads commits, not review or on-call data, so "active" means "committed to these files recently".
- Contract events need a workspace, and are measured after the cross-repository pass extracts contracts; outside a workspace those kinds never fire.
- A stamp change after an upgrade starts a new event baseline, so the first run after it records no events.
- Package usage covers declared, imported packages. A dependency loaded without an import statement is not listed.