Documentation
¶
Index ¶
- Constants
- Variables
- func BlameOwners(ctx context.Context, repoDir string, filePath string, ...) (owners []string, err error)
- func ExtractSubjectType(subject string) (kind string)
- func NewCliCommand() *cli.Command
- func RenderChunkPrompt(repoName string, commits []CommitEntry, metrics DigestMetrics, ...) (system string, user string)
- func RenderCommitEntry(commit CommitEntry) (rendered string)
- func RenderMetricsSection(metrics DigestMetrics) (rendered string)
- func RenderRepoHeader(repoName string) (rendered string)
- func RenderThreadRegistry(threads []Thread) (rendered string)
- func SaveCursors(dir string, cursors CursorMap) (err error)
- func ValidateCursorHash(ctx context.Context, repoDir string, hash string) (err error)
- func WriteDigest(w io.Writer, digests []RepoDigest) (err error)
- func WriteFlattenedJSONEachRow(w io.Writer, repos []RepoChunks) (err error)
- type BoundaryCrossing
- type Chunk
- type ChunkResult
- type CommitCursor
- type CommitEntry
- type CursorMap
- type DigestChunker
- type DigestMetrics
- type FlatCommitChange
- type FollowUpSignal
- type IterationHotspot
- type MetricsConfig
- type RepoChunks
- type RepoDigest
- type RevertSignal
- type SlidingWindow
- func (inst *SlidingWindow) ContextTokenCount(counter TokenCounterI) (count int64)
- func (inst *SlidingWindow) LoadFromDir() (err error)
- func (inst *SlidingWindow) Persist(chunkIndex int32, commits []CommitEntry) (err error)
- func (inst *SlidingWindow) Push(summary string)
- func (inst *SlidingWindow) RenderContext() (context string)
- type StatEntry
- type Thread
- type ThreadSpan
- type TiktokenCounter
- type TokenCounterI
Constants ¶
const DefaultSystemPrompt = `You are a changelog curator for a technical software project. You write for engineers familiar with the codebase — assume domain vocabulary is known. Do not re-explain packages, frameworks, or acronyms.
Inputs: the current chunk's commits (subjects, bodies, stats), computed metrics (hotspots, reverts, optional follow-up signals, optional boundary crossings), and — when present — prior-window summaries.
Use commit bodies for motivation. Do not paraphrase subjects verbatim.
Title: "# Changelog" followed on the next line by an italic date range derived from commit dates, formatted as *YYYY-MM-DD – YYYY-MM-DD* (a single date if the range collapses to one day).
After the title, write the sections below in order. Emit only sections that have material; omit empty sections silently. Do not write placeholder "None detected" blocks.
Narrative (no heading) — open with 2–4 paragraphs grouping commits by theme. State what changed and the constraint or motivation behind it. Cite short hashes inline in parentheses using the first 8 characters, e.g. (a1b2c3d4). Use verbs of fact. Do not use adjectives of scale like "comprehensive", "extensive", "major", "robust", "seamless", "significant".
### Decisions & Deletions — bullets for durable architectural events: ADR transitions, package/file removals, reverts (from metrics), public API breaks, new top-level modules, dependency bumps. Cite hashes.
### Hotspots — only files with 3 or more commits this chunk. One line per file naming the reason for churn (iteration on an in-flight spec, bug-fix cycle, refactor sweep). If multiple non-generated hotspots share the same reason, consolidate them into a single bullet listing the paths instead of repeating the reason. Collapse all hotspots tagged [generated] in the metrics into one trailing bullet of the form: "generated artifacts regenerated (N files)" — do not enumerate their paths.
### Follow-ups & Risks — surface the individual WIP/fixup/squash/hotfix commits provided under "Follow-up signals" in the metrics, along with reverts, unresolved TODOs, or short-lived code visible in commit bodies. Never invent an aggregate ratio ("N of M commits are WIP"); cite only the specific commits the metrics feed names. If no follow-up signals appear in the metrics, the section has no material — skip it entirely.
### Continuity — exactly one line, only when prior summaries are supplied: what thread this chunk closes, continues, or reverts.
Rules:
- Hard cap: 250 words excluding hash citations.
- Fold any boundary-crossing flags into the relevant Narrative paragraph; do not create a dedicated section for them.
- Never echo commit subjects verbatim — paraphrase into prose.
- If a Thread Registry is supplied in the system context, every themed claim in the Narrative must cite a registered thread id in backticks (e.g. ` + "`fffi2-codegen`" + `). Never invent thread ids that are not in the registry. If this chunk's activity genuinely falls outside every registered thread, say so plainly ("outside registered threads") rather than fabricating a new one. The Continuity line is the natural home for thread citations when multiple threads are advanced.
`
const FlatCommitChangeStructure = "repoName String, chunkIndex Int32, " +
"commitHash String, commitShortHash String, commitDate String, " +
"commitAuthor String, commitAuthorEmail String, commitSubject String, " +
"subjectType String, path String, adds Int32, dels Int32"
FlatCommitChangeStructure is the ClickHouse --structure argument matching the JSON tags on FlatCommitChange. Kept here so mine-trends stays in sync with the struct.
const ThreadSynthesisSystemPrompt = `` /* 1713-byte string literal not displayed */
Variables ¶
var ErrCursorHashNotFound = errors.New("cursor hash not found in repository")
var ErrNoCommits = errors.New("git repository has no commits")
var ErrNotAGitRepo = errors.New("not a git repository")
var PackageProps = packageprops.Props{ WASMWASI: packageprops.WASMBlocked, WASMJS: packageprops.WASMBlocked, WASMFreestanding: packageprops.WASMBlocked, }
PackageProps records this package's curated properties (ADR-0080). Seeded by `wasmsurvey props generate`; curate by hand, then `wasmsurvey props verify`.
Functions ¶
func BlameOwners ¶
func BlameOwners(ctx context.Context, repoDir string, filePath string, coAuthorCache map[string][]string) (owners []string, err error)
BlameOwners returns the set of author and co-author emails for a file, derived from the current git blame snapshot.
func ExtractSubjectType ¶
ExtractSubjectType returns a normalized kind for a commit subject. Fixup / squash / revert prefixes and WIP-as-word take precedence over Conventional Commits `type:` extraction because they are stronger signals of intent. Returns "other" when nothing matches.
func NewCliCommand ¶
func RenderChunkPrompt ¶
func RenderChunkPrompt(repoName string, commits []CommitEntry, metrics DigestMetrics, windowContext string, systemPrompt string, threadRegistry string) (system string, user string)
func RenderCommitEntry ¶
func RenderCommitEntry(commit CommitEntry) (rendered string)
func RenderMetricsSection ¶
func RenderMetricsSection(metrics DigestMetrics) (rendered string)
func RenderRepoHeader ¶
func RenderThreadRegistry ¶
RenderThreadRegistry formats a thread registry for injection into the system prompt as a bulleted markdown block. One line per thread, anchored on the id the LLM is expected to cite verbatim.
func SaveCursors ¶
SaveCursors writes cursors.json atomically via temp file + rename so a crashed write never leaves a partial file (resume state would be corrupted otherwise).
func ValidateCursorHash ¶
ValidateCursorHash confirms the hash still resolves to a commit in repoDir. Returns ErrCursorHashNotFound if git rev-parse fails (history was rewritten).
func WriteDigest ¶
func WriteDigest(w io.Writer, digests []RepoDigest) (err error)
func WriteFlattenedJSONEachRow ¶
func WriteFlattenedJSONEachRow(w io.Writer, repos []RepoChunks) (err error)
WriteFlattenedJSONEachRow streams one JSON object per line to w. Preferred for large inputs — avoids materialising the full flat slice in memory.
Types ¶
type BoundaryCrossing ¶
type BoundaryCrossing struct {
File string `json:"file"`
CommitHash string `json:"commitHash"`
Author string `json:"author"`
Owners []string `json:"owners"`
}
func DetectBoundaryCrossings ¶
func DetectBoundaryCrossings(ctx context.Context, repoDir string, commits []CommitEntry) (crossings []BoundaryCrossing, err error)
DetectBoundaryCrossings identifies commits that modify files whose blame authors (and co-authors) do not include the commit author.
type Chunk ¶
type Chunk struct {
Index int32
Commits []CommitEntry
TokenCount int64
Metrics DigestMetrics
}
type ChunkResult ¶
type ChunkResult struct {
Index int32 `json:"index"`
TokenCount int64 `json:"tokenCount"`
Summary string `json:"summary,omitempty"`
Metrics DigestMetrics `json:"metrics"`
Commits []CommitEntry `json:"commits"`
}
type CommitCursor ¶
type CommitCursor struct {
LastCommitHash string `json:"lastCommitHash"`
LastCommitDate string `json:"lastCommitDate"`
LastChunkIndex int32 `json:"lastChunkIndex"`
UpdatedAt string `json:"updatedAt"`
}
func NewCursorForChunk ¶
func NewCursorForChunk(chunkIndex int32, commits []CommitEntry) (cursor CommitCursor, ok bool)
NewCursorForChunk builds a cursor pointing at the newest commit in chunk. Commits are assumed oldest-first (as produced by git log --reverse in CollectDigest). Returns ok=false for empty chunks.
type CommitEntry ¶
type CursorMap ¶
type CursorMap map[string]CommitCursor
func LoadCursors ¶
LoadCursors reads cursors.json from dir. Returns an empty map if dir is empty or the file does not exist.
type DigestChunker ¶
type DigestChunker struct {
TokenBudget int64
ReservedTokens int64
Counter TokenCounterI
}
func (*DigestChunker) ChunkAll ¶
func (inst *DigestChunker) ChunkAll(digest RepoDigest, overheadTokens int64) (chunks []Chunk, err error)
ChunkAll splits all commits into chunks using a fixed overhead. Use ChunkNext for iterative chunking where overhead changes between chunks.
func (*DigestChunker) ChunkNext ¶
func (inst *DigestChunker) ChunkNext(commits []CommitEntry, index int32, overheadTokens int64, repoName string) (chunk Chunk, consumed int)
ChunkNext returns the next chunk from the front of commits, given the current overhead (system prompt + window context + repo header tokens). Returns the chunk and how many commits were consumed.
type DigestMetrics ¶
type DigestMetrics struct {
BoundaryCrossings []BoundaryCrossing `json:"boundaryCrossings,omitempty"`
IterationHotspots []IterationHotspot `json:"iterationHotspots,omitempty"`
Reverts []RevertSignal `json:"reverts,omitempty"`
FollowUps []FollowUpSignal `json:"followUps,omitempty"`
TotalCommits int32 `json:"totalCommits"`
UniqueAuthors int32 `json:"uniqueAuthors"`
// FollowUpIsAuthorBaseline is true when at least half the chunk's commits
// carry a follow-up signal. In that regime WIP/fixup prefixes are the
// author's routine commit cadence, not a risk signal — the renderer
// suppresses the follow-up bullet so the summarizer does not report a
// meaningless aggregate like "N of M commits are WIP".
FollowUpIsAuthorBaseline bool `json:"followUpIsAuthorBaseline,omitempty"`
}
func ComputeMetrics ¶
func ComputeMetrics(commits []CommitEntry, config MetricsConfig) (metrics DigestMetrics)
type FlatCommitChange ¶
type FlatCommitChange struct {
RepoName string `json:"repoName"`
ChunkIndex int32 `json:"chunkIndex"`
CommitHash string `json:"commitHash"`
CommitShortHash string `json:"commitShortHash"`
CommitDate string `json:"commitDate"`
CommitAuthor string `json:"commitAuthor"`
CommitAuthorEmail string `json:"commitAuthorEmail"`
CommitSubject string `json:"commitSubject"`
SubjectType string `json:"subjectType"`
Path string `json:"path"`
Adds int32 `json:"adds"`
Dels int32 `json:"dels"`
}
FlatCommitChange is one row in the JSONEachRow facts table consumed by trend-mining SQL. One row per file-change per commit; commits that touched no files (empty merges, typo-only commits) emit a single row with Path=="" as a sentinel so commit-level aggregations (countDistinct(commitHash)) still see them. Trend-mining queries that want file-level joins should filter `path <> ”`.
func FlattenRepoChunks ¶
func FlattenRepoChunks(repos []RepoChunks) (rows []FlatCommitChange)
FlattenRepoChunks materialises all file-changes across all chunks as a flat slice. Convenient for tests; production code should prefer the streaming WriteFlattenedJSONEachRow.
type FollowUpSignal ¶
type FollowUpSignal struct {
Hash string `json:"hash"`
Subject string `json:"subject"`
Kind string `json:"kind"`
}
FollowUpSignal flags a commit whose subject indicates unfinished work: wip/fixup/squash/hotfix language. Kind is one of "wip", "fixup", "squash", "hotfix". Surfaced so the summarizer can list them under Follow-ups & Risks.
type IterationHotspot ¶
type MetricsConfig ¶
type MetricsConfig struct {
HotspotTopN int32
}
type RepoChunks ¶
type RepoChunks struct {
RepoName string `json:"repoName"`
RepoPath string `json:"repoPath"`
Chunks []ChunkResult `json:"chunks"`
}
RepoChunks is the intermediate format emitted by extract and consumed by summarize.
type RepoDigest ¶
type RepoDigest struct {
RepoPath string
RepoName string
Commits []CommitEntry
}
func CollectDigest ¶
func CollectDigest(ctx context.Context, repoPath string, since string, author string, noStat bool, fromHash string) (digest RepoDigest, err error)
CollectDigest runs git log in repoPath. If fromHash is non-empty, commits after that hash up to HEAD are returned (since is ignored); this is how seamless resume works. Otherwise commits matching since/author are returned.
type RevertSignal ¶
RevertSignal flags a commit whose subject indicates it reverts earlier work. Surfaced so the summarizer can list them under Decisions & Deletions.
type SlidingWindow ¶
type SlidingWindow struct {
MaxSummaries int32
Summaries []string
Dir string
// contains filtered or unexported fields
}
func (*SlidingWindow) ContextTokenCount ¶
func (inst *SlidingWindow) ContextTokenCount(counter TokenCounterI) (count int64)
func (*SlidingWindow) LoadFromDir ¶
func (inst *SlidingWindow) LoadFromDir() (err error)
func (*SlidingWindow) Persist ¶
func (inst *SlidingWindow) Persist(chunkIndex int32, commits []CommitEntry) (err error)
func (*SlidingWindow) Push ¶
func (inst *SlidingWindow) Push(summary string)
func (*SlidingWindow) RenderContext ¶
func (inst *SlidingWindow) RenderContext() (context string)
type StatEntry ¶
type StatEntry struct {
Path string `json:"path"`
Adds int32 `json:"adds"`
Dels int32 `json:"dels"`
}
StatEntry is the structured counterpart to the human-readable Stat string: per-file add/delete line counts from `git diff-tree --numstat`. Consumed by trend-mining SQL so queries do not need to parse the freeform Stat format. Adds/Dels are 0 for binary files (git reports "-" for those).
type Thread ¶
type Thread struct {
ID string `json:"id"`
Title string `json:"title"`
Span ThreadSpan `json:"span"`
Summary string `json:"summary"`
ComplexityDirection string `json:"complexityDirection"`
PathPrefixes []string `json:"pathPrefixes"`
AnchorCommits []string `json:"anchorCommits"`
}
Thread is one durable narrative arc identified over a chunk window. The registry is written by synthesize-threads and consumed by summarize via --thread-registry; chunk summaries anchor their themes to these ids.
type ThreadSpan ¶
type TiktokenCounter ¶
type TiktokenCounter struct {
Encoding string
CorrectionMultiplier float64
// contains filtered or unexported fields
}
func (*TiktokenCounter) CountTokens ¶
func (inst *TiktokenCounter) CountTokens(text string) (count int64)
func (*TiktokenCounter) Init ¶
func (inst *TiktokenCounter) Init() (err error)
type TokenCounterI ¶
Source Files
¶
- gov_commitdigest.go
- gov_commitdigest_chunker.go
- gov_commitdigest_cli.go
- gov_commitdigest_cursor.go
- gov_commitdigest_flatten.go
- gov_commitdigest_metrics.go
- gov_commitdigest_mine_trends.go
- gov_commitdigest_openaichat.go
- gov_commitdigest_ownership.go
- gov_commitdigest_render.go
- gov_commitdigest_synthesize_threads.go
- gov_commitdigest_tokenizer.go
- gov_commitdigest_window.go
- package_props.go