On this page

Metrics report 🔗

codeps status consumes each configured project's source (SemanticDB, jdeps, or an existing codeps export format JSON file) and writes one JSON document to .codeps/out/<project>/report.json. It has a top-level packages report and, where source data contains files, a top-level files report. inspect-cycle and inspect-node read this cached file to render one cycle or node in detail; use --scope files for file detail.

codeps status
codeps findings --project root --limit 10 --format json

Core metrics are computed fresh from the graph's node/edge list on every run, so a report is a pure function of its input. Budgeted cycle-cut analysis exists in the core library but is not currently wired into the CLI or config, so every cycle's cutAnalysis.status is notRequested in practice (see Cycles). Set the SOURCE_DATE_EPOCH env var (epoch seconds) to pin generatedAt for deterministic CI diffs.

Structure 🔗

{
  "schemaVersion": 4,
  "packages": {
    "schemaVersion": 3,
    "scope": "packages",
    "generatedAt": "<ISO8601 UTC, second precision, e.g. 2026-08-27T10:00:00Z>",
    "summary": {
      "nodes": 100,
      "edges": 214,
      "nodesInCycles": 34,
      "orphans": 3
    },
    "cycles": [
    {
      "id": "scc:cache",
      "members": ["cache", "scheduler"],
      "witnessCycle": ["cache", "scheduler", "cache"],
      "size": 2,
      "extFanIn": 5,
      "internalEdges": 2,
      "incomingEdges": 5,
      "outgoingEdges": 3,
      "cutAnalysis": {
        "status": "notRequested",
        "greedyCutEstimate": null,
        "solutions": [],
        "examinedCandidates": 0
      }
    }
    ],
    "propagators": [
    { "node": "cache", "fanIn": 3, "fanOut": 2, "score": 2.5 }
    ],
    "surface": [
    {
      "node": "cache", "fanIn": 3, "fanOut": 2, "ports": 9, "mutPorts": 5, "exposure": 24,
      "dependentsPerPublicPort": 0.33, "cycleId": "scc:cache",
      "publicSurface": 3, "protectedSurface": 2, "packageSurface": 1, "privateSurface": 4,
      "publicMutableSurface": 1, "protectedMutableSurface": 0, "packageMutableSurface": 0,
      "privateMutableSurface": 1, "totalDeclaredSurface": 10,
      "encapsulationRatio": 0.3, "publicMutableRatio": 0.33
    }
    ],
    "orphans": ["DeadUtil.scala"],
    "findings": [
    {
      "id": "cycle:scc:cache",
      "kind": "cycle",
      "severity": "high",
      "subject": "scc:cache",
      "evidence": "size=2, extFanIn=5, greedyCutEstimate=none",
      "confidence": "high",
      "nextAction": "inspect-cycle --id scc:cache"
    }
    ]
  },
  "files": { "scope": "files", "...": "same report shape" }
}

Summary 🔗

JSON Schemas 🔗

Machine-readable contracts use JSON Schema Draft 2020-12. The schemas describe current output; older artifact versions are not supported. Unknown properties are allowed for additive evolution. Relative $ref links share definitions; offline validators need the entire docs/static/schemas/ directory, with each $id mapped to its local file.

Artifact Schema
report.json (outer v4, scope v3) report.schema.json
Codeps export format export.schema.json
One health history NDJSON line (v6) history.schema.json
findings --format json (v1) findings.schema.json
inspect-cycle --format json inspect-cycle.schema.json
inspect-node --format json inspect-node.schema.json

Unavailable files and file node packageId values serialize as explicit null; readers also accept omitted fields. truncation is explicit null when no findings were omitted. Unavailable surface ratios, cycleId, and greedyCutEstimate are explicit null. Export declaration counters default to zero when missing, and edge weight defaults to one. Schemas validate shapes and types; they do not prove that edges reference existing nodes, that cycle paths are valid, or that aggregate counts agree.

Findings 🔗

findings is a deterministic index of actionable diagnostics. Each finding has a stable id, a kind (cycle, propagator, mutableSurface, or structuralUse), severity, and subject, plus human-readable evidence, confidence, and a nextAction. Findings are ranked by severity, then metric score, then id. They cover every reported SCC, every above-average propagator, every node with exposed mutable ports, and every node whose structural-use proxy is below 1.0. The structural-use kind is a graph proxy based on file/package edges; it does not identify individual declarations. JSON serialization bounds findings at 10,000 rows so large projects remain agent-usable. When rows are omitted, truncation.findingsOmitted records the count. Cycles, propagators, surface rows, and orphans are not subject to this JSON inventory bound.

codeps findings returns a bounded selection of this cached index with exact kind and severity filters. Its omitted counts cached matches beyond the requested limit; sourceFindingsOmitted separately carries the scope report's truncation.findingsOmitted. See the CLI contract. Action hints require callers to preserve the selected project and scope when inspecting a subject.

History and dashboard 🔗

.codeps/<project>.ndjson contains one JSON object per recorded snapshot. Each entry owns its commit metadata once and has direct packages and optional files health sections. Scores are stored and displayed to two decimal places. The dashboard's Home tab shows the two-decimal average of the available package and file scores, with both underlying scores visible; it is a compact directional summary, not a replacement for either scope's evidence.

The trend chart spaces snapshots evenly in recording order and labels its axis with short commit IDs. Dirty snapshots retain the -dirty suffix; timestamp-based IDs remain intact. Hovering or focusing a point shows its full ID, timestamp, and metric value. Selecting a point updates its metric details. On long histories the axis shows a subset of labels; every recorded point remains available through pages of up to 20 snapshots. The chart opens on the newest page; Older and Newer navigate history, while Latest snapshot always shows the newest recorded entry for the selected scope. Switching scopes opens that scope's newest page. Snapshot metadata defaults and overrides are defined in the CLI reference.

Cycles 🔗

A cycle is a strongly connected component with more than 1 member (Tarjan's algorithm). Singleton components are just acyclic nodes and are never reported.

Change propagators 🔗

The JSON index contains every node above the normalized propagation threshold.

Surface 🔗

One row per scope node is retained in JSON, using camelCase field names.

Rows are sorted by dependentsPerPublicPort ascending (nulls last): the most exposed-for-its-use nodes first. Raw declaration counters are retained so consumers can triage public surface, public mutability, and encapsulation independently.

Declaration surface fields 🔗

Each row also carries raw declaration counts, aggregated from the adapter-neutral declarationSurface counters in the export graph:

Exposed surface 🔗

isExposed, ports and mutPorts are resolved per node by the extraction backend (SemanticDB export), using Scala-specific weight rules that never leak into the metrics layer:

private[pkg] and protected members are not exposed; class-private members are omitted from dependency nodes but retained in file declaration counters; var setters are accessors and never count as surface. jdeps data carries no access info, so all its nodes have ports/mutPorts 0 and dependentsPerPublicPort null — a known gap, not silently meaningful.

Orphans 🔗

inspect-cycle and inspect-node render one cycle or node from this cached report at a time (as table or json, via --format). findings renders a bounded selection of the findings index; cycles/propagators/surface/orphans have no full-inventory table command. Use the JSON report directly for the full inventories; the generated .codeps/out/<project>/index.html dashboard shows trends and summary evidence. The JSON findings array is capped at 10,000 rows and describes any omissions in truncation; the other graph-derived inventories (cycles, propagators, surface, orphans) are never bounded.

⬅️ CLI Codeps export format ➡️