On this page
#reposummary
reposummary is a command-line tool that turns a git repository's commits over a chosen time window or revision range into a Markdown journal, with optional LLM-written narration. It is built for developers and AI agents who need a readable account of what a repository has been doing without walking the log commit by commit. Everything in the journal -- commit classification, per-directory churn, issue references, authors and tags -- is extracted deterministically from git, so LLM tokens are spent only on the optional prose layer.
#Quick start
Install the binary, then summarize a repository. --window and --synthesis are both required and have no default, so every run states which span it covers and whether an LLM narrates it.
go install github.com/smm-h/reposummary@latest
reposummary summarize . --window today --synthesis nonenpm (npm i -g reposummary) and PyPI (pip install reposummary) ship wrappers that download the same prebuilt binary.
#Documentation
The usage guide covers every window form, the three synthesis backends and the on-disk cache. The CLI reference is generated from the command declarations themselves, so it lists each flag exactly as the binary parses it.
- Usage guide -- windows, synthesis backends and caching
- CLI reference -- every command, flag and argument
#Packages
The work is split so that the expensive part stays optional: window parsing, git extraction and digesting are deterministic and cost nothing, rendering turns a digest into Markdown, and synthesis is the only stage that talks to a model. The cache sits underneath, keyed on the inputs that fully determine a journal, and prunes entries unread for 90 days.
#internal/window
Package window parses the --window flag into a resolved time/rev range spec.
A window is either a "daterange" (git --since/--until dates over a tip) or a "range" (an explicit git revision range like a..b, or the full history). Parsing is strict: an unrecognized form is a hard error, never a silent fallthrough.
#Spec
type Spec structSpec is a resolved time window.
#ParseWindow
func ParseWindow(spec string) (Spec, error)ParseWindow resolves a window spec string. Forms are matched in order; an unrecognized form returns an error listing the supported forms.
#internal/gitdata
Package gitdata extracts commit metadata, file churn, and tags from a git repository over a resolved window. Everything is read deterministically from git subprocesses; no LLM tokens are spent here.
#FileChange
type FileChange structFileChange is a single file touched by a commit. Added/Deleted are 0 for binary files (git reports "-" for those).
#Commit
type Commit structCommit is a single commit's extracted metadata.
#Tag
type Tag structTag is a git tag whose target resolves to a collected commit.
#GitBase
func GitBase(repo string) ([]string, error)GitBase returns the git command prefix for a repo, or an error if the path is not a git repository. It supports normal repos (.git dir or a working git rev-parse) and the ".bare" convention (a bare repo stored under
#Collect
func Collect(base []string, win window.Spec, tip string) ([]Commit, []Tag, error)Collect gathers commits, their file churn, and matching tags for a window. Empty output yields empty slices and a nil error.
#internal/digest
Package digest turns raw git data into a structured, deterministic summary: commit classification, per-directory churn, issue references, author counts, and per-day activity. This is the free (no-LLM) narrative layer.
#Digest
type Digest structDigest is the structured summary of a window's activity.
#Classify
func Classify(c gitdata.Commit) stringClassify returns the change category for a commit: one of "breaking", "feature", "fix", or "other".
#CleanSubject
func CleanSubject(c gitdata.Commit) stringCleanSubject strips a Conventional Commits prefix if present, otherwise returns the subject unchanged. The result is trimmed.
#Build
func Build(commits []gitdata.Commit, tags []gitdata.Tag, win window.Spec, repoName string) DigestBuild assembles a Digest from collected commits and tags. Commits are assumed to be newest-first (git log order).
#internal/render
Package render turns a Digest (plus an optional LLM narrative) into the final Markdown journal, and produces the compact plain-text digest fed to the LLM prompt. It never imports synth, avoiding an import cycle.
#Markdown
func Markdown(d digest.Digest, narrative, version string) stringMarkdown renders the full journal for a digest and narrative.
#DigestForLLM
func DigestForLLM(d digest.Digest) stringDigestForLLM produces a compact plain-text digest for the LLM prompt.
#internal/synth
Package synth turns a digest into prose via an explicitly-chosen LLM backend.
There is NO silent fallback: if the chosen backend fails at runtime, that is a hard error. The caller picks "none", "claude-cli", or "anthropic-api"; the choice is honored or it errors.
#PROMPT_TEMPLATE
const PROMPT_TEMPLATE = `You are writing a short journal entry narrating what happened in a software repository over a time window.PROMPT_TEMPLATE instructs the model to write flowing prose from the digest.
#Synthesize
func Synthesize(d digest.Digest, mode, model string) (string, error)Synthesize produces narrative prose for a digest using the chosen backend.
#internal/cache
Package cache is a deterministic on-disk journal cache. The journal for a fixed (firstSHA, lastSHA, synthesis, model, version, windowLabel) tuple is deterministic, so identical windows reuse cached output: cost is O(new commits), not O(window size). Storage is plain files; no database. Entries age out: a successful write prunes entries not read in the last 90 days, while a cache hit refreshes an entry's mtime so hot entries stay warm.
#Cache
type Cache structCache is a filesystem-backed journal cache rooted at a directory.
#New
func New(dir string) (*Cache, error)New opens (creating if needed) a cache at dir. An empty dir uses DefaultDir().
#DefaultDir
func DefaultDir() stringDefaultDir returns the default cache directory: $XDG_CACHE_HOME/reposummary, or ~/.cache/reposummary.
#MakeKey
func MakeKey(firstSHA, lastSHA, synthesis, model, version, windowLabel string) stringMakeKey returns the sha256 hex of the join of the cache inputs. The journal is fully determined by this tuple.
windowLabel is part of the key because a zero-commit window has empty firstSHA/lastSHA: without the label, every distinct empty window would collapse to the same key and a cached "no activity" journal could be served under the wrong window heading.
#Cache.Get
func (c *Cache) Get(key string) (string, bool)Get reads a cached journal by key. The second return is false on a miss. On a hit the entry's mtime is refreshed so that frequently-read entries survive age-based pruning. A failed touch is non-fatal (the read still succeeds).
#Cache.Set
func (c *Cache) Set(key, md string) errorSet writes a journal to the cache under key, then opportunistically prunes entries older than maxAge. Pruning is best-effort housekeeping: its failures are swallowed and never turn a successful write into an error.