Skip to content

chronicle

Full name: tenets.core.git.chronicle

chronicle

Chronicle module for git history analysis.

This module provides functionality for analyzing and summarizing git repository history, including commit patterns, contributor activity, and development trends. It extracts historical insights to help understand project evolution and team dynamics over time.

The chronicle functionality provides a narrative view of repository changes, making it easy to understand what happened, when, and by whom.

Classes

CommitSummarydataclass

Python
CommitSummary(sha: str, author: str, email: str, date: datetime, message: str, files_changed: int = 0, lines_added: int = 0, lines_removed: int = 0, is_merge: bool = False, is_revert: bool = False, tags: List[str] = list(), branch: Optional[str] = None, issue_refs: List[str] = list(), pr_refs: List[str] = list())

Summary information for a single commit.

Provides a concise representation of a commit with key information for historical analysis and reporting.

ATTRIBUTEDESCRIPTION
sha

Commit SHA (short form)

TYPE:str

author

Commit author name

TYPE:str

email

Author email

TYPE:str

date

Commit date

TYPE:datetime

message

Commit message (first line)

TYPE:str

files_changed

Number of files changed

TYPE:int

lines_added

Lines added

TYPE:int

lines_removed

Lines removed

TYPE:int

is_merge

Whether this is a merge commit

TYPE:bool

is_revert

Whether this is a revert commit

TYPE:bool

tags

Associated tags

TYPE:List[str]

branch

Branch name if available

TYPE:Optional[str]

issue_refs

Referenced issue numbers

TYPE:List[str]

pr_refs

Referenced PR numbers

TYPE:List[str]

Attributes
net_linesproperty
Python
net_lines: int

Calculate net lines changed.

RETURNSDESCRIPTION
int

Lines added minus lines removed

TYPE:int

commit_typeproperty
Python
commit_type: str

Determine commit type from message.

RETURNSDESCRIPTION
str

Commit type (feat, fix, docs, etc.)

TYPE:str

Functions
to_dict
Python
to_dict() -> Dict[str, Any]

Convert to dictionary representation.

RETURNSDESCRIPTION
Dict[str, Any]

Dict[str, Any]: Dictionary representation

Source code in tenets/core/git/chronicle.py
Python
def to_dict(self) -> Dict[str, Any]:
    """Convert to dictionary representation.

    Returns:
        Dict[str, Any]: Dictionary representation
    """
    return {
        "sha": self.sha,
        "author": self.author,
        "email": self.email,
        "date": self.date.isoformat(),
        "message": self.message,
        "files_changed": self.files_changed,
        "lines_added": self.lines_added,
        "lines_removed": self.lines_removed,
        "type": self.commit_type,
        "is_merge": self.is_merge,
        "is_revert": self.is_revert,
        "tags": self.tags,
        "branch": self.branch,
        "issue_refs": self.issue_refs,
        "pr_refs": self.pr_refs,
    }

DayActivitydataclass

Python
DayActivity(date: datetime, commits: List[CommitSummary] = list(), total_commits: int = 0, unique_authors: Set[str] = set(), lines_added: int = 0, lines_removed: int = 0, files_touched: Set[str] = set(), commit_types: Dict[str, int] = dict(), peak_hour: Optional[int] = None, first_commit_time: Optional[datetime] = None, last_commit_time: Optional[datetime] = None)

Activity summary for a single day.

Aggregates all repository activity for a specific day to provide daily development rhythm insights.

ATTRIBUTEDESCRIPTION
date

Date of activity

TYPE:datetime

commits

List of commits on this day

TYPE:List[CommitSummary]

total_commits

Total commit count

TYPE:int

unique_authors

Set of unique authors

TYPE:Set[str]

lines_added

Total lines added

TYPE:int

lines_removed

Total lines removed

TYPE:int

files_touched

Set of files modified

TYPE:Set[str]

commit_types

Distribution of commit types

TYPE:Dict[str, int]

peak_hour

Hour with most commits

TYPE:Optional[int]

first_commit_time

Time of first commit

TYPE:Optional[datetime]

last_commit_time

Time of last commit

TYPE:Optional[datetime]

Attributes
net_linesproperty
Python
net_lines: int

Calculate net lines changed.

RETURNSDESCRIPTION
int

Net lines changed for the day

TYPE:int

productivity_scoreproperty
Python
productivity_score: float

Calculate daily productivity score.

RETURNSDESCRIPTION
float

Productivity score (0-100)

TYPE:float

ChronicleReportdataclass

Python
ChronicleReport(period_start: datetime, period_end: datetime, total_commits: int = 0, total_contributors: int = 0, commits: List[CommitSummary] = list(), daily_activity: List[DayActivity] = list(), contributor_stats: Dict[str, Dict[str, Any]] = dict(), commit_type_distribution: Dict[str, int] = dict(), file_change_frequency: List[Tuple[str, int]] = list(), hot_periods: List[Dict[str, Any]] = list(), quiet_periods: List[Dict[str, Any]] = list(), significant_events: List[Dict[str, Any]] = list(), trends: List[str] = list(), summary: str = '')

Comprehensive chronicle report of repository history.

Provides a complete narrative view of repository evolution including commits, contributors, trends, and significant events.

ATTRIBUTEDESCRIPTION
period_start

Start of chronicle period

TYPE:datetime

period_end

End of chronicle period

TYPE:datetime

total_commits

Total commits in period

TYPE:int

total_contributors

Total unique contributors

TYPE:int

commits

List of commit summaries

TYPE:List[CommitSummary]

daily_activity

Daily activity breakdown

TYPE:List[DayActivity]

contributor_stats

Statistics by contributor

TYPE:Dict[str, Dict[str, Any]]

commit_type_distribution

Distribution of commit types

TYPE:Dict[str, int]

file_change_frequency

Most frequently changed files

TYPE:List[Tuple[str, int]]

hot_periods

Periods of high activity

TYPE:List[Dict[str, Any]]

quiet_periods

Periods of low activity

TYPE:List[Dict[str, Any]]

significant_events

Notable events (releases, major changes)

TYPE:List[Dict[str, Any]]

trends

Identified trends in development

TYPE:List[str]

summary

Executive summary of the period

TYPE:str

Attributes
most_active_dayproperty
Python
most_active_day: Optional[DayActivity]

Get the most active day.

RETURNSDESCRIPTION
Optional[DayActivity]

Optional[DayActivity]: Most active day or None

activity_levelproperty
Python
activity_level: str

Determine overall activity level.

RETURNSDESCRIPTION
str

Activity level (high, moderate, low)

TYPE:str

Functions
to_dict
Python
to_dict() -> Dict[str, Any]

Convert to dictionary representation.

RETURNSDESCRIPTION
Dict[str, Any]

Dict[str, Any]: Dictionary representation

Source code in tenets/core/git/chronicle.py
Python
def to_dict(self) -> Dict[str, Any]:
    """Convert to dictionary representation.

    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,
            "avg_commits_per_day": (
                self.total_commits / max(1, (self.period_end - self.period_start).days)
            ),
            "narrative": self.summary,
        },
        "commit_types": self.commit_type_distribution,
        "top_contributors": list(self.contributor_stats.items())[:10],
        "top_files": self.file_change_frequency[:20],
        "hot_periods": self.hot_periods[:5],
        "quiet_periods": self.quiet_periods[:5],
        "significant_events": self.significant_events,
        "trends": self.trends,
    }

Chronicle

Python
Chronicle(config: TenetsConfig)

Main chronicle analyzer for git repositories.

Analyzes git history to create a narrative view of repository evolution, identifying patterns, trends, and significant events.

ATTRIBUTEDESCRIPTION
config

Configuration object

logger

Logger instance

git_analyzer

Git analyzer instance

TYPE:Optional[GitAnalyzer]

Initialize chronicle analyzer.

PARAMETERDESCRIPTION
config

TenetsConfig instance

TYPE:TenetsConfig

Source code in tenets/core/git/chronicle.py
Python
def __init__(self, config: TenetsConfig):
    """Initialize chronicle analyzer.

    Args:
        config: TenetsConfig instance
    """
    self.config = config
    self.logger = get_logger(__name__)
    self.git_analyzer: Optional[GitAnalyzer] = None
Functions
analyze
Python
analyze(repo_path: Path, since: Optional[str] = None, until: Optional[str] = None, author: Optional[str] = None, branch: Optional[str] = None, include_merges: bool = True, include_stats: bool = True, max_commits: int = 1000) -> ChronicleReport

Analyze repository history and create chronicle report.

Creates a comprehensive narrative of repository evolution including commits, contributors, trends, and significant events.

PARAMETERDESCRIPTION
repo_path

Path to git repository

TYPE:Path

since

Start date or relative time (e.g., "2 weeks ago")

TYPE:Optional[str]DEFAULT:None

until

End date or relative time

TYPE:Optional[str]DEFAULT:None

author

Filter by specific author

TYPE:Optional[str]DEFAULT:None

branch

Specific branch to analyze

TYPE:Optional[str]DEFAULT:None

include_merges

Whether to include merge commits

TYPE:boolDEFAULT:True

include_stats

Whether to include detailed statistics

TYPE:boolDEFAULT:True

max_commits

Maximum commits to analyze

TYPE:intDEFAULT:1000

RETURNSDESCRIPTION
ChronicleReport

Comprehensive chronicle analysis

TYPE:ChronicleReport

Example

chronicle = Chronicle(config) report = chronicle.analyze( ... Path("."), ... since="1 month ago", ... include_stats=True ... ) print(report.summary)

Source code in tenets/core/git/chronicle.py
Python
def analyze(
    self,
    repo_path: Path,
    since: Optional[str] = None,
    until: Optional[str] = None,
    author: Optional[str] = None,
    branch: Optional[str] = None,
    include_merges: bool = True,
    include_stats: bool = True,
    max_commits: int = 1000,
) -> ChronicleReport:
    """Analyze repository history and create chronicle report.

    Creates a comprehensive narrative of repository evolution including
    commits, contributors, trends, and significant events.

    Args:
        repo_path: Path to git repository
        since: Start date or relative time (e.g., "2 weeks ago")
        until: End date or relative time
        author: Filter by specific author
        branch: Specific branch to analyze
        include_merges: Whether to include merge commits
        include_stats: Whether to include detailed statistics
        max_commits: Maximum commits to analyze

    Returns:
        ChronicleReport: Comprehensive chronicle analysis

    Example:
        >>> chronicle = Chronicle(config)
        >>> report = chronicle.analyze(
        ...     Path("."),
        ...     since="1 month ago",
        ...     include_stats=True
        ... )
        >>> print(report.summary)
    """
    self.logger.debug(f"Analyzing chronicle for {repo_path}")

    # 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 ChronicleReport(
            period_start=datetime.now(),
            period_end=datetime.now(),
            summary="No git repository found",
        )

    # Parse time period
    period_start, period_end = self._parse_time_period(since, until)

    # Initialize report
    report = ChronicleReport(period_start=period_start, period_end=period_end)

    # Get commits
    commits = self._get_commits(
        period_start, period_end, author, branch, include_merges, max_commits
    )

    if not commits:
        report.summary = "No commits found in the specified period"
        return report

    # Process commits sequentially
    # Note: Parallelization was attempted but GitPython commit objects
    # are not thread-safe and accessing commit.stats is very expensive
    for commit in commits:
        commit_summary = self._process_commit(commit, include_stats)
        report.commits.append(commit_summary)

    # Sort commits by date
    report.commits.sort(key=lambda c: c.date)

    # Update basic stats
    report.total_commits = len(report.commits)

    # Analyze daily activity
    report.daily_activity = self._analyze_daily_activity(report.commits)

    # Analyze contributors
    report.contributor_stats = self._analyze_contributors(report.commits)
    report.total_contributors = len(report.contributor_stats)

    # Analyze commit types
    report.commit_type_distribution = self._analyze_commit_types(report.commits)

    # Analyze file changes
    if include_stats:
        report.file_change_frequency = self._analyze_file_changes(commits)

    # Identify hot and quiet periods
    report.hot_periods = self._identify_hot_periods(report.daily_activity)
    report.quiet_periods = self._identify_quiet_periods(report.daily_activity)

    # Identify significant events
    report.significant_events = self._identify_significant_events(report.commits)

    # Identify trends
    report.trends = self._identify_trends(report)

    # Generate summary
    report.summary = self._generate_summary(report)

    self.logger.debug(
        f"Chronicle analysis complete: {report.total_commits} commits, "
        f"{report.total_contributors} contributors"
    )

    return report

ChronicleBuilder

Python
ChronicleBuilder(config: Optional[TenetsConfig] = None)

High-level builder that assembles a simple chronicle dict for CLI.

This composes the existing Chronicle and GitAnalyzer without duplicating analysis logic. It converts inputs to what Chronicle expects and returns a compact, CLI-friendly dictionary.

The CLI tests patch this class, but we provide a functional default for real usage.

Source code in tenets/core/git/chronicle.py
Python
def __init__(self, config: Optional[TenetsConfig] = None) -> None:
    self.config = config or TenetsConfig()
    self.logger = get_logger(__name__)
Functions
build_chronicle
Python
build_chronicle(repo_path: Path, *, since: Optional[object] = None, until: Optional[object] = None, branch: Optional[str] = None, authors: Optional[List[str]] = None, include_merges: bool = True, limit: Optional[int] = None) -> Dict[str, Any]

Build a chronicle summary for the given repository.

PARAMETERDESCRIPTION
repo_path

Path to a git repository

TYPE:Path

since

Start time (datetime or relative/ISO string)

TYPE:Optional[object]DEFAULT:None

until

End time (datetime or relative/ISO string)

TYPE:Optional[object]DEFAULT:None

branch

Branch name to analyze

TYPE:Optional[str]DEFAULT:None

authors

Optional author filters (currently advisory)

TYPE:Optional[List[str]]DEFAULT:None

include_merges

Include merge commits

TYPE:boolDEFAULT:True

limit

Max commits to analyze (advisory to Chronicle)

TYPE:Optional[int]DEFAULT:None

RETURNSDESCRIPTION
Dict[str, Any]

A dictionary with keys expected by the CLI views.

Source code in tenets/core/git/chronicle.py
Python
def build_chronicle(
    self,
    repo_path: Path,
    *,
    since: Optional[object] = None,
    until: Optional[object] = None,
    branch: Optional[str] = None,
    authors: Optional[List[str]] = None,
    include_merges: bool = True,
    limit: Optional[int] = None,
) -> Dict[str, Any]:
    """Build a chronicle summary for the given repository.

    Args:
        repo_path: Path to a git repository
        since: Start time (datetime or relative/ISO string)
        until: End time (datetime or relative/ISO string)
        branch: Branch name to analyze
        authors: Optional author filters (currently advisory)
        include_merges: Include merge commits
        limit: Max commits to analyze (advisory to Chronicle)

    Returns:
        A dictionary with keys expected by the CLI views.
    """

    # Normalize time parameters to strings for Chronicle
    def _to_str(t: Optional[object]) -> Optional[str]:
        if t is None:
            return None
        if isinstance(t, str):
            return t
        try:
            from datetime import datetime as _dt

            if isinstance(t, _dt):
                return t.isoformat()
        except Exception:
            pass
        # Fallback to string repr
        return str(t)

    since_s = _to_str(since)
    until_s = _to_str(until)

    # Run detailed analysis via Chronicle
    chron = Chronicle(self.config)
    report = chron.analyze(
        repo_path,
        since=since_s,
        until=until_s,
        author=(authors[0] if authors else None),  # basic filter support
        branch=branch,
        include_merges=include_merges,
        include_stats=False,  # Disabled for performance - commit.stats is very expensive
        max_commits=limit or 1000,
    )

    # Summarize fields commonly displayed by CLI
    period = (
        f"{report.period_start.date().isoformat()} to {report.period_end.date().isoformat()}"
    )
    files_changed = (
        len({p[0] for p in report.file_change_frequency}) if report.file_change_frequency else 0
    )

    # Lightweight activity signal (placeholder using totals)
    activity = {
        "trend": 0.0,  # real trend computation is beyond this builder
        "current_velocity": report.total_commits,
        "commits_this_week": (
            sum(d.total_commits for d in report.daily_activity[-7:])
            if report.daily_activity
            else 0
        ),
    }

    return {
        "period": period,
        "total_commits": report.total_commits,
        "files_changed": files_changed,
        "activity": activity,
        # Include a small slice of richer data for reports
        "commit_types": report.commit_type_distribution,
        "top_files": report.file_change_frequency[:10],
        "top_contributors": sorted(
            ((a, s.get("commits", 0)) for a, s in report.contributor_stats.items()),
            key=lambda x: x[1],
            reverse=True,
        )[:5],
        # Preserve the original report for advanced formatting if needed
        "_report": report,
    }

Functions

create_chronicle

Python
create_chronicle(repo_path: Path, since: Optional[str] = None, config: Optional[TenetsConfig] = None, **kwargs: Any) -> ChronicleReport

Convenience function to create a repository chronicle.

PARAMETERDESCRIPTION
repo_path

Path to repository

TYPE:Path

since

Start time for chronicle

TYPE:Optional[str]DEFAULT:None

config

Optional configuration

TYPE:Optional[TenetsConfig]DEFAULT:None

**kwargs

Additional arguments for chronicle

TYPE:AnyDEFAULT:{}

RETURNSDESCRIPTION
ChronicleReport

Chronicle analysis

TYPE:ChronicleReport

Example

from tenets.core.git.chronicle import create_chronicle report = create_chronicle(Path("."), since="1 month ago") print(report.summary)

Source code in tenets/core/git/chronicle.py
Python
def create_chronicle(
    repo_path: Path,
    since: Optional[str] = None,
    config: Optional[TenetsConfig] = None,
    **kwargs: Any,
) -> ChronicleReport:
    """Convenience function to create a repository chronicle.

    Args:
        repo_path: Path to repository
        since: Start time for chronicle
        config: Optional configuration
        **kwargs: Additional arguments for chronicle

    Returns:
        ChronicleReport: Chronicle analysis

    Example:
        >>> from tenets.core.git.chronicle import create_chronicle
        >>> report = create_chronicle(Path("."), since="1 month ago")
        >>> print(report.summary)
    """
    if config is None:
        config = TenetsConfig()

    chronicle = Chronicle(config)
    return chronicle.analyze(repo_path, since=since, **kwargs)