Output schema
prcompass analyze writes one JSON document to stdout. The top-level keys are versioned by version (== ANALYSIS_SCHEMA_VERSION from @prcompass/core) — pin to the major when you integrate.
interface CliAnalysisOutput {
version: string; // e.g. "0.1.0"
head: { sha: string; baseSha: string };
diff: { fileCount: number };
pr: PrMetadata | null; // null in --local mode
mining: { totalCommits: number; bugFixCommits: number };
hotspots: Hotspot[];
churn: { files: string[]; byFile: Record<string, FileChurnReport> };
cochange: { edges: CoChangeEdge[] };
risk: { files: string[]; byFile: Record<string, FileRiskReport> };
triage: { verdicts: FileVerdict[] };
adapter: { name: string }; // "local" | "github"
} Every numeric claim in risk is grounded by real commit SHAs in defectDensity.groundedIn — and the composite score is null when there is no bug-fix history to ground it in. The CLI never fabricates a 0 to mean "no signal".
Top-level keys
version
The ANALYSIS_SCHEMA_VERSION constant from @prcompass/core. Independent of the package's npm version, so you can pin parsers without pinning the binary.
"version": "0.1.0" head
The resolved SHAs at the endpoints of --diff. Useful for caching and for cross-referencing tools that work in SHA space.
"head": {
"sha": "def567812345678123456781234567812345678",
"baseSha": "abc123412345678123456781234567812345678"
} diff
A header-style summary of the change set. fileCount is the number of files touched in --diff.
"diff": {
"fileCount": 8
} The per-file change set is not duplicated here — the triage pass (below) reports one verdict per file, and churn / risk carry the per-file metrics. The triage pass maps the diff statuses added | modified | removed | renamed | copied onto @prcompass/pr-triage-filter's vocabulary (removed → deleted, copied → renamed).
pr
PR metadata — present only when a GitHubAdapter was used; otherwise null. Local-mode runs never produce a non-null pr.
"pr": {
"title": "Migrate session middleware to JWE",
"body": "Replaces the cookie session store with JWE tokens.",
"number": 42,
"authorLogin": "alice"
} body and authorLogin are string | null.
mining
Bug-fix vs total commit stats over the walked history.
"mining": {
"totalCommits": 5000,
"bugFixCommits": 904
} hotspots
Bayesian-smoothed bug-fix density per file, sorted descending. An array of { file, density, bugFixCommits }.
"hotspots": [
{
"file": "src/billing/checkout.ts",
"density": 0.74,
"bugFixCommits": 12
}
] density pulls each per-file rate toward the repo-wide bug-fix rate (Bayesian smoothing) so files with very few touches don't dominate the ranking.
churn
Per-file commit count, bug-fix count, and first/last touch timestamps. files is the sorted list of paths; byFile maps each path to its report.
"churn": {
"files": ["src/billing/checkout.ts"],
"byFile": {
"src/billing/checkout.ts": {
"file": "src/billing/checkout.ts",
"commits": 38,
"bugFixCommits": 12,
"firstTouchedAt": "2025-09-12T09:14:00+00:00",
"lastTouchedAt": "2026-04-22T17:03:00+00:00"
}
}
} firstTouchedAt / lastTouchedAt are string | null (ISO-8601, as emitted by git log %aI).
cochange
File × file co-modification graph. Edges carry both raw counts and a Jaccard-style weight.
"cochange": {
"edges": [
{
"a": "src/billing/checkout.ts",
"b": "src/billing/invoice.ts",
"count": 18,
"jaccard": 0.82
}
]
} risk
Per-file composite risk. files is the sorted list of paths; byFile maps each path to its report.
"risk": {
"files": ["src/billing/checkout.ts"],
"byFile": {
"src/billing/checkout.ts": {
"file": "src/billing/checkout.ts",
"score": 0.78,
"defectDensity": {
"value": 0.316,
"groundedIn": ["abc1234", "def5678"]
},
"caveats": ["no-history"]
}
}
} score is a composite of smoothed defect density and log1p(commitCount). It is null when the repo has no bug-fix history (in which case caveats includes "no-bugfix-history"); score === null means "no signal" — never confuse it with 0. defectDensity.value is the Bayesian-smoothed bug-fix rate, and defectDensity.groundedIn lists the bug-fix commit SHAs that touched the file — the evidence behind the number.
triage
The Tier 1 file-priority verdicts from @prcompass/pr-triage-filter. Each verdict is { path, verdict, reason }.
"triage": {
"verdicts": [
{
"path": "pnpm-lock.yaml",
"verdict": "skip",
"reason": "lockfile or generated artifact (pnpm-lock.yaml)"
},
{
"path": "src/billing/checkout.ts",
"verdict": "review-candidate",
"reason": "source code change"
}
]
} verdict is one of skip | skim | review-candidate. reason is a human-readable explanation — it is not a stable identifier, so do not pattern-match on it; match on verdict instead.
adapter
Identifies which adapter produced the context. Useful in logs and replay scenarios.
"adapter": { "name": "local" } Stability
- The set of top-level keys is stable within a major version of
version. - New top-level keys may be added in minor versions.
- Removing or renaming a key is a major-version break.
- The
verdictenum (skip | skim | review-candidate) is part of the contract.
The schema is engineered to be JSON-clean — no Map, no Date, no functions. JSON.parse(formatJson(output)) always round-trips losslessly.