tracker
¶
Full name: tenets.core.momentum.tracker
tracker¶
Velocity tracker module for development momentum analysis.
This module provides the main tracking functionality for development velocity and momentum. It analyzes git history to understand development patterns, team productivity, and project velocity trends over time.
The VelocityTracker class orchestrates the analysis of commits, code changes, and contributor activity to provide actionable insights into team momentum.
Classes¶
DailyVelocitydataclass
¶
DailyVelocity(date: datetime, commits: int = 0, lines_added: int = 0, lines_removed: int = 0, files_changed: int = 0, contributors: Set[str] = set(), pull_requests: int = 0, issues_closed: int = 0, velocity_points: float = 0.0, productivity_score: float = 0.0)
Velocity metrics for a single day.
Tracks development activity and productivity for a specific day, used for building velocity trends and burndown charts.
ATTRIBUTE | DESCRIPTION |
---|---|
date | Date of activity TYPE: |
commits | Number of commits TYPE: |
lines_added | Lines of code added TYPE: |
lines_removed | Lines of code removed TYPE: |
files_changed | Number of files modified TYPE: |
contributors | Set of active contributors |
pull_requests | Number of PRs merged TYPE: |
issues_closed | Number of issues closed TYPE: |
velocity_points | Calculated velocity points TYPE: |
productivity_score | Daily productivity score TYPE: |
WeeklyVelocitydataclass
¶
WeeklyVelocity(week_start: datetime, week_end: datetime, week_number: int, daily_velocities: List[DailyVelocity] = list(), total_commits: int = 0, total_lines_changed: int = 0, unique_contributors: Set[str] = set(), avg_daily_velocity: float = 0.0, velocity_variance: float = 0.0, sprint_completion: Optional[float] = None)
Velocity metrics aggregated by week.
Provides week-level velocity metrics for sprint tracking and longer-term trend analysis.
ATTRIBUTE | DESCRIPTION |
---|---|
week_start | Start date of the week TYPE: |
week_end | End date of the week TYPE: |
week_number | Week number in year TYPE: |
daily_velocities | List of daily velocities TYPE: |
total_commits | Total commits in week TYPE: |
total_lines_changed | Total lines changed TYPE: |
unique_contributors | Unique contributors in week |
avg_daily_velocity | Average daily velocity TYPE: |
velocity_variance | Variance in daily velocity TYPE: |
sprint_completion | Sprint completion percentage if applicable |
ContributorVelocitydataclass
¶
ContributorVelocity(name: str, email: str, commits: int = 0, lines_added: int = 0, lines_removed: int = 0, files_touched: Set[str] = set(), active_days: Set[str] = set(), first_commit: Optional[datetime] = None, last_commit: Optional[datetime] = None, velocity_trend: str = 'stable', productivity_score: float = 0.0, consistency_score: float = 0.0, impact_score: float = 0.0, collaboration_score: float = 0.0, specialization_areas: List[str] = list())
Velocity metrics for an individual contributor.
Tracks individual developer productivity and contribution patterns to understand team dynamics and individual performance.
ATTRIBUTE | DESCRIPTION |
---|---|
name | Contributor name TYPE: |
email | Contributor email TYPE: |
commits | Total commits TYPE: |
lines_added | Total lines added TYPE: |
lines_removed | Total lines removed TYPE: |
files_touched | Set of files modified |
active_days | Days with commits |
first_commit | First commit date in period |
last_commit | Last commit date in period |
velocity_trend | Individual velocity trend TYPE: |
productivity_score | Individual productivity score TYPE: |
consistency_score | Consistency of contributions TYPE: |
impact_score | Impact/influence score TYPE: |
collaboration_score | Collaboration with others TYPE: |
specialization_areas | Areas of expertise |
MomentumReportdataclass
¶
MomentumReport(period_start: datetime, period_end: datetime, total_commits: int = 0, total_contributors: int = 0, active_contributors: int = 0, momentum_metrics: Optional[MomentumMetrics] = None, velocity_trend: Optional[VelocityTrend] = None, sprint_metrics: Optional[SprintMetrics] = None, team_metrics: Optional[TeamMetrics] = None, individual_velocities: List[ContributorVelocity] = list(), daily_breakdown: List[DailyVelocity] = list(), weekly_breakdown: List[WeeklyVelocity] = list(), productivity_metrics: Optional[ProductivityMetrics] = None, recommendations: List[str] = list(), health_score: float = 0.0)
Comprehensive momentum and velocity analysis report.
Aggregates all velocity metrics and trends to provide a complete picture of development momentum and team productivity.
ATTRIBUTE | DESCRIPTION |
---|---|
period_start | Start date of analysis period TYPE: |
period_end | End date of analysis period TYPE: |
total_commits | Total commits in period TYPE: |
total_contributors | Total unique contributors TYPE: |
active_contributors | Currently active contributors TYPE: |
momentum_metrics | Overall momentum metrics TYPE: |
velocity_trend | Velocity trend analysis TYPE: |
sprint_metrics | Sprint-based metrics TYPE: |
team_metrics | Team-level metrics TYPE: |
individual_velocities | Individual contributor velocities TYPE: |
daily_breakdown | Daily velocity breakdown TYPE: |
weekly_breakdown | Weekly velocity breakdown TYPE: |
productivity_metrics | Productivity analysis TYPE: |
recommendations | Actionable recommendations |
health_score | Overall momentum health score TYPE: |
Attributes¶
avg_daily_velocityproperty
¶
Calculate average daily velocity.
RETURNS | DESCRIPTION |
---|---|
float | Average velocity per day TYPE: |
velocity_stabilityproperty
¶
Calculate velocity stability score.
Lower variance indicates more stable/predictable velocity.
RETURNS | DESCRIPTION |
---|---|
float | Stability score (0-100) TYPE: |
Functions¶
to_dict¶
Convert report to dictionary.
RETURNS | DESCRIPTION |
---|---|
Dict[str, Any] | Dict[str, Any]: Dictionary representation |
Source code in tenets/core/momentum/tracker.py
def to_dict(self) -> Dict[str, Any]:
"""Convert report to dictionary.
Returns:
Dict[str, Any]: Dictionary representation
"""
return {
"period": {
"start": self.period_start.isoformat(),
"end": self.period_end.isoformat(),
"days": (self.period_end - self.period_start).days,
},
"summary": {
"total_commits": self.total_commits,
"total_contributors": self.total_contributors,
"active_contributors": self.active_contributors,
"health_score": round(self.health_score, 1),
},
"momentum": self.momentum_metrics.to_dict() if self.momentum_metrics else {},
"velocity_trend": self.velocity_trend.to_dict() if self.velocity_trend else {},
"sprint_metrics": self.sprint_metrics.to_dict() if self.sprint_metrics else {},
"team_metrics": self.team_metrics.to_dict() if self.team_metrics else {},
"productivity": (
self.productivity_metrics.to_dict() if self.productivity_metrics else {}
),
"top_contributors": [
{
"name": c.name,
"commits": c.commits,
"productivity": round(c.productivity_score, 1),
}
for c in sorted(self.individual_velocities, key=lambda x: x.commits, reverse=True)[
:10
]
],
"recommendations": self.recommendations,
}
VelocityTracker¶
Main tracker for development velocity and momentum.
Orchestrates the analysis of git history to track development velocity, team productivity, and momentum trends over time.
ATTRIBUTE | DESCRIPTION |
---|---|
config | Configuration object |
logger | Logger instance |
git_analyzer | Git analyzer instance TYPE: |
Initialize velocity tracker.
PARAMETER | DESCRIPTION |
---|---|
config | TenetsConfig instance TYPE: |
Source code in tenets/core/momentum/tracker.py
Functions¶
track_momentum¶
track_momentum(repo_path: Path, period: str = 'last-month', team: bool = False, author: Optional[str] = None, team_mapping: Optional[Dict[str, List[str]]] = None, sprint_duration: int = 14, daily_breakdown: bool = False, interval: str = 'weekly', exclude_bots: bool = True, **kwargs) -> MomentumReport
Track development momentum for a repository.
Analyzes git history to calculate velocity metrics, identify trends, and provide insights into development momentum.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to git repository TYPE: |
period | Time period to analyze (e.g., "last-month", "30 days") TYPE: |
team | Whether to include team-wide metrics TYPE: |
author | Specific author to analyze |
team_mapping | Optional mapping of team names to members |
sprint_duration | Sprint length in days for sprint metrics TYPE: |
daily_breakdown | Whether to include daily velocity data TYPE: |
interval | Aggregation interval (daily, weekly, monthly) TYPE: |
exclude_bots | Whether to exclude bot commits from analysis TYPE: |
RETURNS | DESCRIPTION |
---|---|
MomentumReport | Comprehensive momentum analysis TYPE: |
Example
tracker = VelocityTracker(config) report = tracker.track_momentum( ... Path("."), ... period="last-quarter", ... team=True ... ) print(f"Team velocity: {report.avg_daily_velocity}")
Source code in tenets/core/momentum/tracker.py
def track_momentum(
self,
repo_path: Path,
period: str = "last-month",
team: bool = False,
author: Optional[str] = None,
team_mapping: Optional[Dict[str, List[str]]] = None,
sprint_duration: int = 14,
daily_breakdown: bool = False,
interval: str = "weekly",
exclude_bots: bool = True,
**kwargs, # Accept additional parameters for compatibility
) -> MomentumReport:
"""Track development momentum for a repository.
Analyzes git history to calculate velocity metrics, identify trends,
and provide insights into development momentum.
Args:
repo_path: Path to git repository
period: Time period to analyze (e.g., "last-month", "30 days")
team: Whether to include team-wide metrics
author: Specific author to analyze
team_mapping: Optional mapping of team names to members
sprint_duration: Sprint length in days for sprint metrics
daily_breakdown: Whether to include daily velocity data
interval: Aggregation interval (daily, weekly, monthly)
exclude_bots: Whether to exclude bot commits from analysis
Returns:
MomentumReport: Comprehensive momentum analysis
Example:
>>> tracker = VelocityTracker(config)
>>> report = tracker.track_momentum(
... Path("."),
... period="last-quarter",
... team=True
... )
>>> print(f"Team velocity: {report.avg_daily_velocity}")
"""
self.logger.debug(f"Tracking momentum for {repo_path} over {period}")
import time
start_time = time.time()
# Initialize git analyzer
self.git_analyzer = GitAnalyzer(repo_path)
if not self.git_analyzer.is_repo():
self.logger.warning(f"Not a git repository: {repo_path}")
return MomentumReport(period_start=datetime.now(), period_end=datetime.now())
# Parse period - check if since/until are provided in kwargs
if "since" in kwargs and "until" in kwargs:
period_start = kwargs["since"]
period_end = kwargs["until"]
elif "since" in kwargs:
period_start = kwargs["since"]
period_end = datetime.now()
else:
period_start, period_end = self._parse_period(period)
# Initialize report
report = MomentumReport(period_start=period_start, period_end=period_end)
# Get commit data
self.logger.info(f"Fetching commits from {period_start} to {period_end}")
fetch_start = time.time()
commits = self._get_commits_in_period(period_start, period_end, author, exclude_bots)
self.logger.info(f"Fetched {len(commits)} commits in {time.time() - fetch_start:.2f}s")
if not commits:
self.logger.info("No commits found in period")
return report
# Analyze daily velocity
analyze_start = time.time()
daily_data = self._analyze_daily_velocity(commits, period_start, period_end)
# Human-friendly timing (avoid confusing 0.00s output)
_elapsed = time.time() - analyze_start
_elapsed_str = "<0.01s" if _elapsed < 0.01 else f"{_elapsed:.2f}s"
self.logger.info(f"Analyzed daily velocity in {_elapsed_str}")
if daily_breakdown:
report.daily_breakdown = daily_data
# Analyze weekly velocity
if interval in ["weekly", "sprint"]:
report.weekly_breakdown = self._analyze_weekly_velocity(daily_data)
# Analyze individual velocities
report.individual_velocities = self._analyze_individual_velocities(
commits, period_start, period_end
)
# Calculate overall metrics
report.total_commits = len(commits)
report.total_contributors = len(
set(
c.author.email
for c in commits
if hasattr(c, "author")
and hasattr(c.author, "email")
and not is_bot_commit(getattr(c.author, "name", ""), getattr(c.author, "email", ""))
)
)
report.active_contributors = sum(
1
for v in report.individual_velocities
if v.last_commit and (datetime.now() - v.last_commit).days <= 7
)
# Calculate momentum metrics
report.momentum_metrics = calculate_momentum_metrics(
daily_data, report.individual_velocities
)
# Analyze velocity trend
report.velocity_trend = self._analyze_velocity_trend(daily_data, report.weekly_breakdown)
# Calculate sprint metrics if requested
if sprint_duration > 0:
report.sprint_metrics = self._calculate_sprint_metrics(daily_data, sprint_duration)
# Calculate team metrics if requested
if team:
report.team_metrics = self._calculate_team_metrics(
report.individual_velocities, team_mapping
)
# Calculate productivity metrics
report.productivity_metrics = self._calculate_productivity_metrics(report)
# Calculate health score
report.health_score = self._calculate_health_score(report)
# Generate recommendations
report.recommendations = self._generate_recommendations(report)
self.logger.debug(
f"Momentum tracking complete: {report.total_commits} commits, "
f"{report.total_contributors} contributors"
)
return report
Functions¶
is_bot_commit¶
Check if a commit is from a bot or automated system.
PARAMETER | DESCRIPTION |
---|---|
author_name | Commit author name TYPE: |
author_email | Commit author email TYPE: |
RETURNS | DESCRIPTION |
---|---|
bool | True if commit appears to be from a bot TYPE: |
Source code in tenets/core/momentum/tracker.py
def is_bot_commit(author_name: str, author_email: str) -> bool:
"""Check if a commit is from a bot or automated system.
Args:
author_name: Commit author name
author_email: Commit author email
Returns:
bool: True if commit appears to be from a bot
"""
# Convert to lowercase for case-insensitive comparison
name_lower = author_name.lower() if author_name else ""
email_lower = author_email.lower() if author_email else ""
# Check exact matches
if name_lower in BOT_PATTERNS or email_lower in BOT_PATTERNS:
return True
# Check patterns in name
bot_indicators = ["[bot]", "bot", "automation", "ci", "release", "deploy"]
for indicator in bot_indicators:
if indicator in name_lower:
return True
# Check email patterns
if "noreply" in email_lower or "bot@" in email_lower or "automated" in email_lower:
return True
# Check for GitHub/GitLab automated emails
if email_lower.endswith("@users.noreply.github.com"):
# Could be a real user, check if it has bot indicators
if any(bot in name_lower for bot in ["bot", "action", "automated"]):
return True
return False
track_momentum¶
track_momentum(repo_path: Path, period: str = 'last-month', config: Optional[TenetsConfig] = None, **kwargs) -> MomentumReport
Convenience function to track momentum.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to repository TYPE: |
period | Time period to analyze TYPE: |
config | Optional configuration TYPE: |
**kwargs | Additional arguments for tracker DEFAULT: |
RETURNS | DESCRIPTION |
---|---|
MomentumReport | Momentum analysis TYPE: |
Source code in tenets/core/momentum/tracker.py
def track_momentum(
repo_path: Path, period: str = "last-month", config: Optional[TenetsConfig] = None, **kwargs
) -> MomentumReport:
"""Convenience function to track momentum.
Args:
repo_path: Path to repository
period: Time period to analyze
config: Optional configuration
**kwargs: Additional arguments for tracker
Returns:
MomentumReport: Momentum analysis
"""
if config is None:
config = TenetsConfig()
tracker = VelocityTracker(config)
return tracker.track_momentum(repo_path, period, **kwargs)
track_team_velocity¶
track_team_velocity(repo_path: Path, period: str = 'last-month', team_mapping: Optional[Dict[str, List[str]]] = None, config: Optional[TenetsConfig] = None) -> TeamMetrics
Track team velocity metrics.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to repository TYPE: |
period | Time period to analyze TYPE: |
team_mapping | Team structure mapping |
config | Optional configuration TYPE: |
RETURNS | DESCRIPTION |
---|---|
TeamMetrics | Team velocity metrics TYPE: |
Source code in tenets/core/momentum/tracker.py
def track_team_velocity(
repo_path: Path,
period: str = "last-month",
team_mapping: Optional[Dict[str, List[str]]] = None,
config: Optional[TenetsConfig] = None,
) -> TeamMetrics:
"""Track team velocity metrics.
Args:
repo_path: Path to repository
period: Time period to analyze
team_mapping: Team structure mapping
config: Optional configuration
Returns:
TeamMetrics: Team velocity metrics
"""
if config is None:
config = TenetsConfig()
tracker = VelocityTracker(config)
report = tracker.track_momentum(repo_path, period, team=True, team_mapping=team_mapping)
return report.team_metrics
track_individual_velocity¶
track_individual_velocity(repo_path: Path, author: str, period: str = 'last-month', config: Optional[TenetsConfig] = None) -> Optional[ContributorVelocity]
Track individual contributor velocity.
PARAMETER | DESCRIPTION |
---|---|
repo_path | Path to repository TYPE: |
author | Author name or email TYPE: |
period | Time period to analyze TYPE: |
config | Optional configuration TYPE: |
RETURNS | DESCRIPTION |
---|---|
Optional[ContributorVelocity] | Optional[ContributorVelocity]: Individual velocity metrics |
Source code in tenets/core/momentum/tracker.py
def track_individual_velocity(
repo_path: Path, author: str, period: str = "last-month", config: Optional[TenetsConfig] = None
) -> Optional[ContributorVelocity]:
"""Track individual contributor velocity.
Args:
repo_path: Path to repository
author: Author name or email
period: Time period to analyze
config: Optional configuration
Returns:
Optional[ContributorVelocity]: Individual velocity metrics
"""
if config is None:
config = TenetsConfig()
tracker = VelocityTracker(config)
report = tracker.track_momentum(repo_path, period, author=author)
# Find the specific contributor
for velocity in report.individual_velocities:
if author.lower() in velocity.name.lower() or author.lower() in velocity.email.lower():
return velocity
return None