gitfamiliar 0.4.0 → 0.5.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/dist/bin/gitfamiliar.js
CHANGED
|
@@ -704,9 +704,9 @@ var GitHubClient = class {
|
|
|
704
704
|
owner = match[2];
|
|
705
705
|
repo = match[3];
|
|
706
706
|
} else if (
|
|
707
|
-
// HTTPS format: https://hostname(:port)?/owner/repo.git
|
|
707
|
+
// HTTPS format: https://(user:token@)?hostname(:port)?/owner/repo.git
|
|
708
708
|
match = url.match(
|
|
709
|
-
/https?:\/\/([^/:]+)(?::\d+)?\/([^/]+)\/([^/.]+?)(\.git)?$/
|
|
709
|
+
/https?:\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/([^/]+)\/([^/.]+?)(\.git)?$/
|
|
710
710
|
)
|
|
711
711
|
) {
|
|
712
712
|
hostname = match[1];
|
|
@@ -933,4 +933,4 @@ export {
|
|
|
933
933
|
resolveGitHubToken,
|
|
934
934
|
computeFamiliarity
|
|
935
935
|
};
|
|
936
|
-
//# sourceMappingURL=chunk-
|
|
936
|
+
//# sourceMappingURL=chunk-LZ67KNHF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/git/client.ts","../src/git/identity.ts","../src/filter/ignore.ts","../src/filter/defaults.ts","../src/utils/line-count.ts","../src/core/file-tree.ts","../src/git/log.ts","../src/scoring/binary.ts","../src/git/blame.ts","../src/utils/batch.ts","../src/scoring/authorship.ts","../src/scoring/review-coverage.ts","../src/utils/math.ts","../src/scoring/weighted.ts","../src/git/diff.ts","../src/scoring/expiration.ts","../src/github/client.ts","../src/github/auth.ts","../src/github/reviews.ts","../src/core/familiarity.ts"],"sourcesContent":["import simpleGit, { type SimpleGit } from 'simple-git';\n\nexport class GitClient {\n private git: SimpleGit;\n readonly repoPath: string;\n\n constructor(repoPath: string) {\n this.repoPath = repoPath;\n this.git = simpleGit(repoPath);\n }\n\n async isRepo(): Promise<boolean> {\n return this.git.checkIsRepo();\n }\n\n async getRepoRoot(): Promise<string> {\n return (await this.git.revparse(['--show-toplevel'])).trim();\n }\n\n async getRepoName(): Promise<string> {\n const root = await this.getRepoRoot();\n return root.split('/').pop() || 'unknown';\n }\n\n async listFiles(): Promise<string[]> {\n const result = await this.git.raw(['ls-files']);\n return result.trim().split('\\n').filter(Boolean);\n }\n\n async getUserName(): Promise<string> {\n return (await this.git.raw(['config', 'user.name'])).trim();\n }\n\n async getUserEmail(): Promise<string> {\n return (await this.git.raw(['config', 'user.email'])).trim();\n }\n\n async getLog(args: string[]): Promise<string> {\n return this.git.raw(['log', ...args]);\n }\n\n async blame(filePath: string, options: string[] = []): Promise<string> {\n return this.git.raw(['blame', ...options, '--', filePath]);\n }\n\n async diff(args: string[]): Promise<string> {\n return this.git.raw(['diff', ...args]);\n }\n\n async show(args: string[]): Promise<string> {\n return this.git.raw(['show', ...args]);\n }\n\n async raw(args: string[]): Promise<string> {\n return this.git.raw(args);\n }\n\n async getRemoteUrl(): Promise<string | null> {\n try {\n const result = await this.git.raw(['remote', 'get-url', 'origin']);\n return result.trim();\n } catch {\n return null;\n }\n }\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity } from \"../core/types.js\";\n\nexport async function resolveUser(\n gitClient: GitClient,\n userFlag?: string,\n): Promise<UserIdentity> {\n if (userFlag) {\n // If it looks like an email, use it as email; otherwise as name\n if (userFlag.includes(\"@\")) {\n return { name: userFlag, email: userFlag };\n }\n return { name: userFlag, email: userFlag };\n }\n\n const name = await gitClient.getUserName();\n const email = await gitClient.getUserEmail();\n\n if (!name && !email) {\n throw new Error(\n \"Could not determine git user. Set git config user.name/user.email or use --user flag.\",\n );\n }\n\n return { name, email };\n}\n\n/**\n * Build git --author args for a user identity.\n */\nexport function getAuthorArgs(user: UserIdentity): string[] {\n return [\"--author\", user.email || user.name];\n}\n\nexport function matchesUser(\n authorName: string,\n authorEmail: string,\n user: UserIdentity,\n): boolean {\n // Match by email (primary) or name (fallback)\n if (user.email && authorEmail) {\n if (authorEmail.toLowerCase() === user.email.toLowerCase()) return true;\n }\n if (user.name && authorName) {\n if (authorName.toLowerCase() === user.name.toLowerCase()) return true;\n }\n return false;\n}\n","import ignore from 'ignore';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DEFAULT_IGNORE_PATTERNS } from './defaults.js';\n\nexport type FileFilter = (filePath: string) => boolean;\n\n/**\n * Create a file filter based on .gitfamiliarignore patterns.\n * Returns a function that returns true if the file should be INCLUDED.\n */\nexport function createFilter(repoRoot: string): FileFilter {\n const ig = ignore();\n\n const ignorePath = join(repoRoot, '.gitfamiliarignore');\n\n if (existsSync(ignorePath)) {\n const content = readFileSync(ignorePath, 'utf-8');\n ig.add(content);\n } else {\n ig.add(DEFAULT_IGNORE_PATTERNS);\n }\n\n return (filePath: string) => !ig.ignores(filePath);\n}\n","export const DEFAULT_IGNORE_PATTERNS = `# Lock files\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\nGemfile.lock\npoetry.lock\nCargo.lock\ncomposer.lock\n\n# Auto-generated\n*.generated.*\n*.min.js\n*.min.css\n*.map\n\n# Build outputs (if git-tracked)\ndist/\nbuild/\n.next/\n\n# Config that rarely needs understanding\n.eslintrc*\n.prettierrc*\ntsconfig.json\n`;\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Count lines in a file. Returns 0 for binary or unreadable files.\n */\nexport function countLines(repoRoot: string, filePath: string): number {\n try {\n const fullPath = join(repoRoot, filePath);\n const content = readFileSync(fullPath, 'utf-8');\n if (content.length === 0) return 0;\n return content.split('\\n').length;\n } catch {\n return 0;\n }\n}\n","import type { GitClient } from '../git/client.js';\nimport type { FileFilter } from '../filter/ignore.js';\nimport type { FolderScore, FileScore, TreeNode } from './types.js';\nimport { countLines } from '../utils/line-count.js';\n\n/**\n * Build a hierarchical file tree from git-tracked files.\n */\nexport async function buildFileTree(\n gitClient: GitClient,\n filter: FileFilter,\n): Promise<FolderScore> {\n const repoRoot = await gitClient.getRepoRoot();\n const allFiles = await gitClient.listFiles();\n const filteredFiles = allFiles.filter(filter);\n\n // Build flat file scores\n const fileScores: FileScore[] = filteredFiles.map((filePath) => ({\n type: 'file' as const,\n path: filePath,\n lines: countLines(repoRoot, filePath),\n score: 0,\n }));\n\n // Build tree structure\n return buildTreeFromFiles(fileScores);\n}\n\nfunction buildTreeFromFiles(files: FileScore[]): FolderScore {\n const root: FolderScore = {\n type: 'folder',\n path: '',\n lines: 0,\n score: 0,\n fileCount: 0,\n children: [],\n };\n\n // Group files by directory path\n const folderMap = new Map<string, FolderScore>();\n folderMap.set('', root);\n\n for (const file of files) {\n const parts = file.path.split('/');\n let currentPath = '';\n\n // Ensure all ancestor folders exist\n for (let i = 0; i < parts.length - 1; i++) {\n const parentPath = currentPath;\n currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];\n\n if (!folderMap.has(currentPath)) {\n const folder: FolderScore = {\n type: 'folder',\n path: currentPath,\n lines: 0,\n score: 0,\n fileCount: 0,\n children: [],\n };\n folderMap.set(currentPath, folder);\n\n // Add to parent\n const parent = folderMap.get(parentPath)!;\n parent.children.push(folder);\n }\n }\n\n // Add file to its parent folder\n const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n const parent = folderMap.get(parentPath)!;\n parent.children.push(file);\n }\n\n // Calculate aggregate line counts and file counts\n computeAggregates(root);\n\n return root;\n}\n\nfunction computeAggregates(node: FolderScore): void {\n let totalLines = 0;\n let totalFiles = 0;\n\n for (const child of node.children) {\n if (child.type === 'file') {\n totalLines += child.lines;\n totalFiles += 1;\n } else {\n computeAggregates(child);\n totalLines += child.lines;\n totalFiles += child.fileCount;\n }\n }\n\n node.lines = totalLines;\n node.fileCount = totalFiles;\n}\n\n/**\n * Walk all files in a tree, calling the visitor on each FileScore.\n */\nexport function walkFiles(\n node: TreeNode,\n visitor: (file: FileScore) => void,\n): void {\n if (node.type === 'file') {\n visitor(node);\n } else {\n for (const child of node.children) {\n walkFiles(child, visitor);\n }\n }\n}\n\n/**\n * Recompute folder scores after file scores have been updated.\n * For binary mode: score = readCount / fileCount\n * For other modes: score = weighted average by line count\n */\nexport function recomputeFolderScores(\n node: FolderScore,\n mode: 'binary' | 'continuous',\n): void {\n let readCount = 0;\n let totalFiles = 0;\n let weightedScore = 0;\n let totalLines = 0;\n\n for (const child of node.children) {\n if (child.type === 'file') {\n totalFiles += 1;\n totalLines += child.lines;\n weightedScore += child.score * child.lines;\n if (child.score > 0) readCount += 1;\n } else {\n recomputeFolderScores(child, mode);\n totalFiles += child.fileCount;\n totalLines += child.lines;\n weightedScore += child.score * child.lines;\n readCount += child.readCount || 0;\n }\n }\n\n node.fileCount = totalFiles;\n node.readCount = readCount;\n\n if (mode === 'binary') {\n node.score = totalFiles > 0 ? readCount / totalFiles : 0;\n } else {\n node.score = totalLines > 0 ? weightedScore / totalLines : 0;\n }\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity, CommitInfo } from \"../core/types.js\";\nimport { getAuthorArgs } from \"./identity.js\";\n\n/**\n * Get the set of files that a user has committed to (any commit, any time).\n * Used for Binary mode.\n */\nexport async function getFilesCommittedByUser(\n gitClient: GitClient,\n user: UserIdentity,\n): Promise<Set<string>> {\n const files = new Set<string>();\n\n // Query by both email and name to catch alias mismatches\n const queries: string[][] = [];\n if (user.email) {\n queries.push([\"--author\", user.email]);\n }\n if (user.name && user.name !== user.email) {\n queries.push([\"--author\", user.name]);\n }\n\n for (const authorArgs of queries) {\n try {\n const output = await gitClient.getLog([\n ...authorArgs,\n \"--name-only\",\n \"--pretty=format:\",\n \"--all\",\n ]);\n for (const line of output.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed) {\n files.add(trimmed);\n }\n }\n } catch {\n // Skip if no commits found\n }\n }\n\n return files;\n}\n\n/**\n * Get detailed commit information for a specific file by a specific user.\n * Used for Weighted mode's commit_score.\n */\nexport async function getDetailedCommits(\n gitClient: GitClient,\n user: UserIdentity,\n filePath: string,\n): Promise<CommitInfo[]> {\n const commits: CommitInfo[] = [];\n\n try {\n const output = await gitClient.getLog([\n ...getAuthorArgs(user),\n \"--numstat\",\n \"--pretty=format:%H|%aI\",\n \"--\",\n filePath,\n ]);\n\n const lines = output.trim().split(\"\\n\");\n let currentHash = \"\";\n let currentDate = new Date();\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n if (trimmed.includes(\"|\")) {\n const parts = trimmed.split(\"|\");\n currentHash = parts[0];\n currentDate = new Date(parts[1]);\n } else {\n const statMatch = trimmed.match(/^(\\d+|-)\\t(\\d+|-)\\t(.+)$/);\n if (statMatch && statMatch[3] === filePath) {\n const added = statMatch[1] === \"-\" ? 0 : parseInt(statMatch[1], 10);\n const deleted = statMatch[2] === \"-\" ? 0 : parseInt(statMatch[2], 10);\n\n // Get file size at that commit\n let fileSizeAtCommit = 1;\n try {\n const content = await gitClient.show([\n `${currentHash}:${filePath}`,\n ]);\n fileSizeAtCommit = Math.max(1, content.split(\"\\n\").length);\n } catch {\n fileSizeAtCommit = Math.max(1, added);\n }\n\n commits.push({\n hash: currentHash,\n date: currentDate,\n addedLines: added,\n deletedLines: deleted,\n fileSizeAtCommit,\n });\n }\n }\n }\n } catch {\n // No commits found\n }\n\n return commits;\n}\n\n/**\n * Get the last date a user touched a specific file (commit).\n */\nexport async function getLastCommitDate(\n gitClient: GitClient,\n user: UserIdentity,\n filePath: string,\n): Promise<Date | null> {\n try {\n const output = await gitClient.getLog([\n ...getAuthorArgs(user),\n \"-1\",\n \"--pretty=format:%aI\",\n \"--\",\n filePath,\n ]);\n const trimmed = output.trim();\n if (trimmed) {\n return new Date(trimmed);\n }\n } catch {\n // No commits found\n }\n return null;\n}\n","import type { FolderScore, FilterMode } from \"../core/types.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\n\n/**\n * Score files in binary mode (read / not read).\n */\nexport function scoreBinary(\n tree: FolderScore,\n writtenFiles: Set<string>,\n reviewedFiles: Set<string>,\n filterMode: FilterMode,\n expiredFiles?: Set<string>,\n): void {\n walkFiles(tree, (file) => {\n const isWritten = writtenFiles.has(file.path);\n const isReviewed =\n reviewedFiles.has(file.path) && !writtenFiles.has(file.path);\n const isExpired = expiredFiles?.has(file.path) ?? false;\n\n file.isWritten = isWritten;\n file.isReviewed = isReviewed;\n file.isExpired = isExpired;\n\n if (isExpired) {\n file.score = 0;\n return;\n }\n\n switch (filterMode) {\n case \"written\":\n file.score = isWritten ? 1 : 0;\n break;\n case \"reviewed\":\n file.score = isReviewed ? 1 : 0;\n break;\n case \"all\":\n default:\n file.score = isWritten || isReviewed ? 1 : 0;\n break;\n }\n });\n\n recomputeFolderScores(tree, \"binary\");\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity } from \"../core/types.js\";\nimport { matchesUser } from \"./identity.js\";\n\nexport interface BlameEntry {\n email: string;\n name: string;\n lines: number;\n}\n\nexport interface BlameResult {\n entries: BlameEntry[];\n totalLines: number;\n}\n\n/**\n * Run git blame on a file and return per-author line counts.\n * Uses -w to ignore whitespace changes.\n */\nexport async function getBlameData(\n gitClient: GitClient,\n filePath: string,\n): Promise<BlameResult> {\n const authorMap = new Map<string, BlameEntry>();\n let totalLines = 0;\n\n try {\n const output = await gitClient.blame(filePath, [\"-w\", \"--porcelain\"]);\n const lines = output.split(\"\\n\");\n\n let currentName = \"\";\n let currentEmail = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"author \")) {\n currentName = line.slice(\"author \".length).trim();\n } else if (line.startsWith(\"author-mail \")) {\n currentEmail = line\n .slice(\"author-mail \".length)\n .replace(/[<>]/g, \"\")\n .trim();\n } else if (line.startsWith(\"\\t\")) {\n // Content line - count for current author\n if (currentEmail || currentName) {\n totalLines++;\n const key = `${currentEmail}|${currentName}`;\n const existing = authorMap.get(key);\n if (existing) {\n existing.lines++;\n } else {\n authorMap.set(key, {\n email: currentEmail,\n name: currentName,\n lines: 1,\n });\n }\n }\n }\n }\n } catch {\n // Binary file or other error - return empty\n }\n\n return { entries: Array.from(authorMap.values()), totalLines };\n}\n\n/**\n * Get the number of lines authored by a specific user in a file.\n */\nexport async function getUserBlameLines(\n gitClient: GitClient,\n filePath: string,\n user: UserIdentity,\n): Promise<{ userLines: number; totalLines: number }> {\n const { entries, totalLines } = await getBlameData(gitClient, filePath);\n\n let userLines = 0;\n for (const entry of entries) {\n if (matchesUser(entry.name, entry.email, user)) {\n userLines += entry.lines;\n }\n }\n\n return { userLines, totalLines };\n}\n","const DEFAULT_BATCH_SIZE = 10;\n\n/**\n * Process items in batches with concurrent execution within each batch.\n */\nexport async function processBatch<T>(\n items: T[],\n fn: (item: T) => Promise<void>,\n batchSize: number = DEFAULT_BATCH_SIZE,\n): Promise<void> {\n for (let i = 0; i < items.length; i += batchSize) {\n const batch = items.slice(i, i + batchSize);\n await Promise.all(batch.map(fn));\n }\n}\n","import type { FolderScore, UserIdentity } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getUserBlameLines } from \"../git/blame.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\nimport { processBatch } from \"../utils/batch.js\";\n\n/**\n * Score files by authorship (git blame-based).\n * score(file) = blame_lines(user) / total_lines(file)\n */\nexport async function scoreAuthorship(\n tree: FolderScore,\n gitClient: GitClient,\n user: UserIdentity,\n): Promise<void> {\n const files: Array<{ path: string; setScore: (s: number) => void }> = [];\n\n walkFiles(tree, (file) => {\n files.push({\n path: file.path,\n setScore: (s) => {\n file.score = s;\n file.blameScore = s;\n },\n });\n });\n\n await processBatch(files, async ({ path, setScore }) => {\n const { userLines, totalLines } = await getUserBlameLines(\n gitClient,\n path,\n user,\n );\n setScore(totalLines > 0 ? userLines / totalLines : 0);\n });\n\n recomputeFolderScores(tree, \"continuous\");\n}\n","import type { FolderScore, ReviewInfo } from '../core/types.js';\nimport { walkFiles, recomputeFolderScores } from '../core/file-tree.js';\n\n/**\n * Score files by review coverage.\n * Files that the user has reviewed (via PR) get score 1, others 0.\n * User's own commits are excluded.\n */\nexport function scoreReviewCoverage(\n tree: FolderScore,\n reviewedFiles: Set<string>,\n): void {\n walkFiles(tree, (file) => {\n file.isReviewed = reviewedFiles.has(file.path);\n file.score = file.isReviewed ? 1 : 0;\n });\n\n recomputeFolderScores(tree, 'binary');\n}\n","/**\n * Sigmoid function: x / (x + k)\n * Saturates contribution of a single commit to 0-1 range.\n */\nexport function sigmoid(x: number, k: number = 0.3): number {\n if (x <= 0) return 0;\n return x / (x + k);\n}\n\n/**\n * Recency decay: e^(-lambda * t)\n * Models memory decay over time.\n * @param days - days since the event\n * @param halfLife - number of days for score to halve (default 180)\n */\nexport function recencyDecay(days: number, halfLife: number = 180): number {\n if (days <= 0) return 1;\n const lambda = Math.LN2 / halfLife;\n return Math.exp(-lambda * days);\n}\n\n/**\n * Scope factor: min(1, attentionThreshold / filesInPR)\n * Models attention dilution in large PRs.\n */\nexport function scopeFactor(\n filesInPR: number,\n attentionThreshold: number = 20,\n): number {\n if (filesInPR <= 0) return 1;\n return Math.min(1, attentionThreshold / filesInPR);\n}\n\n/**\n * Normalized diff: (added + 0.5 * deleted) / fileSize\n */\nexport function normalizedDiff(\n added: number,\n deleted: number,\n fileSize: number,\n): number {\n if (fileSize <= 0) return 0;\n return (added + 0.5 * deleted) / fileSize;\n}\n\n/**\n * Calculate the number of days between two dates.\n */\nexport function daysBetween(a: Date, b: Date): number {\n const ms = Math.abs(b.getTime() - a.getTime());\n return ms / (1000 * 60 * 60 * 24);\n}\n","import type {\n FolderScore,\n UserIdentity,\n WeightConfig,\n ReviewInfo,\n CommitInfo,\n} from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getUserBlameLines } from \"../git/blame.js\";\nimport { getDetailedCommits } from \"../git/log.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\nimport { processBatch } from \"../utils/batch.js\";\nimport {\n sigmoid,\n recencyDecay,\n scopeFactor,\n normalizedDiff,\n daysBetween,\n} from \"../utils/math.js\";\n\nconst REVIEW_BASE_WEIGHTS: Record<string, number> = {\n approved: 0.3,\n commented: 0.15,\n changes_requested: 0.35,\n};\n\nfunction calculateCommitScore(commits: CommitInfo[], now: Date): number {\n let raw = 0;\n for (const c of commits) {\n const nd = normalizedDiff(c.addedLines, c.deletedLines, c.fileSizeAtCommit);\n raw += sigmoid(nd) * recencyDecay(daysBetween(now, c.date));\n }\n return Math.min(1, raw);\n}\n\nfunction calculateReviewScore(\n reviews: ReviewInfo[] | undefined,\n now: Date,\n): number {\n if (!reviews) return 0;\n let raw = 0;\n for (const r of reviews) {\n const baseWeight = REVIEW_BASE_WEIGHTS[r.type] || 0.15;\n raw +=\n baseWeight *\n scopeFactor(r.filesInPR) *\n recencyDecay(daysBetween(now, r.date));\n }\n return Math.min(1, raw);\n}\n\n/**\n * Score files using the weighted mode (blame + commit + review signals).\n */\nexport async function scoreWeighted(\n tree: FolderScore,\n gitClient: GitClient,\n user: UserIdentity,\n weights: WeightConfig,\n reviewData?: Map<string, ReviewInfo[]>,\n now?: Date,\n): Promise<void> {\n const currentDate = now || new Date();\n const files: Array<{\n path: string;\n setScores: (b: number, c: number, r: number, total: number) => void;\n }> = [];\n\n walkFiles(tree, (file) => {\n files.push({\n path: file.path,\n setScores: (b, c, r, total) => {\n file.blameScore = b;\n file.commitScore = c;\n file.reviewScore = r;\n file.score = total;\n },\n });\n });\n\n await processBatch(files, async ({ path, setScores }) => {\n const { userLines, totalLines } = await getUserBlameLines(\n gitClient,\n path,\n user,\n );\n const blameScore = totalLines > 0 ? userLines / totalLines : 0;\n const commitScore = calculateCommitScore(\n await getDetailedCommits(gitClient, user, path),\n currentDate,\n );\n const reviewScore = calculateReviewScore(\n reviewData?.get(path),\n currentDate,\n );\n\n const total =\n weights.blame * blameScore +\n weights.commit * commitScore +\n weights.review * reviewScore;\n\n setScores(blameScore, commitScore, reviewScore, total);\n });\n\n recomputeFolderScores(tree, \"continuous\");\n}\n","import type { GitClient } from './client.js';\n\n/**\n * Calculate how much a file has changed since a given commit.\n * Returns the ratio of changed lines to current total lines.\n * Used for change-based expiration policy.\n */\nexport async function getChangeRatio(\n gitClient: GitClient,\n filePath: string,\n sinceCommit: string,\n): Promise<number> {\n try {\n const output = await gitClient.diff([\n '--numstat',\n `${sinceCommit}..HEAD`,\n '--',\n filePath,\n ]);\n\n const trimmed = output.trim();\n if (!trimmed) return 0;\n\n const match = trimmed.match(/^(\\d+|-)\\t(\\d+|-)\\t/);\n if (!match) return 0;\n\n const added = match[1] === '-' ? 0 : parseInt(match[1], 10);\n const deleted = match[2] === '-' ? 0 : parseInt(match[2], 10);\n const changedLines = added + deleted;\n\n // Get current file line count\n const currentContent = await gitClient.show([`HEAD:${filePath}`]);\n const currentLines = Math.max(1, currentContent.split('\\n').length);\n\n return changedLines / currentLines;\n } catch {\n return 0;\n }\n}\n\n/**\n * Get the commit hash for the last time a user touched a file.\n */\nexport async function getLastTouchCommit(\n gitClient: GitClient,\n filePath: string,\n userEmail: string,\n): Promise<string | null> {\n try {\n const output = await gitClient.getLog([\n '--author', userEmail,\n '-1',\n '--pretty=format:%H',\n '--',\n filePath,\n ]);\n const hash = output.trim();\n return hash || null;\n } catch {\n return null;\n }\n}\n","import type { ExpirationConfig, UserIdentity } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getLastCommitDate } from \"../git/log.js\";\nimport { getLastTouchCommit, getChangeRatio } from \"../git/diff.js\";\nimport { processBatch } from \"../utils/batch.js\";\n\n/**\n * Parse an expiration string from CLI into ExpirationConfig.\n * Formats: \"never\", \"time:180d\", \"change:50%\", \"combined:365d:50%\"\n */\nexport function parseExpirationConfig(input: string): ExpirationConfig {\n if (!input || input === \"never\") {\n return { policy: \"never\" };\n }\n\n if (input.startsWith(\"time:\")) {\n const duration = parseDays(input.slice(\"time:\".length));\n return { policy: \"time\", duration };\n }\n\n if (input.startsWith(\"change:\")) {\n const threshold = parsePercentage(input.slice(\"change:\".length));\n return { policy: \"change\", threshold };\n }\n\n if (input.startsWith(\"combined:\")) {\n const parts = input.slice(\"combined:\".length).split(\":\");\n const duration = parseDays(parts[0]);\n const threshold = parts[1] ? parsePercentage(parts[1]) : 0.5;\n return { policy: \"combined\", duration, threshold };\n }\n\n return { policy: \"never\" };\n}\n\nfunction parseDays(s: string): number {\n const match = s.match(/^(\\d+)d$/);\n if (!match)\n throw new Error(\n `Invalid duration format: \"${s}\". Expected format like \"180d\".`,\n );\n return parseInt(match[1], 10);\n}\n\nfunction parsePercentage(s: string): number {\n const match = s.match(/^(\\d+)%$/);\n if (!match)\n throw new Error(\n `Invalid percentage format: \"${s}\". Expected format like \"50%\".`,\n );\n return parseInt(match[1], 10) / 100;\n}\n\n/**\n * Check if a file's familiarity has expired according to the given policy.\n */\nexport async function isExpired(\n gitClient: GitClient,\n filePath: string,\n user: UserIdentity,\n config: ExpirationConfig,\n now?: Date,\n): Promise<boolean> {\n if (config.policy === \"never\") return false;\n\n const currentDate = now || new Date();\n const email = user.email || user.name;\n\n if (config.policy === \"time\" || config.policy === \"combined\") {\n const lastTouch = await getLastCommitDate(gitClient, user, filePath);\n if (lastTouch && config.duration) {\n const daysSince =\n (currentDate.getTime() - lastTouch.getTime()) / (1000 * 60 * 60 * 24);\n if (daysSince > config.duration) return true;\n }\n }\n\n if (config.policy === \"change\" || config.policy === \"combined\") {\n const lastCommit = await getLastTouchCommit(gitClient, filePath, email);\n if (lastCommit && config.threshold) {\n const ratio = await getChangeRatio(gitClient, filePath, lastCommit);\n if (ratio > config.threshold) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Get the set of expired files for a given user and config.\n */\nexport async function getExpiredFiles(\n gitClient: GitClient,\n files: string[],\n user: UserIdentity,\n config: ExpirationConfig,\n): Promise<Set<string>> {\n if (config.policy === \"never\") return new Set();\n\n const expiredSet = new Set<string>();\n\n await processBatch(files, async (filePath) => {\n if (await isExpired(gitClient, filePath, user, config)) {\n expiredSet.add(filePath);\n }\n });\n\n return expiredSet;\n}\n","import type { ReviewInfo } from \"../core/types.js\";\n\ninterface GitHubReview {\n state: string;\n submitted_at: string;\n}\n\nexport interface GitHubRemoteInfo {\n hostname: string; // e.g. \"github.com\" or \"ghe.example.com\"\n owner: string;\n repo: string;\n apiBaseUrl: string; // e.g. \"https://api.github.com\" or \"https://ghe.example.com/api/v3\"\n}\n\n/**\n * Minimal GitHub client using fetch (no external dependency).\n * Supports both github.com and GitHub Enterprise.\n */\nexport class GitHubClient {\n private token: string;\n private baseUrl: string;\n\n constructor(token: string, apiBaseUrl: string = \"https://api.github.com\") {\n this.token = token;\n this.baseUrl = apiBaseUrl.replace(/\\/+$/, \"\");\n }\n\n private async fetch(path: string): Promise<any> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n Accept: \"application/vnd.github.v3+json\",\n \"User-Agent\": \"gitfamiliar\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 403) {\n throw new Error(\n \"GitHub API rate limit exceeded. Please wait or use a token with higher limits.\",\n );\n }\n throw new Error(\n `GitHub API error: ${response.status} ${response.statusText}`,\n );\n }\n\n return response.json();\n }\n\n /**\n * Verify API connectivity by fetching the authenticated user.\n */\n async verifyConnection(): Promise<{ login: string; name: string | null }> {\n const user = await this.fetch(\"/user\");\n return { login: user.login, name: user.name };\n }\n\n /**\n * Parse owner/repo/hostname from a git remote URL.\n * Supports github.com and GitHub Enterprise hosts.\n */\n static parseRemoteUrl(\n url: string,\n overrideHostname?: string,\n ): GitHubRemoteInfo | null {\n let hostname: string;\n let owner: string;\n let repo: string;\n\n // SSH format: git@hostname:owner/repo.git\n let match = url.match(/git@([^:]+):([^/]+)\\/([^/.]+)(\\.git)?$/);\n if (match) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else if (\n // SSH URL format: ssh://git@hostname(:port)?/owner/repo.git\n (match = url.match(\n /ssh:\\/\\/[^@]+@([^:/]+)(?::\\d+)?\\/([^/]+)\\/([^/.]+?)(\\.git)?$/,\n ))\n ) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else if (\n // HTTPS format: https://hostname(:port)?/owner/repo.git\n (match = url.match(\n /https?:\\/\\/([^/:]+)(?::\\d+)?\\/([^/]+)\\/([^/.]+?)(\\.git)?$/,\n ))\n ) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else {\n return null;\n }\n\n if (overrideHostname) {\n hostname = overrideHostname;\n }\n\n const apiBaseUrl =\n hostname === \"github.com\"\n ? \"https://api.github.com\"\n : `https://${hostname}/api/v3`;\n\n return { hostname, owner, repo, apiBaseUrl };\n }\n\n /**\n * Get all files reviewed by a user across all PRs they reviewed.\n */\n async getReviewedFiles(\n owner: string,\n repo: string,\n username: string,\n ): Promise<Map<string, ReviewInfo[]>> {\n const reviewedFiles = new Map<string, ReviewInfo[]>();\n\n let page = 1;\n const perPage = 100;\n\n while (true) {\n const searchResult = await this.fetch(\n `/search/issues?q=type:pr+repo:${owner}/${repo}+reviewed-by:${username}&per_page=${perPage}&page=${page}`,\n );\n\n if (!searchResult.items || searchResult.items.length === 0) break;\n\n for (const item of searchResult.items) {\n const prNumber = item.number;\n\n const reviews: GitHubReview[] = await this.fetch(\n `/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,\n );\n\n const userReviews = reviews.filter(\n (r: any) => r.user?.login?.toLowerCase() === username.toLowerCase(),\n );\n\n if (userReviews.length === 0) continue;\n\n const prFiles: any[] = await this.fetch(\n `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,\n );\n\n const fileCount = prFiles.length;\n\n for (const review of userReviews) {\n const reviewType = mapReviewState(review.state);\n const reviewDate = new Date(review.submitted_at);\n\n for (const prFile of prFiles) {\n const filePath = prFile.filename;\n const info: ReviewInfo = {\n date: reviewDate,\n type: reviewType,\n filesInPR: fileCount,\n };\n\n if (reviewedFiles.has(filePath)) {\n reviewedFiles.get(filePath)!.push(info);\n } else {\n reviewedFiles.set(filePath, [info]);\n }\n }\n }\n }\n\n if (searchResult.items.length < perPage) break;\n page++;\n }\n\n return reviewedFiles;\n }\n}\n\nfunction mapReviewState(state: string): ReviewInfo[\"type\"] {\n switch (state.toUpperCase()) {\n case \"APPROVED\":\n return \"approved\";\n case \"CHANGES_REQUESTED\":\n return \"changes_requested\";\n default:\n return \"commented\";\n }\n}\n","import { execSync } from \"node:child_process\";\n\n/**\n * Resolve GitHub token from environment or gh CLI.\n * For GitHub Enterprise, pass the hostname (e.g. \"ghe.example.com\")\n * to use `gh auth token --hostname <host>`.\n */\nexport function resolveGitHubToken(hostname?: string): string | null {\n // Check environment variables\n if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;\n if (process.env.GH_TOKEN) return process.env.GH_TOKEN;\n\n // Try gh CLI (always pass --hostname for explicit host resolution)\n try {\n const host = hostname || \"github.com\";\n const token = execSync(`gh auth token --hostname ${host}`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (token) return token;\n } catch {\n // gh CLI not available or not authenticated for this hostname\n }\n\n // Fallback: try gh auth token without --hostname (uses default host)\n if (hostname && hostname !== \"github.com\") {\n try {\n const token = execSync(\"gh auth token\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (token) return token;\n } catch {\n // gh CLI not available or not authenticated\n }\n }\n\n return null;\n}\n","import type { ReviewInfo } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { GitHubClient } from \"./client.js\";\nimport { resolveGitHubToken } from \"./auth.js\";\n\n/**\n * Attempt to fetch review data from GitHub.\n * Returns null if no token or not a GitHub repo.\n * Supports GitHub Enterprise by auto-detecting the hostname from git remote.\n * @param githubUrl - Optional override for GitHub hostname (e.g. \"ghe.example.com\")\n */\nexport async function fetchReviewData(\n gitClient: GitClient,\n username?: string,\n githubUrl?: string,\n): Promise<{\n reviewedFiles: Map<string, ReviewInfo[]>;\n reviewedFileSet: Set<string>;\n} | null> {\n const remoteUrl = await gitClient.getRemoteUrl();\n if (!remoteUrl) return null;\n\n const parsed = GitHubClient.parseRemoteUrl(remoteUrl, githubUrl);\n if (!parsed) return null;\n\n const token = resolveGitHubToken(parsed.hostname);\n if (!token) return null;\n\n // GitHub username is required for review API queries\n if (!username) return null;\n const ghUsername = username;\n\n try {\n const githubClient = new GitHubClient(token, parsed.apiBaseUrl);\n const reviewedFiles = await githubClient.getReviewedFiles(\n parsed.owner,\n parsed.repo,\n ghUsername,\n );\n\n const reviewedFileSet = new Set(reviewedFiles.keys());\n\n return { reviewedFiles, reviewedFileSet };\n } catch {\n return null;\n }\n}\n","import type { CliOptions, FolderScore, ReviewInfo } from \"./types.js\";\nimport { GitClient } from \"../git/client.js\";\nimport { resolveUser } from \"../git/identity.js\";\nimport { createFilter } from \"../filter/ignore.js\";\nimport { buildFileTree, walkFiles } from \"./file-tree.js\";\nimport { getFilesCommittedByUser } from \"../git/log.js\";\nimport { scoreBinary } from \"../scoring/binary.js\";\nimport { scoreAuthorship } from \"../scoring/authorship.js\";\nimport { scoreReviewCoverage } from \"../scoring/review-coverage.js\";\nimport { scoreWeighted } from \"../scoring/weighted.js\";\nimport { getExpiredFiles } from \"../scoring/expiration.js\";\nimport { fetchReviewData } from \"../github/reviews.js\";\n\nexport interface FamiliarityResult {\n tree: FolderScore;\n repoName: string;\n userName: string;\n mode: string;\n writtenCount: number;\n reviewedCount: number;\n bothCount: number;\n totalFiles: number;\n}\n\nexport async function computeFamiliarity(\n options: CliOptions,\n): Promise<FamiliarityResult> {\n const gitClient = new GitClient(options.repoPath);\n\n if (!(await gitClient.isRepo())) {\n throw new Error(`\"${options.repoPath}\" is not a git repository.`);\n }\n\n const repoRoot = await gitClient.getRepoRoot();\n const repoName = await gitClient.getRepoName();\n const userFlag = Array.isArray(options.user) ? options.user[0] : options.user;\n const user = await resolveUser(gitClient, userFlag);\n const filter = createFilter(repoRoot);\n const tree = await buildFileTree(gitClient, filter);\n\n // Get written files (used by binary, weighted)\n const writtenFiles = await getFilesCommittedByUser(gitClient, user);\n\n // Get review data (if available)\n let reviewData: Map<string, ReviewInfo[]> | undefined;\n let reviewedFileSet = new Set<string>();\n\n if (options.mode !== \"authorship\") {\n const reviewResult = await fetchReviewData(\n gitClient,\n userFlag,\n options.githubUrl,\n );\n if (reviewResult) {\n reviewData = reviewResult.reviewedFiles;\n reviewedFileSet = reviewResult.reviewedFileSet;\n }\n }\n\n // Get expired files\n let expiredFiles: Set<string> | undefined;\n if (options.expiration.policy !== \"never\") {\n const allFiles: string[] = [];\n walkFiles(tree, (f) => allFiles.push(f.path));\n expiredFiles = await getExpiredFiles(\n gitClient,\n allFiles,\n user,\n options.expiration,\n );\n }\n\n // Score based on mode\n switch (options.mode) {\n case \"binary\":\n scoreBinary(\n tree,\n writtenFiles,\n reviewedFileSet,\n options.filter,\n expiredFiles,\n );\n break;\n\n case \"authorship\":\n await scoreAuthorship(tree, gitClient, user);\n break;\n\n case \"review-coverage\":\n if (reviewedFileSet.size === 0) {\n console.error(\n \"Warning: No review data available. Set GITHUB_TOKEN or use --user with your GitHub username.\",\n );\n }\n scoreReviewCoverage(tree, reviewedFileSet);\n break;\n\n case \"weighted\":\n await scoreWeighted(tree, gitClient, user, options.weights, reviewData);\n break;\n }\n\n return {\n tree,\n repoName,\n userName: user.name || user.email,\n mode: options.mode,\n ...computeSummary(tree, writtenFiles, reviewedFileSet),\n totalFiles: tree.fileCount,\n };\n}\n\nfunction computeSummary(\n tree: FolderScore,\n writtenFiles: Set<string>,\n reviewedFileSet: Set<string>,\n): { writtenCount: number; reviewedCount: number; bothCount: number } {\n let writtenOnly = 0;\n let reviewedOnly = 0;\n let both = 0;\n\n walkFiles(tree, (file) => {\n const w = writtenFiles.has(file.path);\n const r = reviewedFileSet.has(file.path);\n if (w && r) both++;\n else if (w) writtenOnly++;\n else if (r) reviewedOnly++;\n });\n\n return {\n writtenCount: writtenOnly + both,\n reviewedCount: reviewedOnly + both,\n bothCount: both,\n };\n}\n"],"mappings":"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","names":["readFileSync","join","parentPath","parent","isExpired"]}
|
|
1
|
+
{"version":3,"sources":["../src/git/client.ts","../src/git/identity.ts","../src/filter/ignore.ts","../src/filter/defaults.ts","../src/utils/line-count.ts","../src/core/file-tree.ts","../src/git/log.ts","../src/scoring/binary.ts","../src/git/blame.ts","../src/utils/batch.ts","../src/scoring/authorship.ts","../src/scoring/review-coverage.ts","../src/utils/math.ts","../src/scoring/weighted.ts","../src/git/diff.ts","../src/scoring/expiration.ts","../src/github/client.ts","../src/github/auth.ts","../src/github/reviews.ts","../src/core/familiarity.ts"],"sourcesContent":["import simpleGit, { type SimpleGit } from 'simple-git';\n\nexport class GitClient {\n private git: SimpleGit;\n readonly repoPath: string;\n\n constructor(repoPath: string) {\n this.repoPath = repoPath;\n this.git = simpleGit(repoPath);\n }\n\n async isRepo(): Promise<boolean> {\n return this.git.checkIsRepo();\n }\n\n async getRepoRoot(): Promise<string> {\n return (await this.git.revparse(['--show-toplevel'])).trim();\n }\n\n async getRepoName(): Promise<string> {\n const root = await this.getRepoRoot();\n return root.split('/').pop() || 'unknown';\n }\n\n async listFiles(): Promise<string[]> {\n const result = await this.git.raw(['ls-files']);\n return result.trim().split('\\n').filter(Boolean);\n }\n\n async getUserName(): Promise<string> {\n return (await this.git.raw(['config', 'user.name'])).trim();\n }\n\n async getUserEmail(): Promise<string> {\n return (await this.git.raw(['config', 'user.email'])).trim();\n }\n\n async getLog(args: string[]): Promise<string> {\n return this.git.raw(['log', ...args]);\n }\n\n async blame(filePath: string, options: string[] = []): Promise<string> {\n return this.git.raw(['blame', ...options, '--', filePath]);\n }\n\n async diff(args: string[]): Promise<string> {\n return this.git.raw(['diff', ...args]);\n }\n\n async show(args: string[]): Promise<string> {\n return this.git.raw(['show', ...args]);\n }\n\n async raw(args: string[]): Promise<string> {\n return this.git.raw(args);\n }\n\n async getRemoteUrl(): Promise<string | null> {\n try {\n const result = await this.git.raw(['remote', 'get-url', 'origin']);\n return result.trim();\n } catch {\n return null;\n }\n }\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity } from \"../core/types.js\";\n\nexport async function resolveUser(\n gitClient: GitClient,\n userFlag?: string,\n): Promise<UserIdentity> {\n if (userFlag) {\n // If it looks like an email, use it as email; otherwise as name\n if (userFlag.includes(\"@\")) {\n return { name: userFlag, email: userFlag };\n }\n return { name: userFlag, email: userFlag };\n }\n\n const name = await gitClient.getUserName();\n const email = await gitClient.getUserEmail();\n\n if (!name && !email) {\n throw new Error(\n \"Could not determine git user. Set git config user.name/user.email or use --user flag.\",\n );\n }\n\n return { name, email };\n}\n\n/**\n * Build git --author args for a user identity.\n */\nexport function getAuthorArgs(user: UserIdentity): string[] {\n return [\"--author\", user.email || user.name];\n}\n\nexport function matchesUser(\n authorName: string,\n authorEmail: string,\n user: UserIdentity,\n): boolean {\n // Match by email (primary) or name (fallback)\n if (user.email && authorEmail) {\n if (authorEmail.toLowerCase() === user.email.toLowerCase()) return true;\n }\n if (user.name && authorName) {\n if (authorName.toLowerCase() === user.name.toLowerCase()) return true;\n }\n return false;\n}\n","import ignore from 'ignore';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DEFAULT_IGNORE_PATTERNS } from './defaults.js';\n\nexport type FileFilter = (filePath: string) => boolean;\n\n/**\n * Create a file filter based on .gitfamiliarignore patterns.\n * Returns a function that returns true if the file should be INCLUDED.\n */\nexport function createFilter(repoRoot: string): FileFilter {\n const ig = ignore();\n\n const ignorePath = join(repoRoot, '.gitfamiliarignore');\n\n if (existsSync(ignorePath)) {\n const content = readFileSync(ignorePath, 'utf-8');\n ig.add(content);\n } else {\n ig.add(DEFAULT_IGNORE_PATTERNS);\n }\n\n return (filePath: string) => !ig.ignores(filePath);\n}\n","export const DEFAULT_IGNORE_PATTERNS = `# Lock files\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\nGemfile.lock\npoetry.lock\nCargo.lock\ncomposer.lock\n\n# Auto-generated\n*.generated.*\n*.min.js\n*.min.css\n*.map\n\n# Build outputs (if git-tracked)\ndist/\nbuild/\n.next/\n\n# Config that rarely needs understanding\n.eslintrc*\n.prettierrc*\ntsconfig.json\n`;\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Count lines in a file. Returns 0 for binary or unreadable files.\n */\nexport function countLines(repoRoot: string, filePath: string): number {\n try {\n const fullPath = join(repoRoot, filePath);\n const content = readFileSync(fullPath, 'utf-8');\n if (content.length === 0) return 0;\n return content.split('\\n').length;\n } catch {\n return 0;\n }\n}\n","import type { GitClient } from '../git/client.js';\nimport type { FileFilter } from '../filter/ignore.js';\nimport type { FolderScore, FileScore, TreeNode } from './types.js';\nimport { countLines } from '../utils/line-count.js';\n\n/**\n * Build a hierarchical file tree from git-tracked files.\n */\nexport async function buildFileTree(\n gitClient: GitClient,\n filter: FileFilter,\n): Promise<FolderScore> {\n const repoRoot = await gitClient.getRepoRoot();\n const allFiles = await gitClient.listFiles();\n const filteredFiles = allFiles.filter(filter);\n\n // Build flat file scores\n const fileScores: FileScore[] = filteredFiles.map((filePath) => ({\n type: 'file' as const,\n path: filePath,\n lines: countLines(repoRoot, filePath),\n score: 0,\n }));\n\n // Build tree structure\n return buildTreeFromFiles(fileScores);\n}\n\nfunction buildTreeFromFiles(files: FileScore[]): FolderScore {\n const root: FolderScore = {\n type: 'folder',\n path: '',\n lines: 0,\n score: 0,\n fileCount: 0,\n children: [],\n };\n\n // Group files by directory path\n const folderMap = new Map<string, FolderScore>();\n folderMap.set('', root);\n\n for (const file of files) {\n const parts = file.path.split('/');\n let currentPath = '';\n\n // Ensure all ancestor folders exist\n for (let i = 0; i < parts.length - 1; i++) {\n const parentPath = currentPath;\n currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];\n\n if (!folderMap.has(currentPath)) {\n const folder: FolderScore = {\n type: 'folder',\n path: currentPath,\n lines: 0,\n score: 0,\n fileCount: 0,\n children: [],\n };\n folderMap.set(currentPath, folder);\n\n // Add to parent\n const parent = folderMap.get(parentPath)!;\n parent.children.push(folder);\n }\n }\n\n // Add file to its parent folder\n const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n const parent = folderMap.get(parentPath)!;\n parent.children.push(file);\n }\n\n // Calculate aggregate line counts and file counts\n computeAggregates(root);\n\n return root;\n}\n\nfunction computeAggregates(node: FolderScore): void {\n let totalLines = 0;\n let totalFiles = 0;\n\n for (const child of node.children) {\n if (child.type === 'file') {\n totalLines += child.lines;\n totalFiles += 1;\n } else {\n computeAggregates(child);\n totalLines += child.lines;\n totalFiles += child.fileCount;\n }\n }\n\n node.lines = totalLines;\n node.fileCount = totalFiles;\n}\n\n/**\n * Walk all files in a tree, calling the visitor on each FileScore.\n */\nexport function walkFiles(\n node: TreeNode,\n visitor: (file: FileScore) => void,\n): void {\n if (node.type === 'file') {\n visitor(node);\n } else {\n for (const child of node.children) {\n walkFiles(child, visitor);\n }\n }\n}\n\n/**\n * Recompute folder scores after file scores have been updated.\n * For binary mode: score = readCount / fileCount\n * For other modes: score = weighted average by line count\n */\nexport function recomputeFolderScores(\n node: FolderScore,\n mode: 'binary' | 'continuous',\n): void {\n let readCount = 0;\n let totalFiles = 0;\n let weightedScore = 0;\n let totalLines = 0;\n\n for (const child of node.children) {\n if (child.type === 'file') {\n totalFiles += 1;\n totalLines += child.lines;\n weightedScore += child.score * child.lines;\n if (child.score > 0) readCount += 1;\n } else {\n recomputeFolderScores(child, mode);\n totalFiles += child.fileCount;\n totalLines += child.lines;\n weightedScore += child.score * child.lines;\n readCount += child.readCount || 0;\n }\n }\n\n node.fileCount = totalFiles;\n node.readCount = readCount;\n\n if (mode === 'binary') {\n node.score = totalFiles > 0 ? readCount / totalFiles : 0;\n } else {\n node.score = totalLines > 0 ? weightedScore / totalLines : 0;\n }\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity, CommitInfo } from \"../core/types.js\";\nimport { getAuthorArgs } from \"./identity.js\";\n\n/**\n * Get the set of files that a user has committed to (any commit, any time).\n * Used for Binary mode.\n */\nexport async function getFilesCommittedByUser(\n gitClient: GitClient,\n user: UserIdentity,\n): Promise<Set<string>> {\n const files = new Set<string>();\n\n // Query by both email and name to catch alias mismatches\n const queries: string[][] = [];\n if (user.email) {\n queries.push([\"--author\", user.email]);\n }\n if (user.name && user.name !== user.email) {\n queries.push([\"--author\", user.name]);\n }\n\n for (const authorArgs of queries) {\n try {\n const output = await gitClient.getLog([\n ...authorArgs,\n \"--name-only\",\n \"--pretty=format:\",\n \"--all\",\n ]);\n for (const line of output.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed) {\n files.add(trimmed);\n }\n }\n } catch {\n // Skip if no commits found\n }\n }\n\n return files;\n}\n\n/**\n * Get detailed commit information for a specific file by a specific user.\n * Used for Weighted mode's commit_score.\n */\nexport async function getDetailedCommits(\n gitClient: GitClient,\n user: UserIdentity,\n filePath: string,\n): Promise<CommitInfo[]> {\n const commits: CommitInfo[] = [];\n\n try {\n const output = await gitClient.getLog([\n ...getAuthorArgs(user),\n \"--numstat\",\n \"--pretty=format:%H|%aI\",\n \"--\",\n filePath,\n ]);\n\n const lines = output.trim().split(\"\\n\");\n let currentHash = \"\";\n let currentDate = new Date();\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n if (trimmed.includes(\"|\")) {\n const parts = trimmed.split(\"|\");\n currentHash = parts[0];\n currentDate = new Date(parts[1]);\n } else {\n const statMatch = trimmed.match(/^(\\d+|-)\\t(\\d+|-)\\t(.+)$/);\n if (statMatch && statMatch[3] === filePath) {\n const added = statMatch[1] === \"-\" ? 0 : parseInt(statMatch[1], 10);\n const deleted = statMatch[2] === \"-\" ? 0 : parseInt(statMatch[2], 10);\n\n // Get file size at that commit\n let fileSizeAtCommit = 1;\n try {\n const content = await gitClient.show([\n `${currentHash}:${filePath}`,\n ]);\n fileSizeAtCommit = Math.max(1, content.split(\"\\n\").length);\n } catch {\n fileSizeAtCommit = Math.max(1, added);\n }\n\n commits.push({\n hash: currentHash,\n date: currentDate,\n addedLines: added,\n deletedLines: deleted,\n fileSizeAtCommit,\n });\n }\n }\n }\n } catch {\n // No commits found\n }\n\n return commits;\n}\n\n/**\n * Get the last date a user touched a specific file (commit).\n */\nexport async function getLastCommitDate(\n gitClient: GitClient,\n user: UserIdentity,\n filePath: string,\n): Promise<Date | null> {\n try {\n const output = await gitClient.getLog([\n ...getAuthorArgs(user),\n \"-1\",\n \"--pretty=format:%aI\",\n \"--\",\n filePath,\n ]);\n const trimmed = output.trim();\n if (trimmed) {\n return new Date(trimmed);\n }\n } catch {\n // No commits found\n }\n return null;\n}\n","import type { FolderScore, FilterMode } from \"../core/types.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\n\n/**\n * Score files in binary mode (read / not read).\n */\nexport function scoreBinary(\n tree: FolderScore,\n writtenFiles: Set<string>,\n reviewedFiles: Set<string>,\n filterMode: FilterMode,\n expiredFiles?: Set<string>,\n): void {\n walkFiles(tree, (file) => {\n const isWritten = writtenFiles.has(file.path);\n const isReviewed =\n reviewedFiles.has(file.path) && !writtenFiles.has(file.path);\n const isExpired = expiredFiles?.has(file.path) ?? false;\n\n file.isWritten = isWritten;\n file.isReviewed = isReviewed;\n file.isExpired = isExpired;\n\n if (isExpired) {\n file.score = 0;\n return;\n }\n\n switch (filterMode) {\n case \"written\":\n file.score = isWritten ? 1 : 0;\n break;\n case \"reviewed\":\n file.score = isReviewed ? 1 : 0;\n break;\n case \"all\":\n default:\n file.score = isWritten || isReviewed ? 1 : 0;\n break;\n }\n });\n\n recomputeFolderScores(tree, \"binary\");\n}\n","import type { GitClient } from \"./client.js\";\nimport type { UserIdentity } from \"../core/types.js\";\nimport { matchesUser } from \"./identity.js\";\n\nexport interface BlameEntry {\n email: string;\n name: string;\n lines: number;\n}\n\nexport interface BlameResult {\n entries: BlameEntry[];\n totalLines: number;\n}\n\n/**\n * Run git blame on a file and return per-author line counts.\n * Uses -w to ignore whitespace changes.\n */\nexport async function getBlameData(\n gitClient: GitClient,\n filePath: string,\n): Promise<BlameResult> {\n const authorMap = new Map<string, BlameEntry>();\n let totalLines = 0;\n\n try {\n const output = await gitClient.blame(filePath, [\"-w\", \"--porcelain\"]);\n const lines = output.split(\"\\n\");\n\n let currentName = \"\";\n let currentEmail = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"author \")) {\n currentName = line.slice(\"author \".length).trim();\n } else if (line.startsWith(\"author-mail \")) {\n currentEmail = line\n .slice(\"author-mail \".length)\n .replace(/[<>]/g, \"\")\n .trim();\n } else if (line.startsWith(\"\\t\")) {\n // Content line - count for current author\n if (currentEmail || currentName) {\n totalLines++;\n const key = `${currentEmail}|${currentName}`;\n const existing = authorMap.get(key);\n if (existing) {\n existing.lines++;\n } else {\n authorMap.set(key, {\n email: currentEmail,\n name: currentName,\n lines: 1,\n });\n }\n }\n }\n }\n } catch {\n // Binary file or other error - return empty\n }\n\n return { entries: Array.from(authorMap.values()), totalLines };\n}\n\n/**\n * Get the number of lines authored by a specific user in a file.\n */\nexport async function getUserBlameLines(\n gitClient: GitClient,\n filePath: string,\n user: UserIdentity,\n): Promise<{ userLines: number; totalLines: number }> {\n const { entries, totalLines } = await getBlameData(gitClient, filePath);\n\n let userLines = 0;\n for (const entry of entries) {\n if (matchesUser(entry.name, entry.email, user)) {\n userLines += entry.lines;\n }\n }\n\n return { userLines, totalLines };\n}\n","const DEFAULT_BATCH_SIZE = 10;\n\n/**\n * Process items in batches with concurrent execution within each batch.\n */\nexport async function processBatch<T>(\n items: T[],\n fn: (item: T) => Promise<void>,\n batchSize: number = DEFAULT_BATCH_SIZE,\n): Promise<void> {\n for (let i = 0; i < items.length; i += batchSize) {\n const batch = items.slice(i, i + batchSize);\n await Promise.all(batch.map(fn));\n }\n}\n","import type { FolderScore, UserIdentity } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getUserBlameLines } from \"../git/blame.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\nimport { processBatch } from \"../utils/batch.js\";\n\n/**\n * Score files by authorship (git blame-based).\n * score(file) = blame_lines(user) / total_lines(file)\n */\nexport async function scoreAuthorship(\n tree: FolderScore,\n gitClient: GitClient,\n user: UserIdentity,\n): Promise<void> {\n const files: Array<{ path: string; setScore: (s: number) => void }> = [];\n\n walkFiles(tree, (file) => {\n files.push({\n path: file.path,\n setScore: (s) => {\n file.score = s;\n file.blameScore = s;\n },\n });\n });\n\n await processBatch(files, async ({ path, setScore }) => {\n const { userLines, totalLines } = await getUserBlameLines(\n gitClient,\n path,\n user,\n );\n setScore(totalLines > 0 ? userLines / totalLines : 0);\n });\n\n recomputeFolderScores(tree, \"continuous\");\n}\n","import type { FolderScore, ReviewInfo } from '../core/types.js';\nimport { walkFiles, recomputeFolderScores } from '../core/file-tree.js';\n\n/**\n * Score files by review coverage.\n * Files that the user has reviewed (via PR) get score 1, others 0.\n * User's own commits are excluded.\n */\nexport function scoreReviewCoverage(\n tree: FolderScore,\n reviewedFiles: Set<string>,\n): void {\n walkFiles(tree, (file) => {\n file.isReviewed = reviewedFiles.has(file.path);\n file.score = file.isReviewed ? 1 : 0;\n });\n\n recomputeFolderScores(tree, 'binary');\n}\n","/**\n * Sigmoid function: x / (x + k)\n * Saturates contribution of a single commit to 0-1 range.\n */\nexport function sigmoid(x: number, k: number = 0.3): number {\n if (x <= 0) return 0;\n return x / (x + k);\n}\n\n/**\n * Recency decay: e^(-lambda * t)\n * Models memory decay over time.\n * @param days - days since the event\n * @param halfLife - number of days for score to halve (default 180)\n */\nexport function recencyDecay(days: number, halfLife: number = 180): number {\n if (days <= 0) return 1;\n const lambda = Math.LN2 / halfLife;\n return Math.exp(-lambda * days);\n}\n\n/**\n * Scope factor: min(1, attentionThreshold / filesInPR)\n * Models attention dilution in large PRs.\n */\nexport function scopeFactor(\n filesInPR: number,\n attentionThreshold: number = 20,\n): number {\n if (filesInPR <= 0) return 1;\n return Math.min(1, attentionThreshold / filesInPR);\n}\n\n/**\n * Normalized diff: (added + 0.5 * deleted) / fileSize\n */\nexport function normalizedDiff(\n added: number,\n deleted: number,\n fileSize: number,\n): number {\n if (fileSize <= 0) return 0;\n return (added + 0.5 * deleted) / fileSize;\n}\n\n/**\n * Calculate the number of days between two dates.\n */\nexport function daysBetween(a: Date, b: Date): number {\n const ms = Math.abs(b.getTime() - a.getTime());\n return ms / (1000 * 60 * 60 * 24);\n}\n","import type {\n FolderScore,\n UserIdentity,\n WeightConfig,\n ReviewInfo,\n CommitInfo,\n} from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getUserBlameLines } from \"../git/blame.js\";\nimport { getDetailedCommits } from \"../git/log.js\";\nimport { walkFiles, recomputeFolderScores } from \"../core/file-tree.js\";\nimport { processBatch } from \"../utils/batch.js\";\nimport {\n sigmoid,\n recencyDecay,\n scopeFactor,\n normalizedDiff,\n daysBetween,\n} from \"../utils/math.js\";\n\nconst REVIEW_BASE_WEIGHTS: Record<string, number> = {\n approved: 0.3,\n commented: 0.15,\n changes_requested: 0.35,\n};\n\nfunction calculateCommitScore(commits: CommitInfo[], now: Date): number {\n let raw = 0;\n for (const c of commits) {\n const nd = normalizedDiff(c.addedLines, c.deletedLines, c.fileSizeAtCommit);\n raw += sigmoid(nd) * recencyDecay(daysBetween(now, c.date));\n }\n return Math.min(1, raw);\n}\n\nfunction calculateReviewScore(\n reviews: ReviewInfo[] | undefined,\n now: Date,\n): number {\n if (!reviews) return 0;\n let raw = 0;\n for (const r of reviews) {\n const baseWeight = REVIEW_BASE_WEIGHTS[r.type] || 0.15;\n raw +=\n baseWeight *\n scopeFactor(r.filesInPR) *\n recencyDecay(daysBetween(now, r.date));\n }\n return Math.min(1, raw);\n}\n\n/**\n * Score files using the weighted mode (blame + commit + review signals).\n */\nexport async function scoreWeighted(\n tree: FolderScore,\n gitClient: GitClient,\n user: UserIdentity,\n weights: WeightConfig,\n reviewData?: Map<string, ReviewInfo[]>,\n now?: Date,\n): Promise<void> {\n const currentDate = now || new Date();\n const files: Array<{\n path: string;\n setScores: (b: number, c: number, r: number, total: number) => void;\n }> = [];\n\n walkFiles(tree, (file) => {\n files.push({\n path: file.path,\n setScores: (b, c, r, total) => {\n file.blameScore = b;\n file.commitScore = c;\n file.reviewScore = r;\n file.score = total;\n },\n });\n });\n\n await processBatch(files, async ({ path, setScores }) => {\n const { userLines, totalLines } = await getUserBlameLines(\n gitClient,\n path,\n user,\n );\n const blameScore = totalLines > 0 ? userLines / totalLines : 0;\n const commitScore = calculateCommitScore(\n await getDetailedCommits(gitClient, user, path),\n currentDate,\n );\n const reviewScore = calculateReviewScore(\n reviewData?.get(path),\n currentDate,\n );\n\n const total =\n weights.blame * blameScore +\n weights.commit * commitScore +\n weights.review * reviewScore;\n\n setScores(blameScore, commitScore, reviewScore, total);\n });\n\n recomputeFolderScores(tree, \"continuous\");\n}\n","import type { GitClient } from './client.js';\n\n/**\n * Calculate how much a file has changed since a given commit.\n * Returns the ratio of changed lines to current total lines.\n * Used for change-based expiration policy.\n */\nexport async function getChangeRatio(\n gitClient: GitClient,\n filePath: string,\n sinceCommit: string,\n): Promise<number> {\n try {\n const output = await gitClient.diff([\n '--numstat',\n `${sinceCommit}..HEAD`,\n '--',\n filePath,\n ]);\n\n const trimmed = output.trim();\n if (!trimmed) return 0;\n\n const match = trimmed.match(/^(\\d+|-)\\t(\\d+|-)\\t/);\n if (!match) return 0;\n\n const added = match[1] === '-' ? 0 : parseInt(match[1], 10);\n const deleted = match[2] === '-' ? 0 : parseInt(match[2], 10);\n const changedLines = added + deleted;\n\n // Get current file line count\n const currentContent = await gitClient.show([`HEAD:${filePath}`]);\n const currentLines = Math.max(1, currentContent.split('\\n').length);\n\n return changedLines / currentLines;\n } catch {\n return 0;\n }\n}\n\n/**\n * Get the commit hash for the last time a user touched a file.\n */\nexport async function getLastTouchCommit(\n gitClient: GitClient,\n filePath: string,\n userEmail: string,\n): Promise<string | null> {\n try {\n const output = await gitClient.getLog([\n '--author', userEmail,\n '-1',\n '--pretty=format:%H',\n '--',\n filePath,\n ]);\n const hash = output.trim();\n return hash || null;\n } catch {\n return null;\n }\n}\n","import type { ExpirationConfig, UserIdentity } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { getLastCommitDate } from \"../git/log.js\";\nimport { getLastTouchCommit, getChangeRatio } from \"../git/diff.js\";\nimport { processBatch } from \"../utils/batch.js\";\n\n/**\n * Parse an expiration string from CLI into ExpirationConfig.\n * Formats: \"never\", \"time:180d\", \"change:50%\", \"combined:365d:50%\"\n */\nexport function parseExpirationConfig(input: string): ExpirationConfig {\n if (!input || input === \"never\") {\n return { policy: \"never\" };\n }\n\n if (input.startsWith(\"time:\")) {\n const duration = parseDays(input.slice(\"time:\".length));\n return { policy: \"time\", duration };\n }\n\n if (input.startsWith(\"change:\")) {\n const threshold = parsePercentage(input.slice(\"change:\".length));\n return { policy: \"change\", threshold };\n }\n\n if (input.startsWith(\"combined:\")) {\n const parts = input.slice(\"combined:\".length).split(\":\");\n const duration = parseDays(parts[0]);\n const threshold = parts[1] ? parsePercentage(parts[1]) : 0.5;\n return { policy: \"combined\", duration, threshold };\n }\n\n return { policy: \"never\" };\n}\n\nfunction parseDays(s: string): number {\n const match = s.match(/^(\\d+)d$/);\n if (!match)\n throw new Error(\n `Invalid duration format: \"${s}\". Expected format like \"180d\".`,\n );\n return parseInt(match[1], 10);\n}\n\nfunction parsePercentage(s: string): number {\n const match = s.match(/^(\\d+)%$/);\n if (!match)\n throw new Error(\n `Invalid percentage format: \"${s}\". Expected format like \"50%\".`,\n );\n return parseInt(match[1], 10) / 100;\n}\n\n/**\n * Check if a file's familiarity has expired according to the given policy.\n */\nexport async function isExpired(\n gitClient: GitClient,\n filePath: string,\n user: UserIdentity,\n config: ExpirationConfig,\n now?: Date,\n): Promise<boolean> {\n if (config.policy === \"never\") return false;\n\n const currentDate = now || new Date();\n const email = user.email || user.name;\n\n if (config.policy === \"time\" || config.policy === \"combined\") {\n const lastTouch = await getLastCommitDate(gitClient, user, filePath);\n if (lastTouch && config.duration) {\n const daysSince =\n (currentDate.getTime() - lastTouch.getTime()) / (1000 * 60 * 60 * 24);\n if (daysSince > config.duration) return true;\n }\n }\n\n if (config.policy === \"change\" || config.policy === \"combined\") {\n const lastCommit = await getLastTouchCommit(gitClient, filePath, email);\n if (lastCommit && config.threshold) {\n const ratio = await getChangeRatio(gitClient, filePath, lastCommit);\n if (ratio > config.threshold) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Get the set of expired files for a given user and config.\n */\nexport async function getExpiredFiles(\n gitClient: GitClient,\n files: string[],\n user: UserIdentity,\n config: ExpirationConfig,\n): Promise<Set<string>> {\n if (config.policy === \"never\") return new Set();\n\n const expiredSet = new Set<string>();\n\n await processBatch(files, async (filePath) => {\n if (await isExpired(gitClient, filePath, user, config)) {\n expiredSet.add(filePath);\n }\n });\n\n return expiredSet;\n}\n","import type { ReviewInfo } from \"../core/types.js\";\n\ninterface GitHubReview {\n state: string;\n submitted_at: string;\n}\n\nexport interface GitHubRemoteInfo {\n hostname: string; // e.g. \"github.com\" or \"ghe.example.com\"\n owner: string;\n repo: string;\n apiBaseUrl: string; // e.g. \"https://api.github.com\" or \"https://ghe.example.com/api/v3\"\n}\n\n/**\n * Minimal GitHub client using fetch (no external dependency).\n * Supports both github.com and GitHub Enterprise.\n */\nexport class GitHubClient {\n private token: string;\n private baseUrl: string;\n\n constructor(token: string, apiBaseUrl: string = \"https://api.github.com\") {\n this.token = token;\n this.baseUrl = apiBaseUrl.replace(/\\/+$/, \"\");\n }\n\n private async fetch(path: string): Promise<any> {\n const url = `${this.baseUrl}${path}`;\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n Accept: \"application/vnd.github.v3+json\",\n \"User-Agent\": \"gitfamiliar\",\n },\n });\n\n if (!response.ok) {\n if (response.status === 403) {\n throw new Error(\n \"GitHub API rate limit exceeded. Please wait or use a token with higher limits.\",\n );\n }\n throw new Error(\n `GitHub API error: ${response.status} ${response.statusText}`,\n );\n }\n\n return response.json();\n }\n\n /**\n * Verify API connectivity by fetching the authenticated user.\n */\n async verifyConnection(): Promise<{ login: string; name: string | null }> {\n const user = await this.fetch(\"/user\");\n return { login: user.login, name: user.name };\n }\n\n /**\n * Parse owner/repo/hostname from a git remote URL.\n * Supports github.com and GitHub Enterprise hosts.\n */\n static parseRemoteUrl(\n url: string,\n overrideHostname?: string,\n ): GitHubRemoteInfo | null {\n let hostname: string;\n let owner: string;\n let repo: string;\n\n // SSH format: git@hostname:owner/repo.git\n let match = url.match(/git@([^:]+):([^/]+)\\/([^/.]+)(\\.git)?$/);\n if (match) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else if (\n // SSH URL format: ssh://git@hostname(:port)?/owner/repo.git\n (match = url.match(\n /ssh:\\/\\/[^@]+@([^:/]+)(?::\\d+)?\\/([^/]+)\\/([^/.]+?)(\\.git)?$/,\n ))\n ) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else if (\n // HTTPS format: https://(user:token@)?hostname(:port)?/owner/repo.git\n (match = url.match(\n /https?:\\/\\/(?:[^@]+@)?([^/:]+)(?::\\d+)?\\/([^/]+)\\/([^/.]+?)(\\.git)?$/,\n ))\n ) {\n hostname = match[1];\n owner = match[2];\n repo = match[3];\n } else {\n return null;\n }\n\n if (overrideHostname) {\n hostname = overrideHostname;\n }\n\n const apiBaseUrl =\n hostname === \"github.com\"\n ? \"https://api.github.com\"\n : `https://${hostname}/api/v3`;\n\n return { hostname, owner, repo, apiBaseUrl };\n }\n\n /**\n * Get all files reviewed by a user across all PRs they reviewed.\n */\n async getReviewedFiles(\n owner: string,\n repo: string,\n username: string,\n ): Promise<Map<string, ReviewInfo[]>> {\n const reviewedFiles = new Map<string, ReviewInfo[]>();\n\n let page = 1;\n const perPage = 100;\n\n while (true) {\n const searchResult = await this.fetch(\n `/search/issues?q=type:pr+repo:${owner}/${repo}+reviewed-by:${username}&per_page=${perPage}&page=${page}`,\n );\n\n if (!searchResult.items || searchResult.items.length === 0) break;\n\n for (const item of searchResult.items) {\n const prNumber = item.number;\n\n const reviews: GitHubReview[] = await this.fetch(\n `/repos/${owner}/${repo}/pulls/${prNumber}/reviews`,\n );\n\n const userReviews = reviews.filter(\n (r: any) => r.user?.login?.toLowerCase() === username.toLowerCase(),\n );\n\n if (userReviews.length === 0) continue;\n\n const prFiles: any[] = await this.fetch(\n `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100`,\n );\n\n const fileCount = prFiles.length;\n\n for (const review of userReviews) {\n const reviewType = mapReviewState(review.state);\n const reviewDate = new Date(review.submitted_at);\n\n for (const prFile of prFiles) {\n const filePath = prFile.filename;\n const info: ReviewInfo = {\n date: reviewDate,\n type: reviewType,\n filesInPR: fileCount,\n };\n\n if (reviewedFiles.has(filePath)) {\n reviewedFiles.get(filePath)!.push(info);\n } else {\n reviewedFiles.set(filePath, [info]);\n }\n }\n }\n }\n\n if (searchResult.items.length < perPage) break;\n page++;\n }\n\n return reviewedFiles;\n }\n}\n\nfunction mapReviewState(state: string): ReviewInfo[\"type\"] {\n switch (state.toUpperCase()) {\n case \"APPROVED\":\n return \"approved\";\n case \"CHANGES_REQUESTED\":\n return \"changes_requested\";\n default:\n return \"commented\";\n }\n}\n","import { execSync } from \"node:child_process\";\n\n/**\n * Resolve GitHub token from environment or gh CLI.\n * For GitHub Enterprise, pass the hostname (e.g. \"ghe.example.com\")\n * to use `gh auth token --hostname <host>`.\n */\nexport function resolveGitHubToken(hostname?: string): string | null {\n // Check environment variables\n if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;\n if (process.env.GH_TOKEN) return process.env.GH_TOKEN;\n\n // Try gh CLI (always pass --hostname for explicit host resolution)\n try {\n const host = hostname || \"github.com\";\n const token = execSync(`gh auth token --hostname ${host}`, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (token) return token;\n } catch {\n // gh CLI not available or not authenticated for this hostname\n }\n\n // Fallback: try gh auth token without --hostname (uses default host)\n if (hostname && hostname !== \"github.com\") {\n try {\n const token = execSync(\"gh auth token\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (token) return token;\n } catch {\n // gh CLI not available or not authenticated\n }\n }\n\n return null;\n}\n","import type { ReviewInfo } from \"../core/types.js\";\nimport type { GitClient } from \"../git/client.js\";\nimport { GitHubClient } from \"./client.js\";\nimport { resolveGitHubToken } from \"./auth.js\";\n\n/**\n * Attempt to fetch review data from GitHub.\n * Returns null if no token or not a GitHub repo.\n * Supports GitHub Enterprise by auto-detecting the hostname from git remote.\n * @param githubUrl - Optional override for GitHub hostname (e.g. \"ghe.example.com\")\n */\nexport async function fetchReviewData(\n gitClient: GitClient,\n username?: string,\n githubUrl?: string,\n): Promise<{\n reviewedFiles: Map<string, ReviewInfo[]>;\n reviewedFileSet: Set<string>;\n} | null> {\n const remoteUrl = await gitClient.getRemoteUrl();\n if (!remoteUrl) return null;\n\n const parsed = GitHubClient.parseRemoteUrl(remoteUrl, githubUrl);\n if (!parsed) return null;\n\n const token = resolveGitHubToken(parsed.hostname);\n if (!token) return null;\n\n // GitHub username is required for review API queries\n if (!username) return null;\n const ghUsername = username;\n\n try {\n const githubClient = new GitHubClient(token, parsed.apiBaseUrl);\n const reviewedFiles = await githubClient.getReviewedFiles(\n parsed.owner,\n parsed.repo,\n ghUsername,\n );\n\n const reviewedFileSet = new Set(reviewedFiles.keys());\n\n return { reviewedFiles, reviewedFileSet };\n } catch {\n return null;\n }\n}\n","import type { CliOptions, FolderScore, ReviewInfo } from \"./types.js\";\nimport { GitClient } from \"../git/client.js\";\nimport { resolveUser } from \"../git/identity.js\";\nimport { createFilter } from \"../filter/ignore.js\";\nimport { buildFileTree, walkFiles } from \"./file-tree.js\";\nimport { getFilesCommittedByUser } from \"../git/log.js\";\nimport { scoreBinary } from \"../scoring/binary.js\";\nimport { scoreAuthorship } from \"../scoring/authorship.js\";\nimport { scoreReviewCoverage } from \"../scoring/review-coverage.js\";\nimport { scoreWeighted } from \"../scoring/weighted.js\";\nimport { getExpiredFiles } from \"../scoring/expiration.js\";\nimport { fetchReviewData } from \"../github/reviews.js\";\n\nexport interface FamiliarityResult {\n tree: FolderScore;\n repoName: string;\n userName: string;\n mode: string;\n writtenCount: number;\n reviewedCount: number;\n bothCount: number;\n totalFiles: number;\n}\n\nexport async function computeFamiliarity(\n options: CliOptions,\n): Promise<FamiliarityResult> {\n const gitClient = new GitClient(options.repoPath);\n\n if (!(await gitClient.isRepo())) {\n throw new Error(`\"${options.repoPath}\" is not a git repository.`);\n }\n\n const repoRoot = await gitClient.getRepoRoot();\n const repoName = await gitClient.getRepoName();\n const userFlag = Array.isArray(options.user) ? options.user[0] : options.user;\n const user = await resolveUser(gitClient, userFlag);\n const filter = createFilter(repoRoot);\n const tree = await buildFileTree(gitClient, filter);\n\n // Get written files (used by binary, weighted)\n const writtenFiles = await getFilesCommittedByUser(gitClient, user);\n\n // Get review data (if available)\n let reviewData: Map<string, ReviewInfo[]> | undefined;\n let reviewedFileSet = new Set<string>();\n\n if (options.mode !== \"authorship\") {\n const reviewResult = await fetchReviewData(\n gitClient,\n userFlag,\n options.githubUrl,\n );\n if (reviewResult) {\n reviewData = reviewResult.reviewedFiles;\n reviewedFileSet = reviewResult.reviewedFileSet;\n }\n }\n\n // Get expired files\n let expiredFiles: Set<string> | undefined;\n if (options.expiration.policy !== \"never\") {\n const allFiles: string[] = [];\n walkFiles(tree, (f) => allFiles.push(f.path));\n expiredFiles = await getExpiredFiles(\n gitClient,\n allFiles,\n user,\n options.expiration,\n );\n }\n\n // Score based on mode\n switch (options.mode) {\n case \"binary\":\n scoreBinary(\n tree,\n writtenFiles,\n reviewedFileSet,\n options.filter,\n expiredFiles,\n );\n break;\n\n case \"authorship\":\n await scoreAuthorship(tree, gitClient, user);\n break;\n\n case \"review-coverage\":\n if (reviewedFileSet.size === 0) {\n console.error(\n \"Warning: No review data available. Set GITHUB_TOKEN or use --user with your GitHub username.\",\n );\n }\n scoreReviewCoverage(tree, reviewedFileSet);\n break;\n\n case \"weighted\":\n await scoreWeighted(tree, gitClient, user, options.weights, reviewData);\n break;\n }\n\n return {\n tree,\n repoName,\n userName: user.name || user.email,\n mode: options.mode,\n ...computeSummary(tree, writtenFiles, reviewedFileSet),\n totalFiles: tree.fileCount,\n };\n}\n\nfunction computeSummary(\n tree: FolderScore,\n writtenFiles: Set<string>,\n reviewedFileSet: Set<string>,\n): { writtenCount: number; reviewedCount: number; bothCount: number } {\n let writtenOnly = 0;\n let reviewedOnly = 0;\n let both = 0;\n\n walkFiles(tree, (file) => {\n const w = writtenFiles.has(file.path);\n const r = reviewedFileSet.has(file.path);\n if (w && r) both++;\n else if (w) writtenOnly++;\n else if (r) reviewedOnly++;\n });\n\n return {\n writtenCount: writtenOnly + both,\n reviewedCount: reviewedOnly + both,\n bothCount: both,\n };\n}\n"],"mappings":"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","names":["readFileSync","join","parentPath","parent","isExpired"]}
|
package/dist/index.js
CHANGED