vibe-coding-stats
Version:
TypeScript library for analyzing GitHub repository activity and estimating developer coding metrics. Optimized for vibe coding workflows with session-based commit patterns.
1 lines • 47.5 kB
Source Map (JSON)
{"version":3,"sources":["../src/model/types.ts","../src/api/github.ts","../src/util/time.ts","../src/logic/sessions.ts","../src/logic/aggregate.ts","../src/util/cache.ts","../src/logic/filters.ts","../src/logic/transform.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// Input types\n/**\n * Repository identifier - accepts either a short repo format or full GitHub URL\n *\n * @example\n * // Using short format\n * { repo: 'owner/repo' }\n *\n * @example\n * // Using full URL\n * { url: 'https://github.com/owner/repo' }\n */\nexport type RepoInput = { repo: string } | { url: string };\n\nexport interface StatsOptions {\n sessionTimeoutMin?: number; // default: 45\n firstCommitBonusMin?: number; // default: 15\n since?: string | Date;\n until?: string | Date;\n authors?: string[];\n excludeBots?: boolean; // default: true\n excludeMergeCommits?: boolean; // default: false\n timezone?: string; // default: \"UTC\"\n githubToken?: string; // optional for higher rate limit\n fetchImpl?: typeof fetch;\n perPage?: number; // default: 100\n maxPages?: number;\n useGraphQL?: boolean; // default: false\n cache?: 'memory' | 'none'; // default: 'memory'\n cacheTTLms?: number; // default: 15 min\n}\n\n// Output types\nexport interface AggregateStats {\n totalHours: number;\n sessionsCount: number;\n devDays: number;\n totalCommits: number;\n avgCommitsPerSession: number;\n avgSessionsPerDay: number;\n longestSessionHours: number;\n avgSessionHours: number;\n /** Day of week with most total coding hours (e.g., \"Monday\", \"Tuesday\"). Undefined if no sessions. */\n mostProductiveDayOfWeek?: string;\n /** Longest consecutive streak of days with commits */\n longestStreakDays: number;\n /** Minimum time between consecutive sessions by the same author in minutes. Undefined if no author has 2+ sessions. */\n minTimeBetweenSessionsMin?: number;\n /** Average time in minutes between consecutive commits within sessions across all authors. Undefined if all sessions are single-commit. */\n avgMinutesBetweenCommits?: number;\n /** Maximum time in minutes between consecutive commits within sessions across all authors. Undefined if all sessions are single-commit. */\n maxMinutesBetweenCommits?: number;\n}\n\nexport interface RepoStats {\n repo: string;\n period: { since?: string; until?: string };\n config: {\n sessionTimeoutMin: number;\n firstCommitBonusMin: number;\n timezone: string;\n };\n totals: AggregateStats;\n perAuthor: AuthorStats[];\n perDay: DayStats[];\n raw?: {\n commitSample?: Array<{\n sha: string;\n authorLogin?: string;\n authorName?: string;\n date: string;\n }>;\n };\n}\n\nexport interface AuthorStats extends AggregateStats {\n author: string;\n}\n\nexport interface DayStats {\n date: string; // ISO date string (YYYY-MM-DD)\n totalHours: number;\n sessionsCount: number;\n totalCommits: number;\n authors: string[];\n}\n\n// Internal types\nexport interface Commit {\n sha: string;\n author: string;\n authorLogin?: string;\n date: Date;\n message: string;\n isMerge?: boolean;\n isBot?: boolean;\n}\n\nexport interface Session {\n author: string;\n commits: Commit[];\n startTime: Date;\n endTime: Date;\n durationMinutes: number;\n date: string; // ISO date string of session start in configured timezone\n /** Average time in minutes between consecutive commits in this session. Undefined for single-commit sessions. */\n avgMinutesBetweenCommits?: number;\n /** Maximum time in minutes between consecutive commits in this session. Undefined for single-commit sessions. */\n maxMinutesBetweenCommits?: number;\n}\n\n// Error types\nexport type StatsErrorCode =\n | 'INVALID_REPO'\n | 'NOT_FOUND'\n | 'RATE_LIMIT'\n | 'NETWORK'\n | 'UNAUTHORIZED'\n | 'UNSUPPORTED_PRIVATE_REPO'\n | 'UNKNOWN';\n\nexport class StatsError extends Error {\n constructor(\n public code: StatsErrorCode,\n message: string,\n public details?: unknown\n ) {\n super(message);\n this.name = 'StatsError';\n }\n}\n\n// GitHub API types\nexport interface GitHubCommit {\n sha: string;\n commit: {\n author: {\n name: string;\n email: string;\n date: string;\n };\n message: string;\n };\n author?: {\n login: string;\n type: string;\n } | null;\n parents?: Array<{ sha: string }>;\n}\n\nexport interface GitHubErrorResponse {\n message: string;\n documentation_url?: string;\n}\n","import type {\n GitHubCommit,\n GitHubErrorResponse,\n RepoInput,\n StatsErrorCode,\n} from '../model/types.js';\nimport { StatsError } from '../model/types.js';\n\n/**\n * Parse repo input to extract owner and repo name\n */\nexport function parseRepoInput(input: RepoInput): { owner: string; repo: string } {\n let repoString: string;\n\n if ('repo' in input) {\n repoString = input.repo;\n } else {\n // Parse URL\n const url = input.url.replace(/\\.git$/, '');\n const match = url.match(/github\\.com[:/]([^/]+)\\/([^/]+)/);\n if (!match) {\n throw new StatsError('INVALID_REPO', `Invalid GitHub URL: ${input.url}`);\n }\n repoString = `${match[1]}/${match[2]}`;\n }\n\n const parts = repoString.split('/');\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new StatsError('INVALID_REPO', `Invalid repo format: ${repoString}`);\n }\n\n return { owner: parts[0], repo: parts[1] };\n}\n\n/**\n * Options for fetching GitHub commits\n */\nexport interface FetchGitHubCommitsOptions {\n githubToken?: string;\n since?: string | Date;\n until?: string | Date;\n perPage?: number;\n maxPages?: number;\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Fetch raw commits from GitHub REST API (no filtering or transformation)\n */\nexport async function fetchGitHubCommits(\n owner: string,\n repo: string,\n options: FetchGitHubCommitsOptions = {}\n): Promise<GitHubCommit[]> {\n const {\n githubToken,\n since,\n until,\n perPage = 100,\n maxPages,\n fetchImpl = fetch,\n } = options;\n\n const commits: GitHubCommit[] = [];\n let page = 1;\n let hasMore = true;\n\n while (hasMore) {\n const url = new URL(`https://api.github.com/repos/${owner}/${repo}/commits`);\n url.searchParams.set('per_page', String(perPage));\n url.searchParams.set('page', String(page));\n\n if (since) {\n const sinceDate = since instanceof Date ? since.toISOString() : since;\n url.searchParams.set('since', sinceDate);\n }\n\n if (until) {\n const untilDate = until instanceof Date ? until.toISOString() : until;\n url.searchParams.set('until', untilDate);\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/vnd.github.v3+json',\n };\n\n if (githubToken) {\n headers.Authorization = `Bearer ${githubToken}`;\n }\n\n try {\n const response = await fetchImpl(url.toString(), { headers });\n\n if (!response.ok) {\n await handleGitHubError(response);\n }\n\n const data = (await response.json()) as GitHubCommit[];\n\n if (data.length === 0) {\n hasMore = false;\n break;\n }\n\n commits.push(...data);\n\n // Check if there are more pages\n if (data.length < perPage) {\n hasMore = false;\n } else if (maxPages && page >= maxPages) {\n hasMore = false;\n } else {\n page++;\n }\n } catch (error) {\n if (error instanceof StatsError) {\n throw error;\n }\n throw new StatsError('NETWORK', `Failed to fetch commits: ${String(error)}`, error);\n }\n }\n\n return commits;\n}\n\n/**\n * Handle GitHub API errors\n */\nasync function handleGitHubError(response: Response): Promise<never> {\n const status = response.status;\n let errorCode: StatsErrorCode = 'UNKNOWN';\n let message = `GitHub API error: ${status}`;\n\n try {\n const errorData = (await response.json()) as GitHubErrorResponse;\n message = errorData.message || message;\n } catch {\n // Ignore JSON parse errors\n }\n\n if (status === 404) {\n errorCode = 'NOT_FOUND';\n message = 'Repository not found';\n } else if (status === 401) {\n errorCode = 'UNAUTHORIZED';\n message = 'Invalid or missing GitHub token';\n } else if (status === 403) {\n if (message.toLowerCase().includes('rate limit')) {\n errorCode = 'RATE_LIMIT';\n message = 'GitHub API rate limit exceeded';\n } else {\n errorCode = 'UNAUTHORIZED';\n }\n }\n\n throw new StatsError(errorCode, message, { status, response });\n}\n","import { format, parseISO } from 'date-fns';\nimport { utcToZonedTime, formatInTimeZone } from 'date-fns-tz';\n\n/**\n * Convert a Date to a specific timezone\n */\nexport function toTimezone(date: Date, timezone: string): Date {\n return utcToZonedTime(date, timezone);\n}\n\n/**\n * Format a date as ISO date string (YYYY-MM-DD) in the specified timezone\n */\nexport function toISODate(date: Date, timezone: string): string {\n return formatInTimeZone(date, timezone, 'yyyy-MM-dd');\n}\n\n/**\n * Parse various date inputs to Date object\n */\nexport function parseDate(input: string | Date): Date {\n if (input instanceof Date) {\n return input;\n }\n return parseISO(input);\n}\n\n/**\n * Get the difference in minutes between two dates\n */\nexport function diffInMinutes(date1: Date, date2: Date): number {\n return Math.abs(date1.getTime() - date2.getTime()) / (1000 * 60);\n}\n","import type { Commit, Session } from '../model/types.js';\nimport { diffInMinutes, toISODate } from '../util/time.js';\n\nexport interface SessionConfig {\n sessionTimeoutMin: number;\n firstCommitBonusMin: number;\n timezone: string;\n}\n\n/**\n * Group commits into sessions by author\n * A session is a series of commits by the same author where consecutive commits\n * are no more than sessionTimeoutMin minutes apart\n */\nexport function buildSessions(commits: Commit[], config: SessionConfig): Session[] {\n const { sessionTimeoutMin, firstCommitBonusMin, timezone } = config;\n\n // Group commits by author\n const commitsByAuthor = new Map<string, Commit[]>();\n for (const commit of commits) {\n const existing = commitsByAuthor.get(commit.author) || [];\n existing.push(commit);\n commitsByAuthor.set(commit.author, existing);\n }\n\n const sessions: Session[] = [];\n\n // Process each author's commits\n for (const [author, authorCommits] of commitsByAuthor) {\n // Sort commits by date (oldest first)\n const sortedCommits = authorCommits.sort((a, b) => a.date.getTime() - b.date.getTime());\n\n let currentSession: Commit[] = [];\n\n for (const commit of sortedCommits) {\n if (currentSession.length === 0) {\n // Start a new session\n currentSession.push(commit);\n } else {\n // Check time gap between current commit and last commit in session\n const lastCommit = currentSession[currentSession.length - 1];\n const gap = diffInMinutes(commit.date, lastCommit.date);\n\n if (gap <= sessionTimeoutMin) {\n // Continue current session\n currentSession.push(commit);\n } else {\n // End current session and start a new one\n sessions.push(createSession(currentSession, author, firstCommitBonusMin, timezone));\n currentSession = [commit];\n }\n }\n }\n\n // Don't forget the last session\n if (currentSession.length > 0) {\n sessions.push(createSession(currentSession, author, firstCommitBonusMin, timezone));\n }\n }\n\n return sessions;\n}\n\n/**\n * Create a session from a list of commits\n */\nfunction createSession(\n commits: Commit[],\n author: string,\n firstCommitBonusMin: number,\n timezone: string\n): Session {\n const startTime = commits[0].date;\n const endTime = commits[commits.length - 1].date;\n\n // Calculate duration\n let durationMinutes: number;\n if (commits.length === 1) {\n // Single commit: use bonus time\n durationMinutes = firstCommitBonusMin;\n } else {\n // Multiple commits: time from first to last + bonus\n durationMinutes = diffInMinutes(startTime, endTime) + firstCommitBonusMin;\n }\n\n // Session date is the day it started (in configured timezone)\n const date = toISODate(startTime, timezone);\n\n // Calculate commit gap metrics (only for multi-commit sessions)\n let avgMinutesBetweenCommits: number | undefined;\n let maxMinutesBetweenCommits: number | undefined;\n\n if (commits.length > 1) {\n const gaps: number[] = [];\n for (let i = 1; i < commits.length; i++) {\n const gap = diffInMinutes(commits[i].date, commits[i - 1].date);\n gaps.push(gap);\n }\n\n const totalGap = gaps.reduce((sum, gap) => sum + gap, 0);\n avgMinutesBetweenCommits = Math.round((totalGap / gaps.length) * 100) / 100;\n maxMinutesBetweenCommits = Math.round(Math.max(...gaps) * 100) / 100;\n }\n\n return {\n author,\n commits,\n startTime,\n endTime,\n durationMinutes,\n date,\n avgMinutesBetweenCommits,\n maxMinutesBetweenCommits,\n };\n}\n","import type { Session, AuthorStats, DayStats, AggregateStats } from '../model/types.js';\n\n/**\n * Calculate aggregate statistics from a set of sessions\n * This shared function is used for both totals and per-author stats\n */\nexport function calculateAggregateStats(sessions: Session[]): AggregateStats {\n const totalHours = sessions.reduce((sum, s) => sum + s.durationMinutes / 60, 0);\n const sessionsCount = sessions.length;\n const totalCommits = sessions.reduce((sum, s) => sum + s.commits.length, 0);\n\n // Get unique days\n const uniqueDays = new Set(sessions.map((s) => s.date));\n const devDays = uniqueDays.size;\n\n // Find longest session\n const longestSessionHours = sessions.length > 0\n ? Math.max(...sessions.map((s) => s.durationMinutes / 60))\n : 0;\n\n const avgCommitsPerSession = sessionsCount > 0 ? totalCommits / sessionsCount : 0;\n const avgSessionsPerDay = devDays > 0 ? sessionsCount / devDays : 0;\n const avgSessionHours = sessionsCount > 0 ? totalHours / sessionsCount : 0;\n\n // Calculate most productive day of week\n const mostProductiveDayOfWeek = calculateMostProductiveDayOfWeek(sessions);\n\n // Calculate longest streak\n const longestStreakDays = calculateLongestStreakDays(sessions);\n\n // Calculate minimum time between sessions\n const minTimeBetweenSessionsMin = calculateMinTimeBetweenSessions(sessions);\n\n // Calculate commit gap metrics across all sessions\n const allCommitGaps: number[] = [];\n let maxCommitGap = 0;\n\n for (const session of sessions) {\n if (session.avgMinutesBetweenCommits !== undefined) {\n const numGaps = session.commits.length - 1;\n allCommitGaps.push(...Array(numGaps).fill(session.avgMinutesBetweenCommits));\n }\n if (session.maxMinutesBetweenCommits !== undefined) {\n maxCommitGap = Math.max(maxCommitGap, session.maxMinutesBetweenCommits);\n }\n }\n\n const avgMinutesBetweenCommits = allCommitGaps.length > 0\n ? Math.round((allCommitGaps.reduce((sum, gap) => sum + gap, 0) / allCommitGaps.length) * 100) / 100\n : undefined;\n\n const maxMinutesBetweenCommits = maxCommitGap > 0\n ? Math.round(maxCommitGap * 100) / 100\n : undefined;\n\n return {\n totalHours: Math.round(totalHours * 100) / 100,\n sessionsCount,\n devDays,\n totalCommits,\n avgCommitsPerSession: Math.round(avgCommitsPerSession * 100) / 100,\n avgSessionsPerDay: Math.round(avgSessionsPerDay * 100) / 100,\n longestSessionHours: Math.round(longestSessionHours * 100) / 100,\n avgSessionHours: Math.round(avgSessionHours * 100) / 100,\n mostProductiveDayOfWeek,\n longestStreakDays,\n minTimeBetweenSessionsMin,\n avgMinutesBetweenCommits,\n maxMinutesBetweenCommits,\n };\n}\n\n/**\n * Aggregate sessions into per-author statistics\n */\nexport function aggregateByAuthor(sessions: Session[]): AuthorStats[] {\n // Group sessions by author\n const sessionsByAuthor = new Map<string, Session[]>();\n\n for (const session of sessions) {\n const existing = sessionsByAuthor.get(session.author) || [];\n existing.push(session);\n sessionsByAuthor.set(session.author, existing);\n }\n\n // Calculate stats for each author using the shared function\n return Array.from(sessionsByAuthor.entries())\n .map(([author, authorSessions]) => ({\n author,\n ...calculateAggregateStats(authorSessions),\n }))\n .sort((a, b) => b.totalHours - a.totalHours);\n}\n\n/**\n * Aggregate sessions into per-day statistics\n */\nexport function aggregateByDay(sessions: Session[]): DayStats[] {\n const dayMap = new Map<string, DayStats>();\n\n for (const session of sessions) {\n const existing = dayMap.get(session.date);\n\n if (existing) {\n existing.totalHours += session.durationMinutes / 60;\n existing.sessionsCount += 1;\n existing.totalCommits += session.commits.length;\n if (!existing.authors.includes(session.author)) {\n existing.authors.push(session.author);\n }\n } else {\n dayMap.set(session.date, {\n date: session.date,\n totalHours: session.durationMinutes / 60,\n sessionsCount: 1,\n totalCommits: session.commits.length,\n authors: [session.author],\n });\n }\n }\n\n return Array.from(dayMap.values()).sort((a, b) => a.date.localeCompare(b.date));\n}\n\n/**\n * Calculate total statistics from sessions\n */\nexport function calculateTotals(sessions: Session[]): AggregateStats {\n return calculateAggregateStats(sessions);\n}\n\n/**\n * Calculate the most productive day of week based on total hours\n */\nfunction calculateMostProductiveDayOfWeek(sessions: Session[]): string | undefined {\n if (sessions.length === 0) return undefined;\n\n const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n const dayHours = new Map<number, number>();\n\n for (const session of sessions) {\n const dayOfWeek = new Date(session.startTime).getDay();\n const hours = session.durationMinutes / 60;\n dayHours.set(dayOfWeek, (dayHours.get(dayOfWeek) || 0) + hours);\n }\n\n if (dayHours.size === 0) return undefined;\n\n // Find day with most hours\n let maxHours = 0;\n let maxDay = 0;\n for (const [day, hours] of dayHours.entries()) {\n if (hours > maxHours) {\n maxHours = hours;\n maxDay = day;\n }\n }\n\n return dayNames[maxDay];\n}\n\n/**\n * Calculate the longest streak of consecutive days with commits\n */\nfunction calculateLongestStreakDays(sessions: Session[]): number {\n if (sessions.length === 0) return 0;\n\n // Get unique sorted dates\n const dates = Array.from(new Set(sessions.map((s) => s.date))).sort();\n\n if (dates.length === 0) return 0;\n\n let currentStreak = 1;\n let longestStreak = 1;\n\n for (let i = 1; i < dates.length; i++) {\n const prevDate = new Date(dates[i - 1]);\n const currDate = new Date(dates[i]);\n\n // Calculate difference in days\n const diffTime = currDate.getTime() - prevDate.getTime();\n const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));\n\n if (diffDays === 1) {\n // Consecutive day\n currentStreak++;\n longestStreak = Math.max(longestStreak, currentStreak);\n } else {\n // Streak broken\n currentStreak = 1;\n }\n }\n\n return longestStreak;\n}\n\n/**\n * Calculate the minimum time between consecutive sessions by the same author\n * Returns undefined if no author has 2 or more sessions\n */\nfunction calculateMinTimeBetweenSessions(sessions: Session[]): number | undefined {\n if (sessions.length < 2) return undefined;\n\n // Group sessions by author\n const sessionsByAuthor = new Map<string, Session[]>();\n for (const session of sessions) {\n const existing = sessionsByAuthor.get(session.author) || [];\n existing.push(session);\n sessionsByAuthor.set(session.author, existing);\n }\n\n let minGapMinutes = Infinity;\n\n // For each author with 2+ sessions, find minimum gap between consecutive sessions\n for (const authorSessions of sessionsByAuthor.values()) {\n if (authorSessions.length < 2) continue;\n\n // Sort this author's sessions by end time\n const sortedSessions = [...authorSessions].sort((a, b) => a.endTime.getTime() - b.endTime.getTime());\n\n // Find gaps between consecutive sessions\n for (let i = 1; i < sortedSessions.length; i++) {\n const prevSessionEnd = sortedSessions[i - 1].endTime.getTime();\n const currentSessionStart = sortedSessions[i].startTime.getTime();\n\n const gapMinutes = (currentSessionStart - prevSessionEnd) / (1000 * 60);\n\n // Only consider positive gaps (non-overlapping sessions)\n if (gapMinutes > 0) {\n minGapMinutes = Math.min(minGapMinutes, gapMinutes);\n }\n }\n }\n\n // If minGapMinutes is still Infinity, no author had valid gaps between sessions\n if (minGapMinutes === Infinity) return undefined;\n\n return Math.round(minGapMinutes * 100) / 100;\n}\n","import type { RepoStats, StatsOptions } from '../model/types.js';\n\ninterface CacheEntry {\n data: RepoStats;\n timestamp: number;\n}\n\nclass MemoryCache {\n private cache = new Map<string, CacheEntry>();\n\n get(key: string, ttlMs: number): RepoStats | null {\n const entry = this.cache.get(key);\n if (!entry) return null;\n\n const now = Date.now();\n if (now - entry.timestamp > ttlMs) {\n // Entry expired\n this.cache.delete(key);\n return null;\n }\n\n return entry.data;\n }\n\n set(key: string, data: RepoStats): void {\n this.cache.set(key, {\n data,\n timestamp: Date.now(),\n });\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n size(): number {\n return this.cache.size;\n }\n}\n\n// Singleton instance\nconst memoryCache = new MemoryCache();\n\n/**\n * Generate a cache key from repo and options\n */\nexport function generateCacheKey(\n owner: string,\n repo: string,\n options: StatsOptions\n): string {\n const parts = [\n `${owner}/${repo}`,\n options.since?.toString() ?? '',\n options.until?.toString() ?? '',\n options.authors?.join(',') ?? '',\n options.excludeBots?.toString() ?? 'true',\n options.excludeMergeCommits?.toString() ?? 'false',\n options.sessionTimeoutMin?.toString() ?? '45',\n options.firstCommitBonusMin?.toString() ?? '15',\n options.timezone ?? 'UTC',\n ];\n\n return parts.join('|');\n}\n\n/**\n * Get data from cache if available and not expired\n */\nexport function getFromCache(\n key: string,\n options: StatsOptions\n): RepoStats | null {\n if (options.cache === 'none') {\n return null;\n }\n\n // Default TTL: 1 hour (3600000 ms)\n const ttlMs = options.cacheTTLms ?? 3600000;\n return memoryCache.get(key, ttlMs);\n}\n\n/**\n * Store data in cache\n */\nexport function setInCache(\n key: string,\n data: RepoStats,\n options: StatsOptions\n): void {\n if (options.cache === 'none') {\n return;\n }\n\n memoryCache.set(key, data);\n}\n\n/**\n * Clear all cache entries\n */\nexport function clearCache(): void {\n memoryCache.clear();\n}\n\n/**\n * Get cache size\n */\nexport function getCacheSize(): number {\n return memoryCache.size();\n}\n","import type { GitHubCommit, StatsOptions } from '../model/types.js';\n\n/**\n * Common bot patterns to detect automated commits\n */\nexport const BOT_PATTERNS = [\n 'bot',\n 'dependabot',\n 'renovate',\n 'github-actions',\n 'greenkeeper',\n 'snyk',\n 'codecov',\n 'travis',\n 'circleci',\n '[bot]',\n];\n\n/**\n * Check if a commit author is likely a bot\n */\nexport function isLikelyBot(authorName: string, authorLogin?: string): boolean {\n const nameLower = authorName.toLowerCase();\n const loginLower = authorLogin?.toLowerCase() || '';\n\n return BOT_PATTERNS.some(\n (pattern) => nameLower.includes(pattern) || loginLower.includes(pattern)\n );\n}\n\n/**\n * Check if a commit is a merge commit\n */\nexport function isMergeCommit(ghCommit: GitHubCommit): boolean {\n return (ghCommit.parents?.length || 0) > 1;\n}\n\n/**\n * Check if a commit should be included based on filter options\n */\nexport function shouldIncludeCommit(\n ghCommit: GitHubCommit,\n options: Pick<StatsOptions, 'excludeBots' | 'excludeMergeCommits' | 'authors'>\n): boolean {\n const { excludeBots = true, excludeMergeCommits = false, authors } = options;\n\n const authorName = ghCommit.commit.author.name;\n const authorLogin = ghCommit.author?.login;\n const isBot = isLikelyBot(authorName, authorLogin);\n const isMerge = isMergeCommit(ghCommit);\n\n // Apply filters\n if (excludeBots && isBot) return false;\n if (excludeMergeCommits && isMerge) return false;\n\n if (authors && authors.length > 0) {\n const matchesAuthor = authors.some(\n (a) =>\n a.toLowerCase() === authorName.toLowerCase() ||\n a.toLowerCase() === authorLogin?.toLowerCase()\n );\n if (!matchesAuthor) return false;\n }\n\n return true;\n}\n","import type { Commit, GitHubCommit } from '../model/types.js';\nimport { isLikelyBot, isMergeCommit } from './filters.js';\n\n/**\n * Transform a GitHub API commit to our internal Commit type\n */\nexport function transformGitHubCommit(ghCommit: GitHubCommit): Commit {\n const authorName = ghCommit.commit.author.name;\n const authorLogin = ghCommit.author?.login;\n const isBot = isLikelyBot(authorName, authorLogin);\n const isMerge = isMergeCommit(ghCommit);\n\n return {\n sha: ghCommit.sha,\n author: authorLogin || authorName,\n authorLogin,\n date: new Date(ghCommit.commit.author.date),\n message: ghCommit.commit.message,\n isMerge,\n isBot,\n };\n}\n","import type { RepoInput, StatsOptions, RepoStats, Commit } from './model/types.js';\nimport { parseRepoInput, fetchGitHubCommits } from './api/github.js';\nimport { buildSessions } from './logic/sessions.js';\nimport { aggregateByAuthor, aggregateByDay, calculateTotals } from './logic/aggregate.js';\nimport { generateCacheKey, getFromCache, setInCache } from './util/cache.js';\nimport { transformGitHubCommit } from './logic/transform.js';\nimport { shouldIncludeCommit } from './logic/filters.js';\n\n/**\n * Get coding statistics for a GitHub repository\n *\n * Analyzes commit history to estimate developer activity including hours spent,\n * coding sessions, and per-day/per-author breakdowns.\n *\n * @param repoInput - Repository identifier using either short format `{ repo: 'owner/repo' }` or URL format `{ url: 'https://github.com/owner/repo' }`\n * @param options - Configuration options for analysis (all optional)\n * @returns Promise resolving to detailed statistics about the repository\n *\n * @example\n * // Basic usage with short repo format\n * const stats = await getRepoStats({ repo: 'coffee-cpu/vibe-coding-stats' });\n *\n * @example\n * // Using full GitHub URL\n * const stats = await getRepoStats({ url: 'https://github.com/coffee-cpu/vibe-coding-stats' });\n *\n * @example\n * // With custom options\n * const stats = await getRepoStats(\n * { repo: 'coffee-cpu/vibe-coding-stats' },\n * {\n * sessionTimeoutMin: 60,\n * since: '2024-01-01',\n * timezone: 'America/New_York',\n * excludeBots: true\n * }\n * );\n */\nexport async function getRepoStats(\n repoInput: RepoInput,\n options: StatsOptions = {}\n): Promise<RepoStats> {\n\n // Parse repository\n const { owner, repo } = parseRepoInput(repoInput);\n const repoString = `${owner}/${repo}`;\n\n // Check cache\n const cacheKey = generateCacheKey(owner, repo, options);\n const cached = getFromCache(cacheKey, options);\n if (cached) {\n return cached;\n }\n\n // Set defaults\n const sessionTimeoutMin = options.sessionTimeoutMin ?? 45;\n const firstCommitBonusMin = options.firstCommitBonusMin ?? 15;\n const timezone = options.timezone ?? 'UTC';\n\n // Fetch raw commits from GitHub\n const rawCommits = await fetchGitHubCommits(owner, repo, options);\n\n // Transform and filter commits\n const commits: Commit[] = rawCommits\n .filter((ghCommit) => shouldIncludeCommit(ghCommit, options))\n .map((ghCommit) => transformGitHubCommit(ghCommit));\n\n // Build sessions\n const sessions = buildSessions(commits, {\n sessionTimeoutMin,\n firstCommitBonusMin,\n timezone,\n });\n\n // Aggregate statistics\n const totals = calculateTotals(sessions);\n const perAuthor = aggregateByAuthor(sessions);\n const perDay = aggregateByDay(sessions);\n\n // Build result\n const result: RepoStats = {\n repo: repoString,\n period: {\n since: options.since\n ? options.since instanceof Date\n ? options.since.toISOString()\n : options.since\n : undefined,\n until: options.until\n ? options.until instanceof Date\n ? options.until.toISOString()\n : options.until\n : undefined,\n },\n config: {\n sessionTimeoutMin,\n firstCommitBonusMin,\n timezone,\n },\n totals,\n perAuthor,\n perDay,\n };\n\n // Store in cache\n setInCache(cacheKey, result, options);\n\n return result;\n}\n\n// Re-export types\nexport type {\n RepoInput,\n StatsOptions,\n RepoStats,\n AuthorStats,\n DayStats,\n Session,\n Commit,\n StatsErrorCode,\n} from './model/types.js';\nexport { StatsError } from './model/types.js';\n\n// Re-export cache utilities\nexport { clearCache, getCacheSize } from './util/cache.js';\n"]}