AI 驱动的代码仓库健康度评估提交频率、代码异味与文档覆盖率的综合分析代码仓库的健康状况无法通过单一指标来评判。提交频率反映团队活跃度代码异味暗示技术债务的累积速度文档覆盖率衡量知识的可传递性——这三个维度构成了一个立体的评估框架。利用 AI 对这些维度进行自动化分析可以让健康度评估从主观感受变为数据驱动。一、评估指标体系的设计一个多维度的仓库健康度模型需要覆盖以下维度每个指标由采集层自动收集再通过加权公式计算总分健康度得分 Σ (维度得分 × 维度权重) 其中维度得分 Σ (指标得分 × 指标权重) / 指标总数二、数据采集层的实现2.1 Git 提交历史的分析/** * Git 提交历史分析器 * 通过 Git 命令行采集提交频率、贡献者分布等数据 */ import { execSync } from child_process; interface CommitMetrics { /** 提交总数 */ totalCommits: number; /** 分析周期内的提交数 */ periodCommits: number; /** 日均提交数 */ dailyAverage: number; /** 活跃贡献者列表 */ contributors: Array{ name: string; email: string; commits: number; percentage: number; }; /** 提交时间分布按小时 */ hourlyDistribution: number[]; /** 最近提交距今天数 */ daysSinceLastCommit: number; } class GitAnalyzer { private repoPath: string; constructor(repoPath: string) { this.repoPath repoPath; } /** * 分析指定时间范围内的提交指标 */ analyzeCommits(since: string 30 days ago): CommitMetrics { try { // 获取指定范围内的日志 const logOutput this.execGit( log --since${since} --format%H|%an|%ae|%aI --no-merges ); const commits logOutput .split(\n) .filter(Boolean) .map(line { const [hash, author, email, date] line.split(|); return { hash, author, email, date: new Date(date) }; }); // 统计贡献者分布 const contributorMap new Mapstring, { name: string; email: string; commits: number }(); const hourlyDist new Array(24).fill(0); commits.forEach(c { const key c.email; if (!contributorMap.has(key)) { contributorMap.set(key, { name: c.author, email: c.email, commits: 0 }); } contributorMap.get(key)!.commits; hourlyDist[c.date.getHours()]; }); const contributors Array.from(contributorMap.values()) .map(c ({ ...c, percentage: (c.commits / commits.length) * 100 })) .sort((a, b) b.commits - a.commits); return { totalCommits: commits.length, periodCommits: commits.length, dailyAverage: commits.length / 30, contributors, hourlyDistribution: hourlyDist, daysSinceLastCommit: this.getDaysSinceLastCommit(commits), }; } catch (error) { console.error([GitAnalyzer] 提交分析失败, error); return this.getEmptyMetrics(); } } /** * 获取每个文件的修改频率热力图数据 */ getFileChangeHeatmap(since: string 90 days ago): Mapstring, number { const output this.execGit( log --since${since} --name-only --format --no-merges ); const fileMap new Mapstring, number(); output.split(\n) .filter(Boolean) .forEach(file { fileMap.set(file, (fileMap.get(file) || 0) 1); }); return fileMap; } private execGit(args: string): string { try { return execSync(git -C ${this.repoPath} ${args}, { encoding: utf-8, maxBuffer: 10 * 1024 * 1024, // 10MB }).trim(); } catch (error) { throw new Error(Git 命令执行失败: ${args}\n${(error as Error).message}); } } private getDaysSinceLastCommit(commits: Array{ date: Date }): number { if (commits.length 0) return 999; const lastDate commits[0].date; return Math.floor((Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24)); } private getEmptyMetrics(): CommitMetrics { return { totalCommits: 0, periodCommits: 0, dailyAverage: 0, contributors: [], hourlyDistribution: new Array(24).fill(0), daysSinceLastCommit: 999, }; } }三、AI 驱动的代码异味检测传统代码异味检测依赖静态规则如 ESLint但对于长函数、深层嵌套、数据泥团等结构性异味规则引擎往往力不从心。AI 模型可以理解代码的语义结构识别更复杂的模式。3.1 检测流程3.2 核心实现/** * AI 代码异味检测器 */ interface CodeSmell { file: string; line: number; type: long_method | deep_nesting | large_class | data_clump | primitive_obsession; severity: high | medium | low; description: string; suggestion: string; } interface SmellDetectionRequest { code: string; filePath: string; language: string; } class AISmellDetector { private llmEndpoint: string; private maxCodeLength: number; constructor(llmEndpoint: string /api/ai/smell-detect, maxCodeLength 8000) { this.llmEndpoint llmEndpoint; this.maxCodeLength maxCodeLength; } /** * 批量检测指定文件的代码异味 */ async detectSmells(files: Array{ path: string; content: string; language: string }): PromiseCodeSmell[] { const results: CodeSmell[] []; // 并发检测控制并发数避免打爆 API const CONCURRENCY 3; for (let i 0; i files.length; i CONCURRENCY) { const batch files.slice(i, i CONCURRENCY); const batchResults await Promise.allSettled( batch.map(file this.detectSingleFile(file)) ); batchResults.forEach((result, index) { if (result.status fulfilled) { results.push(...result.value); } else { console.error([AISmellDetector] 文件检测失败: ${batch[index].path}, result.reason); } }); } return results; } /** * 检测单个文件的代码异味 */ private async detectSingleFile( file: { path: string; content: string; language: string } ): PromiseCodeSmell[] { // 内容过长的文件截取关键部分 const truncated file.content.length this.maxCodeLength ? file.content.slice(0, this.maxCodeLength) \n// [内容已截断] : file.content; const prompt this.buildDetectionPrompt(truncated, file.language); const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 30000); try { const response await fetch(this.llmEndpoint, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages: [{ role: user, content: prompt }], temperature: 0.1, // 低温度保证结果一致性 response_format: { type: json_object }, }), signal: controller.signal, }); if (!response.ok) { throw new Error(AI 服务异常: ${response.status}); } const data await response.json(); return this.parseSmellResults(data, file.path); } finally { clearTimeout(timeoutId); } } /** * 构建检测提示词 */ private buildDetectionPrompt(code: string, language: string): string { return JSON.stringify({ task: detect_code_smells, language, code, smell_types: [ long_method: 方法超过合理长度, deep_nesting: 嵌套层级过深3层, large_class: 类职责过多, data_clump: 总是成组出现但未封装的数据, primitive_obsession: 用基础类型代替对象, duplicated_logic: 可明显抽取的重复逻辑, ], output_format: { smells: [{ type: , severity: , line: 0, description: , suggestion: }], }, requirements: [ 只报告确实存在的问题不要过度检测, severity 按实际影响判定high影响可维护性medium建议改进low风格偏好, ], }); } /** * 解析 LLM 返回的异味检测结果 */ private parseSmellResults(data: Recordstring, unknown, filePath: string): CodeSmell[] { try { if (!data.smells || !Array.isArray(data.smells)) return []; return data.smells .filter((s: Recordstring, unknown) [long_method, deep_nesting, large_class, data_clump, primitive_obsession] .includes(s.type as string) ) .map((s: Recordstring, unknown) ({ file: filePath, line: Number(s.line) || 0, type: s.type as CodeSmell[type], severity: (s.severity as CodeSmell[severity]) || medium, description: String(s.description || ), suggestion: String(s.suggestion || ), })); } catch (error) { console.error([AISmellDetector] 结果解析失败, error); return []; } } }四、文档覆盖率评估4.1 评估维度文档覆盖率不是简单的README 是否存在而是一个多层次的检查/** * 文档覆盖率评估器 */ interface DocumentationReport { /** 总体评分 (0-100) */ overallScore: number; /** README 完整度 */ readme: { exists: boolean; hasInstallGuide: boolean; hasUsageExample: boolean; hasAPIDocs: boolean; hasContributionGuide: boolean; score: number; }; /** API 文档覆盖率 */ apiCoverage: { /** 有文档注释的导出函数比例 */ documentedExports: number; totalExports: number; coverage: number; }; /** 变更日志 */ changelog: { exists: boolean; followsSemanticVersion: boolean; lastUpdate: string | null; }; } class DocumentationAnalyzer { /** * 分析仓库文档覆盖情况 */ async analyze(repoPath: string): PromiseDocumentationReport { const fs await import(fs/promises); const path await import(path); // 检测 README const readmeResult await this.analyzeReadme(repoPath, fs, path); // 检测 API 文档覆盖率 const apiResult await this.analyzeAPICoverage(repoPath, fs, path); // 检测变更日志 const changelogResult await this.analyzeChangelog(repoPath, fs, path); // 计算总分 const overallScore Math.round( readmeResult.score * 0.4 apiResult.coverage * 0.4 (changelogResult.exists ? 20 : 0) ); return { overallScore, readme: readmeResult, apiCoverage: apiResult, changelog: changelogResult, }; } private async analyzeReadme( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[readme] { const files await fs.readdir(repoPath); const readmeFile files.find( f f.toLowerCase() readme.md || f.toLowerCase() readme ); if (!readmeFile) { return { exists: false, hasInstallGuide: false, hasUsageExample: false, hasAPIDocs: false, hasContributionGuide: false, score: 0, }; } const content await fs.readFile(path.join(repoPath, readmeFile), utf-8); const lowerContent content.toLowerCase(); const checks { hasInstallGuide: /install|安装|setup|quick.?start/i.test(lowerContent), hasUsageExample: /usage|用法|example|示例|/.test(lowerContent), hasAPIDocs: /\bapi\b|接口|endpoint/i.test(lowerContent), hasContributionGuide: /contribut|贡献|pull.?request|pr/i.test(lowerContent), }; const score Object.values(checks).filter(Boolean).length * 25; return { exists: true, ...checks, score, }; } private async analyzeAPICoverage( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[apiCoverage] { const srcPath path.join(repoPath, src); let totalExports 0; let documentedExports 0; try { const files await this.getSourceFiles(srcPath, fs, path); for (const file of files) { const content await fs.readFile(file, utf-8); const exports content.match(/export\s(const|function|class|interface|type)\s\w/g) || []; totalExports exports.length; // 检查 export 前的 JSDoc 注释 for (const exp of exports) { // 简化检查查看导出声明前的几行是否有 JSDoc 风格注释 const expIndex content.indexOf(exp); const preceding content.slice(Math.max(0, expIndex - 200), expIndex); if (/\/\*\*[\s\S]*?\*\//.test(preceding)) { documentedExports; } } } } catch { // src 目录不存在返回空结果 } return { documentedExports, totalExports, coverage: totalExports 0 ? (documentedExports / totalExports) * 100 : 0, }; } private async getSourceFiles( dir: string, fs: typeof import(fs/promises), path: typeof import(path) ): Promisestring[] { const results: string[] []; try { const entries await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath path.join(dir, entry.name); if (entry.isDirectory() entry.name ! node_modules) { results.push(...await this.getSourceFiles(fullPath, fs, path)); } else if (/\.(ts|js|tsx|jsx)$/.test(entry.name)) { results.push(fullPath); } } } catch { // 目录不可读跳过 } return results; } private async analyzeChangelog( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[changelog] { const changelogPath path.join(repoPath, CHANGELOG.md); try { const content await fs.readFile(changelogPath, utf-8); return { exists: true, followsSemanticVersion: /\d\.\d\.\d/.test(content), lastUpdate: this.extractLastChangelogDate(content), }; } catch { return { exists: false, followsSemanticVersion: false, lastUpdate: null }; } } private extractLastChangelogDate(content: string): string | null { const match content.match(/\d{4}-\d{2}-\d{2}/); return match ? match[0] : null; } }五、综合分析报告生成将各维度数据汇总为结构化的健康度报告综合评分以 0-100 分量化仓库健康度80 分以上为健康60-80 分需要关注60 分以下存在较高风险。每个月执行一次全量评估将报告归档至项目文档中形成可追溯的健康趋势曲线。总结AI 驱动的代码仓库健康度评估方案将传统的人工 code review 感受升级为数据驱动的量化分析提交分析通过 Git 命令行提取提交频率、贡献者分布和时间规律判断团队活跃度。AI 异味检测结合 AST 解析和 LLM 语义理解识别长方法、深层嵌套等结构性异味。文档覆盖率自动检查 README 完整性、API 文档比例和变更日志规范性。综合评分加权汇总各维度得分输出可对比的健康度趋势报告。该方案适合作为 CI 流水线的一个可选步骤或作为月度仓库健康回顾的自动化工具。建议将检测结果与告警系统打通当健康度得分持续下降或出现高危异味时自动通知团队。
AI 驱动的代码仓库健康度评估:提交频率、代码异味与文档覆盖率的综合分析
AI 驱动的代码仓库健康度评估提交频率、代码异味与文档覆盖率的综合分析代码仓库的健康状况无法通过单一指标来评判。提交频率反映团队活跃度代码异味暗示技术债务的累积速度文档覆盖率衡量知识的可传递性——这三个维度构成了一个立体的评估框架。利用 AI 对这些维度进行自动化分析可以让健康度评估从主观感受变为数据驱动。一、评估指标体系的设计一个多维度的仓库健康度模型需要覆盖以下维度每个指标由采集层自动收集再通过加权公式计算总分健康度得分 Σ (维度得分 × 维度权重) 其中维度得分 Σ (指标得分 × 指标权重) / 指标总数二、数据采集层的实现2.1 Git 提交历史的分析/** * Git 提交历史分析器 * 通过 Git 命令行采集提交频率、贡献者分布等数据 */ import { execSync } from child_process; interface CommitMetrics { /** 提交总数 */ totalCommits: number; /** 分析周期内的提交数 */ periodCommits: number; /** 日均提交数 */ dailyAverage: number; /** 活跃贡献者列表 */ contributors: Array{ name: string; email: string; commits: number; percentage: number; }; /** 提交时间分布按小时 */ hourlyDistribution: number[]; /** 最近提交距今天数 */ daysSinceLastCommit: number; } class GitAnalyzer { private repoPath: string; constructor(repoPath: string) { this.repoPath repoPath; } /** * 分析指定时间范围内的提交指标 */ analyzeCommits(since: string 30 days ago): CommitMetrics { try { // 获取指定范围内的日志 const logOutput this.execGit( log --since${since} --format%H|%an|%ae|%aI --no-merges ); const commits logOutput .split(\n) .filter(Boolean) .map(line { const [hash, author, email, date] line.split(|); return { hash, author, email, date: new Date(date) }; }); // 统计贡献者分布 const contributorMap new Mapstring, { name: string; email: string; commits: number }(); const hourlyDist new Array(24).fill(0); commits.forEach(c { const key c.email; if (!contributorMap.has(key)) { contributorMap.set(key, { name: c.author, email: c.email, commits: 0 }); } contributorMap.get(key)!.commits; hourlyDist[c.date.getHours()]; }); const contributors Array.from(contributorMap.values()) .map(c ({ ...c, percentage: (c.commits / commits.length) * 100 })) .sort((a, b) b.commits - a.commits); return { totalCommits: commits.length, periodCommits: commits.length, dailyAverage: commits.length / 30, contributors, hourlyDistribution: hourlyDist, daysSinceLastCommit: this.getDaysSinceLastCommit(commits), }; } catch (error) { console.error([GitAnalyzer] 提交分析失败, error); return this.getEmptyMetrics(); } } /** * 获取每个文件的修改频率热力图数据 */ getFileChangeHeatmap(since: string 90 days ago): Mapstring, number { const output this.execGit( log --since${since} --name-only --format --no-merges ); const fileMap new Mapstring, number(); output.split(\n) .filter(Boolean) .forEach(file { fileMap.set(file, (fileMap.get(file) || 0) 1); }); return fileMap; } private execGit(args: string): string { try { return execSync(git -C ${this.repoPath} ${args}, { encoding: utf-8, maxBuffer: 10 * 1024 * 1024, // 10MB }).trim(); } catch (error) { throw new Error(Git 命令执行失败: ${args}\n${(error as Error).message}); } } private getDaysSinceLastCommit(commits: Array{ date: Date }): number { if (commits.length 0) return 999; const lastDate commits[0].date; return Math.floor((Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24)); } private getEmptyMetrics(): CommitMetrics { return { totalCommits: 0, periodCommits: 0, dailyAverage: 0, contributors: [], hourlyDistribution: new Array(24).fill(0), daysSinceLastCommit: 999, }; } }三、AI 驱动的代码异味检测传统代码异味检测依赖静态规则如 ESLint但对于长函数、深层嵌套、数据泥团等结构性异味规则引擎往往力不从心。AI 模型可以理解代码的语义结构识别更复杂的模式。3.1 检测流程3.2 核心实现/** * AI 代码异味检测器 */ interface CodeSmell { file: string; line: number; type: long_method | deep_nesting | large_class | data_clump | primitive_obsession; severity: high | medium | low; description: string; suggestion: string; } interface SmellDetectionRequest { code: string; filePath: string; language: string; } class AISmellDetector { private llmEndpoint: string; private maxCodeLength: number; constructor(llmEndpoint: string /api/ai/smell-detect, maxCodeLength 8000) { this.llmEndpoint llmEndpoint; this.maxCodeLength maxCodeLength; } /** * 批量检测指定文件的代码异味 */ async detectSmells(files: Array{ path: string; content: string; language: string }): PromiseCodeSmell[] { const results: CodeSmell[] []; // 并发检测控制并发数避免打爆 API const CONCURRENCY 3; for (let i 0; i files.length; i CONCURRENCY) { const batch files.slice(i, i CONCURRENCY); const batchResults await Promise.allSettled( batch.map(file this.detectSingleFile(file)) ); batchResults.forEach((result, index) { if (result.status fulfilled) { results.push(...result.value); } else { console.error([AISmellDetector] 文件检测失败: ${batch[index].path}, result.reason); } }); } return results; } /** * 检测单个文件的代码异味 */ private async detectSingleFile( file: { path: string; content: string; language: string } ): PromiseCodeSmell[] { // 内容过长的文件截取关键部分 const truncated file.content.length this.maxCodeLength ? file.content.slice(0, this.maxCodeLength) \n// [内容已截断] : file.content; const prompt this.buildDetectionPrompt(truncated, file.language); const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 30000); try { const response await fetch(this.llmEndpoint, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages: [{ role: user, content: prompt }], temperature: 0.1, // 低温度保证结果一致性 response_format: { type: json_object }, }), signal: controller.signal, }); if (!response.ok) { throw new Error(AI 服务异常: ${response.status}); } const data await response.json(); return this.parseSmellResults(data, file.path); } finally { clearTimeout(timeoutId); } } /** * 构建检测提示词 */ private buildDetectionPrompt(code: string, language: string): string { return JSON.stringify({ task: detect_code_smells, language, code, smell_types: [ long_method: 方法超过合理长度, deep_nesting: 嵌套层级过深3层, large_class: 类职责过多, data_clump: 总是成组出现但未封装的数据, primitive_obsession: 用基础类型代替对象, duplicated_logic: 可明显抽取的重复逻辑, ], output_format: { smells: [{ type: , severity: , line: 0, description: , suggestion: }], }, requirements: [ 只报告确实存在的问题不要过度检测, severity 按实际影响判定high影响可维护性medium建议改进low风格偏好, ], }); } /** * 解析 LLM 返回的异味检测结果 */ private parseSmellResults(data: Recordstring, unknown, filePath: string): CodeSmell[] { try { if (!data.smells || !Array.isArray(data.smells)) return []; return data.smells .filter((s: Recordstring, unknown) [long_method, deep_nesting, large_class, data_clump, primitive_obsession] .includes(s.type as string) ) .map((s: Recordstring, unknown) ({ file: filePath, line: Number(s.line) || 0, type: s.type as CodeSmell[type], severity: (s.severity as CodeSmell[severity]) || medium, description: String(s.description || ), suggestion: String(s.suggestion || ), })); } catch (error) { console.error([AISmellDetector] 结果解析失败, error); return []; } } }四、文档覆盖率评估4.1 评估维度文档覆盖率不是简单的README 是否存在而是一个多层次的检查/** * 文档覆盖率评估器 */ interface DocumentationReport { /** 总体评分 (0-100) */ overallScore: number; /** README 完整度 */ readme: { exists: boolean; hasInstallGuide: boolean; hasUsageExample: boolean; hasAPIDocs: boolean; hasContributionGuide: boolean; score: number; }; /** API 文档覆盖率 */ apiCoverage: { /** 有文档注释的导出函数比例 */ documentedExports: number; totalExports: number; coverage: number; }; /** 变更日志 */ changelog: { exists: boolean; followsSemanticVersion: boolean; lastUpdate: string | null; }; } class DocumentationAnalyzer { /** * 分析仓库文档覆盖情况 */ async analyze(repoPath: string): PromiseDocumentationReport { const fs await import(fs/promises); const path await import(path); // 检测 README const readmeResult await this.analyzeReadme(repoPath, fs, path); // 检测 API 文档覆盖率 const apiResult await this.analyzeAPICoverage(repoPath, fs, path); // 检测变更日志 const changelogResult await this.analyzeChangelog(repoPath, fs, path); // 计算总分 const overallScore Math.round( readmeResult.score * 0.4 apiResult.coverage * 0.4 (changelogResult.exists ? 20 : 0) ); return { overallScore, readme: readmeResult, apiCoverage: apiResult, changelog: changelogResult, }; } private async analyzeReadme( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[readme] { const files await fs.readdir(repoPath); const readmeFile files.find( f f.toLowerCase() readme.md || f.toLowerCase() readme ); if (!readmeFile) { return { exists: false, hasInstallGuide: false, hasUsageExample: false, hasAPIDocs: false, hasContributionGuide: false, score: 0, }; } const content await fs.readFile(path.join(repoPath, readmeFile), utf-8); const lowerContent content.toLowerCase(); const checks { hasInstallGuide: /install|安装|setup|quick.?start/i.test(lowerContent), hasUsageExample: /usage|用法|example|示例|/.test(lowerContent), hasAPIDocs: /\bapi\b|接口|endpoint/i.test(lowerContent), hasContributionGuide: /contribut|贡献|pull.?request|pr/i.test(lowerContent), }; const score Object.values(checks).filter(Boolean).length * 25; return { exists: true, ...checks, score, }; } private async analyzeAPICoverage( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[apiCoverage] { const srcPath path.join(repoPath, src); let totalExports 0; let documentedExports 0; try { const files await this.getSourceFiles(srcPath, fs, path); for (const file of files) { const content await fs.readFile(file, utf-8); const exports content.match(/export\s(const|function|class|interface|type)\s\w/g) || []; totalExports exports.length; // 检查 export 前的 JSDoc 注释 for (const exp of exports) { // 简化检查查看导出声明前的几行是否有 JSDoc 风格注释 const expIndex content.indexOf(exp); const preceding content.slice(Math.max(0, expIndex - 200), expIndex); if (/\/\*\*[\s\S]*?\*\//.test(preceding)) { documentedExports; } } } } catch { // src 目录不存在返回空结果 } return { documentedExports, totalExports, coverage: totalExports 0 ? (documentedExports / totalExports) * 100 : 0, }; } private async getSourceFiles( dir: string, fs: typeof import(fs/promises), path: typeof import(path) ): Promisestring[] { const results: string[] []; try { const entries await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath path.join(dir, entry.name); if (entry.isDirectory() entry.name ! node_modules) { results.push(...await this.getSourceFiles(fullPath, fs, path)); } else if (/\.(ts|js|tsx|jsx)$/.test(entry.name)) { results.push(fullPath); } } } catch { // 目录不可读跳过 } return results; } private async analyzeChangelog( repoPath: string, fs: typeof import(fs/promises), path: typeof import(path) ): PromiseDocumentationReport[changelog] { const changelogPath path.join(repoPath, CHANGELOG.md); try { const content await fs.readFile(changelogPath, utf-8); return { exists: true, followsSemanticVersion: /\d\.\d\.\d/.test(content), lastUpdate: this.extractLastChangelogDate(content), }; } catch { return { exists: false, followsSemanticVersion: false, lastUpdate: null }; } } private extractLastChangelogDate(content: string): string | null { const match content.match(/\d{4}-\d{2}-\d{2}/); return match ? match[0] : null; } }五、综合分析报告生成将各维度数据汇总为结构化的健康度报告综合评分以 0-100 分量化仓库健康度80 分以上为健康60-80 分需要关注60 分以下存在较高风险。每个月执行一次全量评估将报告归档至项目文档中形成可追溯的健康趋势曲线。总结AI 驱动的代码仓库健康度评估方案将传统的人工 code review 感受升级为数据驱动的量化分析提交分析通过 Git 命令行提取提交频率、贡献者分布和时间规律判断团队活跃度。AI 异味检测结合 AST 解析和 LLM 语义理解识别长方法、深层嵌套等结构性异味。文档覆盖率自动检查 README 完整性、API 文档比例和变更日志规范性。综合评分加权汇总各维度得分输出可对比的健康度趋势报告。该方案适合作为 CI 流水线的一个可选步骤或作为月度仓库健康回顾的自动化工具。建议将检测结果与告警系统打通当健康度得分持续下降或出现高危异味时自动通知团队。