Spec 017: Archobs Team Analysis¶
Problem¶
The archobs tool measures coupling, boundary health, and risk hotspots at the file and cluster level, but has no visibility into the human dimension: who works on what, how concentrated knowledge is, and where bus factor risk exists. This gap means:
- No author attribution in git extraction — the git history parser captures commit SHA, timestamp, message, and file status, but discards author information.
- No knowledge concentration metrics — a cluster maintained by a single developer is a different kind of risk than one with broad ownership, but these risks are invisible.
- No bus factor analysis — the minimum number of developers needed to cover the majority of a cluster's commits is a critical organizational health metric that has no representation in the pipeline.
Goal¶
Add author/team analysis to archobs that:
- Captures author names during git history extraction (additive column)
- Computes per-cluster author distribution, bus factor, and knowledge concentration (HHI)
- Exposes results via
archobs show teamwith table/json/csv output - Produces Parquet artifacts that the fitness check (spec 018) can consume
- Represents unavailable team history without implying that bus-factor analysis ran successfully
Non-Goals¶
- Author identity resolution (mapping multiple git names to one person)
- Blame-level analysis (line-by-line authorship)
- Team boundary definition or organizational chart mapping
- Email extraction or PII handling (author name only)
- Historical team metric trends (single-snapshot analysis)
Algorithm¶
Author Extraction¶
Add %an (author name) to the git log format string. The state machine parser gains an awaiting_author state between awaiting_ts and awaiting_msg. The resulting DataFrame adds an author column:
This is an additive change — existing consumers reference columns by name, not position. The if "author" in df.columns guard in the pipeline ensures backward compatibility with pre-existing .archobs/ artifacts.
Team Metrics (pure functions)¶
compute_author_stats(commits_df, file_metrics_df): - Join commits with file_metrics on path to get cluster_id - Group by (cluster_id, author) - Count commits and distinct files per author per cluster - Compute pct_of_cluster (author's commits / cluster total)
compute_bus_factor(author_stats_df, threshold=0.8): - For each cluster, sort authors by commit_count descending - Accumulate until cumulative percentage >= threshold - Bus factor = count of authors needed to reach threshold - Also return top author and their percentage
compute_knowledge_concentration(author_stats_df): - For each cluster, compute Herfindahl-Hirschman Index (HHI) - HHI = sum of squared market shares (pct_of_cluster^2) - Range: 0 (perfectly dispersed) to 1 (single author monopoly) - Also return author count per cluster
Pipeline Integration¶
In AnalysisRun.report(), after _enrich_cluster_commit_counts, always persist current-generation frames:
author_stats_df = compute_author_stats(commit_files_df, file_metrics_df)
bus_factor_df = compute_bus_factor(author_stats_df)
concentration_df = compute_knowledge_concentration(author_stats_df)
write_parquet(author_stats_df, store.base_path, "author_stats")
write_parquet(bus_factor_df, store.base_path, "bus_factor")
write_parquet(concentration_df, store.base_path, "concentration")
Writing schema-correct empty frames clears prior-generation team data. Consumers MUST treat empty team frames as unavailable analysis rather than as a measured zero-risk result.
Interface¶
CLI¶
When team analysis is unavailable, table output returns an explanatory no-data message and JSON output returns the valid empty collection [].
Acceptance scenario: Given a completed report with no author-bearing commit data, when a consumer runs archobs show team --format json, then the command exits successfully with [] and never emits a non-JSON explanatory string on stdout.
Parquet Artifacts¶
author_stats.parquet: cluster_id, author, commit_count, file_count, pct_of_cluster bus_factor.parquet: cluster_id, bus_factor, top_author, top_author_pct concentration.parquet: cluster_id, hhi, author_count
Files¶
| File | Action |
|---|---|
tools/archobs/src/archobs/git_history.py |
Modify — LOG_FORMAT + parser + author column |
tools/archobs/src/archobs/team_metrics.py |
Create |
tools/archobs/src/archobs/pipeline.py |
Modify — team metrics in report() |
tools/archobs/src/archobs/cli.py |
Modify — add show team |
tools/archobs/src/archobs/display.py |
Modify — add readers + format_team |
tools/archobs/tests/test_team_metrics.py |
Create |
tools/archobs/tests/test_core.py |
Modify |
tools/archobs/tests/test_show.py |
Modify |
Tests¶
- compute_author_stats with multiple authors
- compute_author_stats empty DataFrame
- compute_bus_factor single author = 1
- compute_bus_factor multiple authors
- compute_knowledge_concentration monopoly = 1.0
- compute_knowledge_concentration dispersed < 0.5
- Author column present in git extraction
- Pipeline produces author_stats.parquet
- show team table/json/csv formats
- show team CLI integration
- Empty team artifacts replace prior-generation values
- Empty team JSON output is
[], not plain text
Risks¶
| Risk | Mitigation |
|---|---|
| Author name variants (John vs john vs John D.) | Out of scope — note as limitation; future identity resolution |
| Large repos with many authors | Parquet is efficient; no in-memory scaling concern |
| Backward compat with existing artifacts | Persist current schema even when source author data is absent; consumers recognize empty artifacts as unavailable |