gitfamiliar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/bin/gitfamiliar.js +514 -0
- package/dist/bin/gitfamiliar.js.map +1 -0
- package/dist/chunk-DW2PHZVZ.js +867 -0
- package/dist/chunk-DW2PHZVZ.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/templates/gitfamiliarignore.default +24 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/core/types.ts","../../src/cli/options.ts","../../src/cli/output/terminal.ts","../../src/cli/output/html.ts","../../src/utils/open-browser.ts","../../bin/gitfamiliar.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { parseOptions } from './options.js';\nimport { computeFamiliarity } from '../core/familiarity.js';\nimport { renderTerminal } from './output/terminal.js';\nimport { generateAndOpenHTML } from './output/html.js';\n\nexport function createProgram(): Command {\n const program = new Command();\n\n program\n .name('gitfamiliar')\n .description('Visualize your code familiarity from Git history')\n .version('0.1.0')\n .option('-m, --mode <mode>', 'Scoring mode: binary, authorship, review-coverage, weighted', 'binary')\n .option('-u, --user <user>', 'Git user name or email (defaults to git config)')\n .option('-f, --filter <filter>', 'Filter mode: all, written, reviewed', 'all')\n .option('-e, --expiration <policy>', 'Expiration policy: never, time:180d, change:50%, combined:365d:50%', 'never')\n .option('--html', 'Generate HTML treemap report', false)\n .option('-w, --weights <weights>', 'Weights for weighted mode: blame,commit,review (e.g., \"0.5,0.35,0.15\")')\n .action(async (rawOptions) => {\n try {\n const repoPath = process.cwd();\n const options = parseOptions(rawOptions, repoPath);\n const result = await computeFamiliarity(options);\n\n if (options.html) {\n await generateAndOpenHTML(result, repoPath);\n } else {\n renderTerminal(result);\n }\n } catch (error: any) {\n console.error(`Error: ${error.message}`);\n process.exit(1);\n }\n });\n\n return program;\n}\n","export type ScoringMode =\n | \"binary\"\n | \"authorship\"\n | \"review-coverage\"\n | \"weighted\";\nexport type FilterMode = \"all\" | \"written\" | \"reviewed\";\nexport type ExpirationPolicyType = \"never\" | \"time\" | \"change\" | \"combined\";\n\nexport interface ExpirationConfig {\n policy: ExpirationPolicyType;\n duration?: number; // days\n threshold?: number; // 0-1 (e.g., 0.5 for 50%)\n}\n\nexport interface WeightConfig {\n blame: number; // default 0.5\n commit: number; // default 0.35\n review: number; // default 0.15\n}\n\nexport interface CliOptions {\n mode: ScoringMode;\n user?: string;\n filter: FilterMode;\n expiration: ExpirationConfig;\n html: boolean;\n weights: WeightConfig;\n repoPath: string;\n}\n\nexport interface UserIdentity {\n name: string;\n email: string;\n}\n\nexport interface FileScore {\n type: \"file\";\n path: string;\n lines: number;\n score: number;\n isWritten?: boolean;\n isReviewed?: boolean;\n blameScore?: number;\n commitScore?: number;\n reviewScore?: number;\n isExpired?: boolean;\n lastTouchDate?: Date;\n}\n\nexport interface FolderScore {\n type: \"folder\";\n path: string;\n lines: number;\n score: number;\n fileCount: number;\n readCount?: number;\n children: TreeNode[];\n}\n\nexport type TreeNode = FileScore | FolderScore;\n\nexport interface CommitInfo {\n hash: string;\n date: Date;\n addedLines: number;\n deletedLines: number;\n fileSizeAtCommit: number;\n}\n\nexport interface ReviewInfo {\n date: Date;\n type: \"approved\" | \"commented\" | \"changes_requested\";\n filesInPR: number;\n}\n\nexport const DEFAULT_WEIGHTS: WeightConfig = {\n blame: 0.5,\n commit: 0.35,\n review: 0.15,\n};\n\nexport const DEFAULT_EXPIRATION: ExpirationConfig = {\n policy: \"never\",\n};\n","import type { CliOptions, ScoringMode, FilterMode, WeightConfig } from '../core/types.js';\nimport { DEFAULT_WEIGHTS, DEFAULT_EXPIRATION } from '../core/types.js';\nimport { parseExpirationConfig } from '../scoring/expiration.js';\n\nexport interface RawCliOptions {\n mode?: string;\n user?: string;\n filter?: string;\n expiration?: string;\n html?: boolean;\n weights?: string;\n}\n\nexport function parseOptions(raw: RawCliOptions, repoPath: string): CliOptions {\n const mode = validateMode(raw.mode || 'binary');\n const filter = validateFilter(raw.filter || 'all');\n\n let weights = DEFAULT_WEIGHTS;\n if (raw.weights) {\n weights = parseWeights(raw.weights);\n }\n\n const expiration = raw.expiration\n ? parseExpirationConfig(raw.expiration)\n : DEFAULT_EXPIRATION;\n\n return {\n mode,\n user: raw.user,\n filter,\n expiration,\n html: raw.html || false,\n weights,\n repoPath,\n };\n}\n\nfunction validateMode(mode: string): ScoringMode {\n const valid: ScoringMode[] = ['binary', 'authorship', 'review-coverage', 'weighted'];\n if (!valid.includes(mode as ScoringMode)) {\n throw new Error(`Invalid mode: \"${mode}\". Valid modes: ${valid.join(', ')}`);\n }\n return mode as ScoringMode;\n}\n\nfunction validateFilter(filter: string): FilterMode {\n const valid: FilterMode[] = ['all', 'written', 'reviewed'];\n if (!valid.includes(filter as FilterMode)) {\n throw new Error(`Invalid filter: \"${filter}\". Valid filters: ${valid.join(', ')}`);\n }\n return filter as FilterMode;\n}\n\nfunction parseWeights(s: string): WeightConfig {\n const parts = s.split(',').map(Number);\n if (parts.length !== 3 || parts.some(isNaN)) {\n throw new Error(`Invalid weights: \"${s}\". Expected format: \"0.5,0.35,0.15\"`);\n }\n const sum = parts[0] + parts[1] + parts[2];\n if (Math.abs(sum - 1) > 0.01) {\n throw new Error(`Weights must sum to 1.0, got ${sum}`);\n }\n return { blame: parts[0], commit: parts[1], review: parts[2] };\n}\n","import chalk from 'chalk';\nimport type { FamiliarityResult } from '../../core/familiarity.js';\nimport type { FolderScore, FileScore, TreeNode } from '../../core/types.js';\n\nconst BAR_WIDTH = 10;\nconst FILLED_CHAR = '\\u2588'; // █\nconst EMPTY_CHAR = '\\u2591'; // ░\n\nfunction makeBar(score: number): string {\n const filled = Math.round(score * BAR_WIDTH);\n const empty = BAR_WIDTH - filled;\n const bar = FILLED_CHAR.repeat(filled) + EMPTY_CHAR.repeat(empty);\n\n if (score >= 0.8) return chalk.green(bar);\n if (score >= 0.5) return chalk.yellow(bar);\n if (score > 0) return chalk.red(bar);\n return chalk.gray(bar);\n}\n\nfunction formatPercent(score: number): string {\n return `${Math.round(score * 100)}%`;\n}\n\nfunction getModeLabel(mode: string): string {\n switch (mode) {\n case 'binary': return 'Binary mode';\n case 'authorship': return 'Authorship mode';\n case 'review-coverage': return 'Review Coverage mode';\n case 'weighted': return 'Weighted mode';\n default: return mode;\n }\n}\n\nfunction renderFolder(\n node: FolderScore,\n indent: number,\n mode: string,\n maxDepth: number,\n): string[] {\n const lines: string[] = [];\n const prefix = ' '.repeat(indent);\n\n // Sort children: folders first, then files, by name\n const sorted = [...node.children].sort((a, b) => {\n if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;\n return a.path.localeCompare(b.path);\n });\n\n for (const child of sorted) {\n if (child.type === 'folder') {\n const folder = child as FolderScore;\n const name = folder.path.split('/').pop() + '/';\n const bar = makeBar(folder.score);\n const pct = formatPercent(folder.score);\n\n if (mode === 'binary') {\n const readCount = folder.readCount || 0;\n lines.push(\n `${prefix}${chalk.bold(name.padEnd(16))} ${bar} ${pct.padStart(4)} (${readCount}/${folder.fileCount} files)`,\n );\n } else {\n lines.push(\n `${prefix}${chalk.bold(name.padEnd(16))} ${bar} ${pct.padStart(4)}`,\n );\n }\n\n // Recurse if within depth limit\n if (indent < maxDepth) {\n lines.push(...renderFolder(folder, indent + 1, mode, maxDepth));\n }\n }\n }\n\n return lines;\n}\n\nexport function renderTerminal(result: FamiliarityResult): void {\n const { tree, repoName, mode } = result;\n\n console.log('');\n console.log(chalk.bold(`GitFamiliar \\u2014 ${repoName} (${getModeLabel(mode)})`));\n console.log('');\n\n if (mode === 'binary') {\n const readCount = tree.readCount || 0;\n const pct = formatPercent(tree.score);\n console.log(`Overall: ${readCount}/${tree.fileCount} files (${pct})`);\n } else {\n const pct = formatPercent(tree.score);\n console.log(`Overall: ${pct}`);\n }\n\n console.log('');\n\n const folderLines = renderFolder(tree, 1, mode, 2);\n for (const line of folderLines) {\n console.log(line);\n }\n\n console.log('');\n\n if (mode === 'binary') {\n const { writtenCount, reviewedCount, bothCount } = result;\n console.log(\n `Written: ${writtenCount} files | Reviewed: ${reviewedCount} files | Both: ${bothCount} files`,\n );\n console.log('');\n }\n}\n","import { writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { FamiliarityResult } from '../../core/familiarity.js';\nimport { openBrowser } from '../../utils/open-browser.js';\n\nfunction generateTreemapHTML(result: FamiliarityResult): string {\n const dataJson = JSON.stringify(result.tree);\n const mode = result.mode;\n const repoName = result.repoName;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>GitFamiliar \\u2014 ${repoName}</title>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #1a1a2e;\n color: #e0e0e0;\n overflow: hidden;\n }\n #header {\n padding: 16px 24px;\n background: #16213e;\n border-bottom: 1px solid #0f3460;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n #header h1 { font-size: 18px; color: #e94560; }\n #header .info { font-size: 14px; color: #a0a0a0; }\n #breadcrumb {\n padding: 8px 24px;\n background: #16213e;\n font-size: 13px;\n border-bottom: 1px solid #0f3460;\n }\n #breadcrumb span { cursor: pointer; color: #5eadf7; }\n #breadcrumb span:hover { text-decoration: underline; }\n #breadcrumb .sep { color: #666; margin: 0 4px; }\n #controls {\n padding: 8px 24px;\n background: #16213e;\n border-bottom: 1px solid #0f3460;\n display: flex;\n gap: 12px;\n align-items: center;\n }\n #controls button {\n padding: 4px 12px;\n border: 1px solid #0f3460;\n background: #1a1a2e;\n color: #e0e0e0;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n }\n #controls button.active {\n background: #e94560;\n border-color: #e94560;\n color: white;\n }\n #treemap { width: 100%; }\n #tooltip {\n position: absolute;\n pointer-events: none;\n background: rgba(22, 33, 62, 0.95);\n border: 1px solid #0f3460;\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 13px;\n line-height: 1.6;\n display: none;\n z-index: 100;\n max-width: 300px;\n }\n #legend {\n position: absolute;\n bottom: 16px;\n right: 16px;\n background: rgba(22, 33, 62, 0.9);\n border: 1px solid #0f3460;\n border-radius: 6px;\n padding: 10px;\n font-size: 12px;\n }\n #legend .gradient-bar {\n width: 120px;\n height: 12px;\n background: linear-gradient(to right, #e94560, #f5a623, #27ae60);\n border-radius: 3px;\n margin: 4px 0;\n }\n #legend .labels { display: flex; justify-content: space-between; font-size: 10px; color: #888; }\n</style>\n</head>\n<body>\n<div id=\"header\">\n <h1>GitFamiliar \\u2014 ${repoName}</h1>\n <div class=\"info\">${mode.charAt(0).toUpperCase() + mode.slice(1)} mode | ${result.totalFiles} files</div>\n</div>\n<div id=\"breadcrumb\"><span onclick=\"zoomTo('')\">root</span></div>\n${mode === 'binary' ? `\n<div id=\"controls\">\n <span style=\"font-size:12px;color:#888;\">Filter:</span>\n <button class=\"active\" onclick=\"setFilter('all')\">All</button>\n <button onclick=\"setFilter('written')\">Written only</button>\n <button onclick=\"setFilter('reviewed')\">Reviewed only</button>\n</div>` : ''}\n<div id=\"treemap\"></div>\n<div id=\"tooltip\"></div>\n<div id=\"legend\">\n <div>Familiarity</div>\n <div class=\"gradient-bar\"></div>\n <div class=\"labels\"><span>0%</span><span>50%</span><span>100%</span></div>\n</div>\n\n<script src=\"https://d3js.org/d3.v7.min.js\"></script>\n<script>\nconst rawData = ${dataJson};\nconst mode = \"${mode}\";\nlet currentFilter = 'all';\nlet currentPath = '';\n\nfunction scoreColor(score) {\n if (score <= 0) return '#e94560';\n if (score >= 1) return '#27ae60';\n if (score < 0.5) {\n const t = score / 0.5;\n return d3.interpolateRgb('#e94560', '#f5a623')(t);\n }\n const t = (score - 0.5) / 0.5;\n return d3.interpolateRgb('#f5a623', '#27ae60')(t);\n}\n\nfunction getFileScore(file) {\n if (mode !== 'binary') return file.score;\n if (currentFilter === 'written') return file.isWritten ? 1 : 0;\n if (currentFilter === 'reviewed') return file.isReviewed ? 1 : 0;\n return file.score;\n}\n\nfunction findNode(node, path) {\n if (node.path === path) return node;\n if (node.children) {\n for (const child of node.children) {\n const found = findNode(child, path);\n if (found) return found;\n }\n }\n return null;\n}\n\nfunction flattenFiles(node) {\n const files = [];\n function walk(n) {\n if (n.type === 'file') {\n files.push(n);\n } else if (n.children) {\n n.children.forEach(walk);\n }\n }\n walk(node);\n return files;\n}\n\nfunction render() {\n const container = document.getElementById('treemap');\n container.innerHTML = '';\n\n const headerH = document.getElementById('header').offsetHeight;\n const breadcrumbH = document.getElementById('breadcrumb').offsetHeight;\n const controlsEl = document.getElementById('controls');\n const controlsH = controlsEl ? controlsEl.offsetHeight : 0;\n const width = window.innerWidth;\n const height = window.innerHeight - headerH - breadcrumbH - controlsH;\n\n const targetNode = currentPath ? findNode(rawData, currentPath) : rawData;\n if (!targetNode) return;\n\n const hierarchyData = {\n name: targetNode.path || 'root',\n children: (targetNode.children || []).map(function buildChild(c) {\n if (c.type === 'file') {\n return { name: c.path.split('/').pop(), data: c, value: Math.max(1, c.lines) };\n }\n return {\n name: c.path.split('/').pop(),\n data: c,\n children: (c.children || []).map(buildChild),\n };\n }),\n };\n\n const root = d3.hierarchy(hierarchyData)\n .sum(d => d.value || 0)\n .sort((a, b) => (b.value || 0) - (a.value || 0));\n\n d3.treemap()\n .size([width, height])\n .padding(2)\n .paddingTop(18)\n .round(true)(root);\n\n const svg = d3.select('#treemap')\n .append('svg')\n .attr('width', width)\n .attr('height', height);\n\n const tooltip = document.getElementById('tooltip');\n\n // Draw groups (folders)\n const groups = svg.selectAll('g')\n .data(root.descendants().filter(d => d.depth > 0))\n .join('g')\n .attr('transform', d => \\`translate(\\${d.x0},\\${d.y0})\\`);\n\n groups.append('rect')\n .attr('width', d => Math.max(0, d.x1 - d.x0))\n .attr('height', d => Math.max(0, d.y1 - d.y0))\n .attr('fill', d => {\n if (d.data.data) {\n const score = d.children ? d.data.data.score : getFileScore(d.data.data);\n return scoreColor(score);\n }\n return '#333';\n })\n .attr('opacity', d => d.children ? 0.3 : 0.85)\n .attr('stroke', '#1a1a2e')\n .attr('stroke-width', 1)\n .attr('rx', 2)\n .style('cursor', d => d.children ? 'pointer' : 'default')\n .on('click', (event, d) => {\n if (d.children && d.data.data && d.data.data.type === 'folder') {\n zoomTo(d.data.data.path);\n }\n })\n .on('mouseover', (event, d) => {\n if (!d.data.data) return;\n const data = d.data.data;\n const name = data.path || d.data.name;\n const score = d.children ? data.score : getFileScore(data);\n let html = \\`<strong>\\${name}</strong><br>Score: \\${Math.round(score * 100)}%<br>Lines: \\${data.lines}\\`;\n if (data.type === 'folder') {\n html += \\`<br>Files: \\${data.fileCount}\\`;\n }\n if (data.blameScore !== undefined) {\n html += \\`<br>Blame: \\${Math.round(data.blameScore * 100)}%\\`;\n }\n if (data.commitScore !== undefined) {\n html += \\`<br>Commit: \\${Math.round(data.commitScore * 100)}%\\`;\n }\n if (data.reviewScore !== undefined) {\n html += \\`<br>Review: \\${Math.round(data.reviewScore * 100)}%\\`;\n }\n tooltip.innerHTML = html;\n tooltip.style.display = 'block';\n })\n .on('mousemove', (event) => {\n tooltip.style.left = (event.pageX + 12) + 'px';\n tooltip.style.top = (event.pageY - 12) + 'px';\n })\n .on('mouseout', () => {\n tooltip.style.display = 'none';\n });\n\n // Labels\n groups.append('text')\n .attr('x', 4)\n .attr('y', 13)\n .attr('fill', '#fff')\n .attr('font-size', '11px')\n .attr('font-weight', d => d.children ? 'bold' : 'normal')\n .text(d => {\n const w = (d.x1 - d.x0);\n const name = d.data.name || '';\n if (w < 40) return '';\n if (w < name.length * 7) return name.slice(0, Math.floor(w / 7)) + '..';\n return name;\n });\n}\n\nfunction zoomTo(path) {\n currentPath = path;\n updateBreadcrumb();\n render();\n}\n\nfunction updateBreadcrumb() {\n const el = document.getElementById('breadcrumb');\n const parts = currentPath ? currentPath.split('/') : [];\n let html = '<span onclick=\"zoomTo(\\\\'\\\\')\">root</span>';\n let accumulated = '';\n for (const part of parts) {\n accumulated = accumulated ? accumulated + '/' + part : part;\n const p = accumulated;\n html += \\`<span class=\"sep\">/</span><span onclick=\"zoomTo('\\${p}')\">\\${part}</span>\\`;\n }\n el.innerHTML = html;\n}\n\nfunction setFilter(f) {\n currentFilter = f;\n document.querySelectorAll('#controls button').forEach(btn => {\n btn.classList.toggle('active', btn.textContent.toLowerCase().includes(f));\n });\n render();\n}\n\nwindow.addEventListener('resize', render);\nrender();\n</script>\n</body>\n</html>`;\n}\n\nexport async function generateAndOpenHTML(\n result: FamiliarityResult,\n repoPath: string,\n): Promise<void> {\n const html = generateTreemapHTML(result);\n const outputPath = join(repoPath, 'gitfamiliar-report.html');\n\n writeFileSync(outputPath, html, 'utf-8');\n console.log(`Report generated: ${outputPath}`);\n\n await openBrowser(outputPath);\n}\n","export async function openBrowser(filePath: string): Promise<void> {\n try {\n const open = await import('open');\n await open.default(filePath);\n } catch {\n console.log(`Could not open browser automatically. Open this file manually:`);\n console.log(` ${filePath}`);\n }\n}\n","import { createProgram } from '../src/cli/index.js';\n\nconst program = createProgram();\nprogram.parse();\n"],"mappings":"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","names":["program"]}
|