devs

package
v1.0.0-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: Apache-2.0, Apache-2.0 Imports: 27 Imported by: 0

README

Developers Analysis

Preface

Software is built by people. Understanding the activity and contributions of the development team is essential for project management, though it must be done with care to avoid misuse.

Problem

Managers and teams often lack visibility into the distribution of work:

  • "Who is the primary contributor to this project?"
  • "Is the workload distributed evenly?"
  • "Which languages is each developer working with?"

How analyzer solves it

The Devs analyzer computes high-level activity statistics for each developer. It tracks:

  • Number of commits.
  • Lines added, removed, and changed.
  • Breakdown of activity by programming language.

Historical context

Counting lines of code (LOC) and commits is one of the oldest forms of software metrics. While widely criticized as a measure of productivity (quality != quantity), it remains a valid measure of activity and impact.

Real world examples

  • Bus Factor: Identifying key developers whose absence would stall the project.
  • Language Expertise: Finding out who on the team is writing the most Go vs. Python code.

How analyzer works here

  1. Identity Merging: Uses the IdentityDetector to merge multiple emails/names for the same person (using .mailmap or heuristics).
  2. Diff Analysis: For every commit, it calculates the line stats (added/removed/changed).
  3. Aggregation: Aggregates these stats per author and per time interval (tick).
  4. Language Detection: Maps files to languages to provide a language-specific breakdown.

Limitations

  • LOC is not Productivity: This analyzer does not measure code quality or problem-solving value. A deletion of 1000 lines can be more valuable than an addition of 1000 lines.
  • Squashed Commits: Squashing commits can obscure individual contributions.

Metrics

The devs analyzer computes the following metrics. Each metric implements the Metric[In, Out] interface from pkg/metrics, making them reusable across output formats (plot, JSON, YAML).

developers

Type: list

Per-developer contribution statistics including commits, lines added/removed, language breakdown, and activity timeline. Developers are sorted by commit count.

Output fields:

  • id - Developer identifier
  • name - Developer name (from identity resolution)
  • commits - Total number of commits
  • lines_added - Total lines added
  • lines_removed - Total lines removed
  • lines_changed - Total lines changed (modifications)
  • net_lines - Net line change (added - removed)
  • languages - Map of language to line stats
  • first_tick - First active time period
  • last_tick - Last active time period
  • active_ticks - Number of periods with activity
languages

Type: list

Per-language contribution statistics showing total lines and contributor breakdown. Languages are sorted by total lines added.

Output fields:

  • name - Language name
  • total_lines - Total lines added in this language
  • contributors - Map of developer ID to lines added
bus_factor

Type: risk

Knowledge concentration risk per language. Measures how dependent each language's codebase is on individual contributors.

Risk levels:

  • CRITICAL - Single developer owns >= 90% of codebase
  • HIGH - Single developer owns >= 80%
  • MEDIUM - Single developer owns >= 60%
  • LOW - Single developer owns < 60%

Output fields:

  • language - Language name
  • primary_dev_id - ID of top contributor
  • primary_dev_name - Name of top contributor
  • primary_percentage - Percentage owned by primary
  • secondary_dev_id - ID of second contributor (if exists)
  • secondary_dev_name - Name of second contributor
  • secondary_percentage - Percentage owned by secondary
  • risk_level - Risk classification
activity

Type: time_series

Time-series of commit activity per tick, broken down by developer. Shows contribution velocity over the analysis period.

Output fields:

  • tick - Time period index
  • by_developer - Map of developer ID to commit count
  • total_commits - Total commits in this tick
churn

Type: time_series

Time-series of lines added and removed per tick. High churn may indicate refactoring, feature development, or instability.

Output fields:

  • tick - Time period index
  • lines_added - Lines added in this tick
  • lines_removed - Lines removed in this tick
  • net_change - Net line change (added - removed)
aggregate

Type: aggregate

Aggregate statistics across all developers and the analysis period.

Output fields:

  • total_commits - Total commits across all developers
  • total_lines_added - Total lines added
  • total_lines_removed - Total lines removed
  • total_developers - Number of unique developers
  • active_developers - Developers with commits in recent 30% of period
  • analysis_period_ticks - Number of time periods analyzed

Further plans

  • More nuanced metrics (e.g., "churn" vs. "productive code").

Documentation

Overview

Package devs provides devs functionality.

Index

Constants

View Source
const (
	ConfigDevsConsiderEmptyCommits  = "Devs.ConsiderEmptyCommits"
	ConfigDevsAnonymize             = "Devs.Anonymize"
	ConfigDevsBusFactorThreshold    = "Devs.BusFactorThreshold"
	ConfigDevsRiskThresholdCritical = "Devs.RiskThresholdCritical"
	ConfigDevsRiskThresholdHigh     = "Devs.RiskThresholdHigh"
	ConfigDevsRiskThresholdMedium   = "Devs.RiskThresholdMedium"
	ConfigDevsActiveThresholdRatio  = "Devs.ActiveThresholdRatio"
	ConfigDevsDefaultActiveDays     = "Devs.DefaultActiveDays"
	ConfigDevsHLLPrecision          = "Devs.HLLPrecision"
)

Configuration option keys for the devs analyzer.

View Source
const (
	ThresholdCritical = 90.0
	ThresholdHigh     = 80.0
	ThresholdMedium   = 60.0
)

Risk thresholds.

View Source
const (
	KindDeveloper = "developer"
	KindLanguage  = "language"
	KindBusFactor = "bus_factor"
	KindActivity  = "activity"
	KindChurn     = "churn"
	KindAggregate = "aggregate"
)

Store record kind constants.

View Source
const ActiveThresholdRatio = 0.7

ActiveThresholdRatio defines what portion of analysis period counts as "recent". Used as fallback when TickSize is not available.

View Source
const DefaultActiveDays = 90

DefaultActiveDays is the time-based threshold for considering a developer "active". Developers with commits in the last 90 days are counted as active.

Variables

View Source
var (
	ErrInvalidPeopleDict = errors.New("devs: invalid ReversedPeopleDict in report")
)

Error definitions for the devs analyzer.

Functions

func AggregateCommitsToTicks

func AggregateCommitsToTicks(
	commitDevData map[string]*CommitDevData,
	commitsByTick map[int][]gitlib.Hash,
) map[int]map[int]*DevTick

AggregateCommitsToTicks builds per-tick per-developer data from per-commit data grouped by the commits_by_tick mapping.

func GenerateChart

func GenerateChart(report analyze.Report) (components.Charter, error)

GenerateChart creates a stacked bar chart showing developer activity over time.

func GenerateDashboard

func GenerateDashboard(report analyze.Report, writer io.Writer) error

GenerateDashboard creates the developer analytics dashboard HTML.

func GenerateSections

func GenerateSections(report analyze.Report) ([]plotpage.Section, error)

GenerateSections returns the dashboard sections without rendering.

func GenerateStoreSections

func GenerateStoreSections(reader analyze.ReportReader) ([]plotpage.Section, error)

GenerateStoreSections reads pre-computed devs data from a ReportReader and builds the same dashboard sections as GenerateSections, without materializing a full Report or recomputing metrics.

func RegisterDevPlotSections

func RegisterDevPlotSections()

RegisterDevPlotSections registers the plot section renderer for the devs analyzer.

Types

type ActivityData

type ActivityData struct {
	Tick         int                `json:"tick"                 yaml:"tick"`
	StartTime    string             `json:"start_time,omitempty" yaml:"start_time,omitempty"`
	EndTime      string             `json:"end_time,omitempty"   yaml:"end_time,omitempty"`
	ByDeveloper  []DeveloperCommits `json:"by_developer"         yaml:"by_developer"`
	TotalCommits int                `json:"total_commits"        yaml:"total_commits"`
}

ActivityData contains time-series activity for a single tick.

type ActivityMetric

type ActivityMetric struct {
	metrics.MetricMeta
}

ActivityMetric computes time-series commit activity.

func NewActivityMetric

func NewActivityMetric() *ActivityMetric

NewActivityMetric creates the activity metric.

func (*ActivityMetric) Compute

func (m *ActivityMetric) Compute(input *TickData) []ActivityData

Compute calculates activity time series from tick data.

type AggregateData

type AggregateData struct {
	TotalCommits              int    `json:"total_commits"               yaml:"total_commits"`
	TotalLinesAdded           int    `json:"total_lines_added"           yaml:"total_lines_added"`
	TotalLinesRemoved         int    `json:"total_lines_removed"         yaml:"total_lines_removed"`
	TotalDevelopers           int    `json:"total_developers"            yaml:"total_developers"`
	ActiveDevelopers          int    `json:"active_developers"           yaml:"active_developers"`
	EstimatedTotalDevelopers  uint64 `json:"estimated_total_developers"  yaml:"estimated_total_developers"`
	EstimatedActiveDevelopers uint64 `json:"estimated_active_developers" yaml:"estimated_active_developers"`
	AnalysisPeriodTicks       int    `json:"analysis_period_ticks"       yaml:"analysis_period_ticks"`
	ProjectBusFactor          int    `json:"project_bus_factor"          yaml:"project_bus_factor"`
	TotalLanguages            int    `json:"total_languages"             yaml:"total_languages"`
}

AggregateData contains summary statistics.

type AggregateInput

type AggregateInput struct {
	Developers []DeveloperData
	Languages  []LanguageData
	Ticks      map[int]map[int]*DevTick
	TickSize   time.Duration
}

AggregateInput is the input for aggregate computation.

type AggregateMetric

type AggregateMetric struct {
	metrics.MetricMeta
}

AggregateMetric computes summary statistics.

func NewAggregateMetric

func NewAggregateMetric() *AggregateMetric

NewAggregateMetric creates the aggregate metric.

func (*AggregateMetric) Compute

func (m *AggregateMetric) Compute(input AggregateInput) AggregateData

Compute calculates aggregate statistics.

func (*AggregateMetric) ComputeWithOptions

func (m *AggregateMetric) ComputeWithOptions(input AggregateInput, opts MetricOptions) AggregateData

ComputeWithOptions calculates aggregate statistics with configurable thresholds.

type Analyzer

type Analyzer struct {
	*analyze.BaseHistoryAnalyzer[*ComputedMetrics]
	common.IdentityMixin

	TreeDiff  *plumbing.TreeDiffAnalyzer
	Ticks     *plumbing.TicksSinceStart
	Languages *plumbing.LanguagesDetectionAnalyzer
	LineStats *plumbing.LinesStatsCalculator

	ConsiderEmptyCommits bool
	Anonymize            bool
	// contains filtered or unexported fields
}

Analyzer calculates per-developer line statistics across commit history.

func NewAnalyzer

func NewAnalyzer() *Analyzer

NewAnalyzer creates a new devs analyzer.

func (*Analyzer) ApplySnapshot

func (a *Analyzer) ApplySnapshot(snap analyze.PlumbingSnapshot)

ApplySnapshot restores plumbing state from a snapshot.

func (*Analyzer) Boot

func (a *Analyzer) Boot() error

Boot restores the analyzer from hibernated state. Re-initializes the merge tracker for the next chunk.

func (*Analyzer) Configure

func (a *Analyzer) Configure(facts map[string]any) error

Configure configures the analyzer with the given facts.

func (*Analyzer) Consume

func (a *Analyzer) Consume(_ context.Context, ac *analyze.Context) (analyze.TC, error)

Consume processes a single commit and returns a TC with per-commit dev stats.

func (*Analyzer) ExtractCommitTimeSeries

func (a *Analyzer) ExtractCommitTimeSeries(report analyze.Report) map[string]any

ExtractCommitTimeSeries extracts per-commit dev stats from a finalized report.

func (*Analyzer) Fork

func (a *Analyzer) Fork(n int) []analyze.HistoryAnalyzer

Fork creates independent copies of the analyzer for parallel processing.

func (*Analyzer) GenerateChart

func (a *Analyzer) GenerateChart(report analyze.Report) (components.Charter, error)

GenerateChart creates a chart for the history analyzer.

func (*Analyzer) GenerateDashboardForAnalyzer

func (a *Analyzer) GenerateDashboardForAnalyzer(report analyze.Report, writer io.Writer) error

GenerateDashboardForAnalyzer creates the full dashboard for this analyzer.

func (*Analyzer) GenerateSections

func (a *Analyzer) GenerateSections(report analyze.Report) ([]plotpage.Section, error)

GenerateSections returns the dashboard sections for combined reports.

func (*Analyzer) Hibernate

func (a *Analyzer) Hibernate() error

Hibernate compresses the analyzer's state to reduce memory usage. Resets the merge tracker since processed commits won't be seen again during streaming (commits are processed chronologically).

func (*Analyzer) Initialize

func (a *Analyzer) Initialize(_ *gitlib.Repository) error

Initialize prepares the analyzer for processing commits.

func (*Analyzer) Merge

func (a *Analyzer) Merge(_ []analyze.HistoryAnalyzer)

Merge is a no-op.

func (*Analyzer) ReleaseSnapshot

func (a *Analyzer) ReleaseSnapshot(_ analyze.PlumbingSnapshot)

ReleaseSnapshot is a no-op for devs.

func (*Analyzer) SnapshotPlumbing

func (a *Analyzer) SnapshotPlumbing() analyze.PlumbingSnapshot

SnapshotPlumbing captures the current plumbing state.

func (*Analyzer) WriteToStore

func (a *Analyzer) WriteToStore(ctx context.Context, ticks []analyze.TICK, w analyze.ReportWriter) error

WriteToStore implements analyze.StoreWriter. It extracts per-commit dev data from TICKs, computes all metrics, and streams pre-computed results as individual records:

  • "developer": per-developer DeveloperData records (sorted by commits desc).
  • "language": per-language LanguageData records (sorted by total lines desc).
  • "bus_factor": per-language BusFactorData records (sorted by risk).
  • "activity": per-tick ActivityData records (sorted by tick).
  • "churn": per-tick ChurnData records (sorted by tick).
  • "aggregate": single AggregateData record.

type BusFactorData

type BusFactorData struct {
	Language          string  `json:"language"                       yaml:"language"`
	BusFactor         int     `json:"bus_factor"                     yaml:"bus_factor"`
	TotalContributors int     `json:"total_contributors"             yaml:"total_contributors"`
	PrimaryDevID      int     `json:"primary_dev_id"                 yaml:"primary_dev_id"`
	PrimaryDevName    string  `json:"primary_dev_name"               yaml:"primary_dev_name"`
	PrimaryDevEmail   string  `json:"primary_dev_email,omitempty"    yaml:"primary_dev_email,omitempty"`
	PrimaryPct        float64 `json:"primary_percentage"             yaml:"primary_percentage"`
	SecondaryDevID    int     `json:"secondary_dev_id,omitempty"     yaml:"secondary_dev_id,omitempty"`
	SecondaryDevName  string  `json:"secondary_dev_name,omitempty"   yaml:"secondary_dev_name,omitempty"`
	SecondaryDevEmail string  `json:"secondary_dev_email,omitempty"  yaml:"secondary_dev_email,omitempty"`
	SecondaryPct      float64 `json:"secondary_percentage,omitempty" yaml:"secondary_percentage,omitempty"`
	RiskLevel         string  `json:"risk_level"                     yaml:"risk_level"`
}

BusFactorData contains knowledge concentration data for a language. BusFactor follows the CHAOSS Contributor Absence Factor methodology: the smallest number of contributors responsible for 50% of total contributions.

type BusFactorInput

type BusFactorInput struct {
	Languages []LanguageData
	Names     []string
}

BusFactorInput is the input for bus factor computation.

type BusFactorMetric

type BusFactorMetric struct {
	metrics.MetricMeta
}

BusFactorMetric computes knowledge concentration risk per language.

func NewBusFactorMetric

func NewBusFactorMetric() *BusFactorMetric

NewBusFactorMetric creates the bus factor metric.

func (*BusFactorMetric) Compute

func (m *BusFactorMetric) Compute(input BusFactorInput) []BusFactorData

Compute calculates bus factor risk from language data. Contributors map values represent total contribution (Added+Removed).

func (*BusFactorMetric) ComputeWithOptions

func (m *BusFactorMetric) ComputeWithOptions(input BusFactorInput, opts MetricOptions) []BusFactorData

ComputeWithOptions calculates bus factor risk with configurable thresholds.

type ChurnData

type ChurnData struct {
	Tick      int    `json:"tick"                 yaml:"tick"`
	StartTime string `json:"start_time,omitempty" yaml:"start_time,omitempty"`
	EndTime   string `json:"end_time,omitempty"   yaml:"end_time,omitempty"`
	Added     int    `json:"lines_added"          yaml:"lines_added"`
	Removed   int    `json:"lines_removed"        yaml:"lines_removed"`
	Net       int    `json:"net_change"           yaml:"net_change"`
}

ChurnData contains code churn for a single tick.

type ChurnMetric

type ChurnMetric struct {
	metrics.MetricMeta
}

ChurnMetric computes time-series code churn.

func NewChurnMetric

func NewChurnMetric() *ChurnMetric

NewChurnMetric creates the churn metric.

func (*ChurnMetric) Compute

func (m *ChurnMetric) Compute(input *TickData) []ChurnData

Compute calculates churn time series from tick data.

type CommitDevData

type CommitDevData struct {
	Commits   int                              `json:"commits"`
	Added     int                              `json:"lines_added"`
	Removed   int                              `json:"lines_removed"`
	Changed   int                              `json:"lines_changed"`
	AuthorID  int                              `json:"author_id"`
	Languages map[string]pkgplumbing.LineStats `json:"languages,omitempty"`
}

CommitDevData holds aggregate dev stats for a single commit.

type ComputedMetrics

type ComputedMetrics struct {
	Ticks      map[int]map[int]*DevTick `json:"-"          yaml:"-"`
	TickSize   time.Duration            `json:"-"          yaml:"-"`
	Aggregate  AggregateData            `json:"aggregate"  yaml:"aggregate"`
	Developers []DeveloperData          `json:"developers" yaml:"developers"`
	Languages  []LanguageData           `json:"languages"  yaml:"languages"`
	BusFactor  []BusFactorData          `json:"busfactor"  yaml:"busfactor"`
	Activity   []ActivityData           `json:"activity"   yaml:"activity"`
	Churn      []ChurnData              `json:"churn"      yaml:"churn"`
	// contains filtered or unexported fields
}

ComputedMetrics holds all computed metric results for the devs analyzer. This is populated by running each metric's Compute method.

func ComputeAllMetrics

func ComputeAllMetrics(report analyze.Report) (*ComputedMetrics, error)

ComputeAllMetrics runs all devs metrics and returns the results.

func ComputeAllMetricsWithOptions

func ComputeAllMetricsWithOptions(report analyze.Report, opts MetricOptions) (*ComputedMetrics, error)

ComputeAllMetricsWithOptions runs all devs metrics with configurable thresholds.

func (*ComputedMetrics) AnalyzerName

func (m *ComputedMetrics) AnalyzerName() string

AnalyzerName returns the analyzer identifier.

func (*ComputedMetrics) ToJSON

func (m *ComputedMetrics) ToJSON() any

ToJSON returns the metrics in JSON-serializable format.

func (*ComputedMetrics) ToYAML

func (m *ComputedMetrics) ToYAML() any

ToYAML returns the metrics in YAML-serializable format.

type DashboardData

type DashboardData struct {
	Metrics      *ComputedMetrics
	TopLanguages []string // Top N language names for radar chart.
}

DashboardData wraps ComputedMetrics with rendering-specific data.

type DevTick

type DevTick struct {
	pkgplumbing.LineStats

	Languages map[string]pkgplumbing.LineStats
	Commits   int
}

DevTick is the statistics for a development tick and a particular developer.

type DeveloperCommits

type DeveloperCommits struct {
	DevID   int `json:"dev_id"  yaml:"dev_id"`
	Commits int `json:"commits" yaml:"commits"`
}

DeveloperCommits holds a developer's commit count within a single tick.

type DeveloperData

type DeveloperData struct {
	ID          int                  `json:"id"              yaml:"id"`
	Name        string               `json:"name"            yaml:"name"`
	Email       string               `json:"email,omitempty" yaml:"email,omitempty"`
	Commits     int                  `json:"commits"         yaml:"commits"`
	Added       int                  `json:"lines_added"     yaml:"lines_added"`
	Removed     int                  `json:"lines_removed"   yaml:"lines_removed"`
	Changed     int                  `json:"lines_changed"   yaml:"lines_changed"`
	NetLines    int                  `json:"net_lines"       yaml:"net_lines"`
	Languages   []LanguageStatsEntry `json:"languages"       yaml:"languages"`
	FirstTick   int                  `json:"first_tick"      yaml:"first_tick"`
	LastTick    int                  `json:"last_tick"       yaml:"last_tick"`
	ActiveTicks int                  `json:"active_ticks"    yaml:"active_ticks"`
	// contains filtered or unexported fields
}

DeveloperData contains computed data for a single developer.

type DevelopersMetric

type DevelopersMetric struct {
	metrics.MetricMeta
}

DevelopersMetric computes per-developer statistics.

func NewDevelopersMetric

func NewDevelopersMetric() *DevelopersMetric

NewDevelopersMetric creates the developers metric.

func (*DevelopersMetric) Compute

func (m *DevelopersMetric) Compute(input *TickData) []DeveloperData

Compute calculates developer statistics from tick data.

type LanguageData

type LanguageData struct {
	Name              string      `json:"name"               yaml:"name"`
	TotalLines        int         `json:"total_lines"        yaml:"total_lines"`
	TotalContribution int         `json:"total_contribution" yaml:"total_contribution"`
	Contributors      map[int]int `json:"contributors"       yaml:"contributors"`
}

LanguageData contains computed data for a programming language.

type LanguageStatsEntry

type LanguageStatsEntry struct {
	Language string `json:"language" yaml:"language"`
	Added    int    `json:"added"    yaml:"added"`
	Removed  int    `json:"removed"  yaml:"removed"`
	Changed  int    `json:"changed"  yaml:"changed"`
}

LanguageStatsEntry holds line stats for a single language.

type LanguagesMetric

type LanguagesMetric struct {
	metrics.MetricMeta
}

LanguagesMetric computes per-language statistics.

func NewLanguagesMetric

func NewLanguagesMetric() *LanguagesMetric

NewLanguagesMetric creates the languages metric.

func (*LanguagesMetric) Compute

func (m *LanguagesMetric) Compute(developers []DeveloperData) []LanguageData

Compute calculates language statistics from developer data.

type MetricOptions

type MetricOptions struct {
	BusFactorThreshold    float64
	RiskThresholdCritical float64
	RiskThresholdHigh     float64
	RiskThresholdMedium   float64
	ActiveThresholdRatio  float64
	DefaultActiveDays     int
	HLLPrecision          int
}

MetricOptions holds configurable thresholds for devs metric computation.

func DefaultMetricOptions

func DefaultMetricOptions() MetricOptions

DefaultMetricOptions returns MetricOptions populated with package-level defaults.

type TickData

type TickData struct {
	Ticks      map[int]map[int]*DevTick
	Names      []string
	TickSize   time.Duration
	TickBounds map[int]analyze.TickBounds
	DevSketch  *hll.Sketch `json:"-" yaml:"-"`
}

TickData is the raw input data for devs metrics computation.

func ParseTickDataWithPrecision

func ParseTickDataWithPrecision(report analyze.Report, precision int) (*TickData, error)

ParseTickDataWithPrecision extracts TickData from an analyzer report using a custom HLL precision.

type TickDevData

type TickDevData struct {
	// DevData maps commit hash hex to per-commit developer statistics.
	DevData map[string]*CommitDevData
	// contains filtered or unexported fields
}

TickDevData is the per-tick aggregated payload stored in analyze.TICK.Data. It groups all per-commit developer data within one time bucket.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL