API reference

The package exposes a single top-level entry — there are no subpath imports. Modules are grouped by responsibility but everything ships from '@nkwib/pr-engine'.

High-level

analyze(ctx)

function analyze(ctx: AnalyzeContext): AnalysisOutput;

Runs the full pipeline: mineCommits → computeChurn → computeCochange → computeHotspots → computeRisk. Pure — no I/O, no Date.now(), no Math.random(). Same input → same output, bit-for-bit.

import { analyze, type AnalyzeContext, type AnalysisOutput } from '@nkwib/pr-engine';

const output: AnalysisOutput = analyze(ctx);
console.log(output.version);  // === ANALYSIS_SCHEMA_VERSION

ANALYSIS_SCHEMA_VERSION

const ANALYSIS_SCHEMA_VERSION: '0.1.0';

The schema version emitted as AnalysisOutput.version. Bumped when the output shape breaks compat. Independent of the package's npm version — consumers can rely on it for parsing without pinning the package.

AnalysisOutput

interface AnalysisOutput {
  readonly version: string;
  readonly head: { readonly sha: string; readonly baseSha: string };
  readonly pr: PrMetadata | null;
  readonly diff: { readonly fileCount: number; readonly files: readonly DiffFile[] };
  readonly mining: MineStats;
  readonly hotspots: HotspotsReport;
  readonly churn: ChurnReport;
  readonly cochange: CochangeReport;
  readonly risk: RiskReport;
}

Engines

mineCommits(opts)

function mineCommits(opts: { commits: readonly CommitRecord[] }): MinedCommits;

Classifies each commit as bug-fix or not, attaches a signal. The bundle also exposes BUGFIX_REGEX, isBugFixCommit, filterBugFixCommits for callers who want the heuristic without running the full pass.

computeChurn(opts)

function computeChurn(opts: { mined: MinedCommits }): ChurnReport;

Per-file commit count, bug-fix count, defect density, first/last touched.

computeCochange(opts)

function computeCochange(opts: { mined: MinedCommits }): CochangeReport;

File × file co-modification graph with Jaccard weights. Heaviest engine — O(C × F²) where F is files-per-commit. Default maxFilesPerCommit: 50 cap prevents pathological commits from blowing the budget.

computeHotspots(opts)

function computeHotspots(opts: { mined: MinedCommits }): HotspotsReport;

Bayesian-smoothed bug-fix score per file.

computeRisk(opts)

function computeRisk(opts: {
  mined: MinedCommits;
  hotspots: HotspotsReport;
  churn: ChurnReport;
  cochange: CochangeReport;
}): RiskReport;

Combines the four engine outputs into a per-file risk report with groundedIn SHA pointers and caveats.

Parsers

These convert raw git log stdout into typed records. Pure — pass strings, get data.

parseCommitMetadata(stdout)

function parseCommitMetadata(stdout: string): CommitMetadata[];

Parses the output of git log --format=… with the canonical separator format %x1e%H%x1f%P%x1f%aN%x1f%aI%x1f%B.

parseCommitFiles(stdout)

function parseCommitFiles(stdout: string): Map<string, string[]>;

Parses the output of git log --name-only --format='\x1eCOMMIT %H'. Returns a SHA → filesTouched map.

Bug-fix heuristic

const BUGFIX_REGEX: RegExp;
function isBugFixCommit(commit: CommitRecord): boolean;
function filterBugFixCommits(commits: readonly CommitRecord[]): CommitRecord[];

The default regex is the conservative one used internally by mineCommits. Override by writing your own filter and feeding the result back in.

Adapter contract

ProviderAdapter

interface ProviderAdapter {
  readonly name: string;             // 'local' | 'github' | …
  collect(): Promise<AnalyzeContext>;
}

Implement this interface to feed the engine from any source. The CLI package ships LocalAdapter (subprocess) and GitHubAdapter (Octokit + clone). The engine itself never imports adapters — that boundary is enforced by ESLint.

AnalyzeContext

interface AnalyzeContext {
  readonly commits: readonly CommitRecord[];
  readonly diff: {
    readonly baseSha: string;
    readonly headSha: string;
    readonly files: readonly DiffFile[];
  };
  readonly pr: PrMetadata | null;
}

What every adapter produces. The engine consumes only data, never callbacks.

Public types (selection)

The engine exports the full type vocabulary alongside its functions. Highlights:

TypeWhere it appears
CommitRecordinput to mineCommits, return of parseCommit*
MinedCommitsreturn of mineCommits, input to every other engine
ChurnReportreturn of computeChurn
CochangeReportreturn of computeCochange
HotspotsReportreturn of computeHotspots
RiskReportreturn of computeRisk; byFile[path].score ∈ [0, 1] \| null
DiffFileitems in AnalyzeContext.diff.files; carries status, additions, …
DiffStatus'added' \| 'modified' \| 'removed' \| 'renamed' \| 'copied'

See JSDoc on every export for the exhaustive shape.

Module entry

import {
  // High-level
  analyze,
  ANALYSIS_SCHEMA_VERSION,
  type AnalysisOutput,

  // Engines
  mineCommits,
  computeChurn,
  computeCochange,
  computeHotspots,
  computeRisk,

  // Parsers
  parseCommitMetadata,
  parseCommitFiles,
  BUGFIX_REGEX,
  isBugFixCommit,
  filterBugFixCommits,

  // Adapter contract
  type ProviderAdapter,
  type AnalyzeContext,
  type CommitRecord,
  type DiffFile,
  type DiffStatus
} from '@nkwib/pr-engine';

There is no subpath namespace — everything ships from '@nkwib/pr-engine'. New behaviour arrives as new top-level exports, never as @nkwib/pr-engine/something.

@nkwib/pr-engine Deterministic engine — mining, churn, cochange, hotspots, risk