diff --git a/skills/gitlink-research-insight/scripts/analyze.js b/skills/gitlink-research-insight/scripts/analyze.js index 31f7e08..9506a02 100644 --- a/skills/gitlink-research-insight/scripts/analyze.js +++ b/skills/gitlink-research-insight/scripts/analyze.js @@ -17,26 +17,28 @@ const prs = readJSON('prs-all.json').data || {}; const langs = readJSON('repo-languages.json').data || {}; const contribs = readJSON('repo-contributors.json').data || {}; -// ==== 数据提取(兼容实际字段名)==== +// ==== 数据提取 ==== const name = repo.name || 'unknown'; const fullName = repo.full_name || 'unknown'; +const isMirror = !!repo.mirror; // 镜像仓库检测 -// description: 从 issue 的 project 信息中获取,或从 repo 顶层获取 +// description let description = ''; try { description = issues.issues[0].project.description || ''; } catch(e) {} if (!description) description = repo.description || repo.about || ''; +// 从 repo 的 fork_info 获取描述 +if (!description && repo.fork_info) { + try { description = issues.issues[0].project.description || ''; } catch(e) {} +} -// 创建时间:从 issue 的第一条创建时间推断,或从项目信息获取 +// 创建时间 let created = repo.created_at || repo.created_on || ''; if (!created && issues.issues && issues.issues.length > 0) { created = issues.issues[issues.issues.length - 1].created_at || ''; } -// 支持 "2026-06-04 15:35" 格式的日期 if (created) created = created.replace(' ', 'T'); const updated = repo.updated_at || repo.last_updated || ''; - -// Star/Like — GitLink 使用 praises_count 和 watchers_count const stars = repo.praises_count || repo.watchers_count || 0; const forks = repo.forked_count || 0; const defaultBranch = repo.default_branch || 'master'; @@ -45,7 +47,6 @@ const contributorCount = repo.contributor_users_count || 0; const langEntries = Object.entries(langs).sort((a,b) => parseFloat(b[1]) - parseFloat(a[1])); const topLangs = langEntries.slice(0, 3).map(e => e[0] + ' ' + e[1]).join(', '); - const topics = (repo.topics || []).join(', ') || '无标签'; // 项目年龄(月) @@ -64,108 +65,138 @@ const closedCount = issues.closed_count || 0; const openedCount = issues.opened_count || 0; const totalIssues = closedCount + openedCount || issueList.length || issuesCount; -// PR 数据 — status 是字符串 "open"/"merged"/"closed" +// PR 数据 const prList = prs.pulls || []; -let prOpen = 0, prMerged = 0; +let prOpen = 0, prMerged = 0, prClosed = 0; prList.forEach(p => { const s = (p.status || '').toLowerCase(); if (s === 'open' || s === 'opened') prOpen++; else if (s === 'merged' || s === 'merge') prMerged++; + else if (s === 'closed') prClosed++; }); -const prTotal = repo.pull_requests_count || prList.length || 1; +const prTotal = repo.pull_requests_count || prList.length || 0; // 贡献者 const contribList = contribs.list || []; const uniqueContributors = contribList.length || contributorCount; +// ==== 是否有足够的社区活动数据 ==== +const hasActivity = totalIssues > 0 || prTotal > 0 || uniqueContributors > 1; + // ==== 维度评分 ==== // 1. 成熟度评分 (0-10) -const ageScore = Math.min(10, Math.max(0, ageMonths / 2)); +// 修复:创建时间不可用时给中等分 +const ageScore = ageMonths > 0 ? Math.min(10, ageMonths / 2) : 5; const starScore = stars > 100 ? 10 : stars > 50 ? 7 : stars > 10 ? 5 : Math.max(1, stars); const forkScore = forks > 50 ? 10 : forks > 20 ? 7 : forks > 5 ? 5 : Math.max(1, forks); -const updateScore = 7; +const updateScore = ageMonths > 0 ? 7 : 5; const maturityScore = Math.round((ageScore * 0.25 + starScore * 0.25 + forkScore * 0.20 + updateScore * 0.30) * 10) / 10; // 2. 文档质量评分 (0-10) let docScore = 3; -if (description && description.length > 10) docScore += 2; +if (description && description.length > 5) docScore += 2; if (repo.license_short_name || repo.license_name) docScore += 2; if (repo.open_devops) docScore += 1; if (repo.has_tests) docScore += 1; +// 镜像仓库且无检测到的文档,给保底分表示"数据不可用" +if (isMirror && docScore < 4) docScore = 4; docScore = Math.min(10, docScore); // 3. 贡献模式评分 (0-10) const contribScore = Math.min(10, Math.max(1, uniqueContributors * 2)); -const freqScore = 6; -const contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10; +const freqScore = hasActivity ? 6 : 3; +let contribModeScore; +if (isMirror && uniqueContributors <= 1) { + // 镜像仓库:贡献数据不可用,给中等分 + contribModeScore = 5; +} else { + contribModeScore = Math.round((contribScore * 0.30 + freqScore * 0.30 + 5 * 0.20 + 5 * 0.20) * 10) / 10; +} // 4. 社区健康度评分 (0-10) -const closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0; -const closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : 3; -const mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0; -const mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : 3; -const communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10; +let closeRate = 0, mergeRate = 0; +let closeRateScore, mergeRateScore, communityScore; +if (isMirror && totalIssues === 0 && prTotal === 0) { + closeRateScore = 5; mergeRateScore = 5; communityScore = 5; +} else { + closeRate = totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) : 0; + closeRateScore = closeRate > 80 ? 10 : closeRate > 50 ? 7 : closeRate > 30 ? 5 : totalIssues > 0 ? 3 : 5; + mergeRate = prTotal > 0 ? Math.round(prMerged / prTotal * 100) : 0; + mergeRateScore = mergeRate > 70 ? 10 : mergeRate > 40 ? 7 : prTotal > 0 ? 3 : 5; + communityScore = Math.round((closeRateScore * 0.30 + 6 * 0.25 + mergeRateScore * 0.25 + 6 * 0.20) * 10) / 10; +} // 5. 综合评分 -const totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10; +// 镜像仓库:调整权重 +let totalScore; +if (isMirror && !hasActivity) { + // 镜像仓库:成熟度和文档权重提高 + totalScore = Math.round((maturityScore * 0.40 + docScore * 0.30 + contribModeScore * 0.15 + communityScore * 0.15) * 10) / 10; +} else { + totalScore = Math.round((maturityScore * 0.25 + docScore * 0.20 + contribModeScore * 0.30 + communityScore * 0.25) * 10) / 10; +} const level = totalScore >= 9 ? '🟢 优秀' : totalScore >= 7 ? '🟢 良好' : totalScore >= 5 ? '🟡 一般' : totalScore >= 3 ? '🔴 堪忧' : '🔴 危险'; -// 可复现性 -const hasLicense = repo.license_short_name ? '✅' : '❌'; -const hasReadme = (description && description.length > 0) ? '✅' : '❌'; -const hasDevops = repo.open_devops ? '✅' : '❌'; +// 可读的状态显示 +const totalIssuesStr = totalIssues > 0 ? `${totalIssues}(关闭 ${closedCount} / 打开 ${totalIssues - closedCount})` : `${totalIssues || '—'}`; +const prTotalStr = prTotal > 0 ? `${prTotal}(合并 ${prMerged} / 打开 ${prOpen})` : `${prTotal || '—'}`; +const contributorsStr = uniqueContributors > 0 ? `${uniqueContributors} 人` : '—'; // 改进建议 const suggestions = []; if (stars < 20) suggestions.push('🟡 中优先级 — 项目影响力有限,建议在社区中推广'); -if (!repo.license_short_name) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件'); -if (uniqueContributors < 3) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者'); +if (!repo.license_short_name && !isMirror) suggestions.push('🔴 高优先级 — 缺少开源许可证,建议添加 LICENSE 文件'); +if (uniqueContributors < 3 && !isMirror) suggestions.push('🔴 高优先级 — 贡献者较少,建议标记 good-first-issue 吸引新贡献者'); if (closeRate < 50 && totalIssues > 0) suggestions.push('🟡 中优先级 — Issue 关闭率偏低,建议定期清理积压 Issue'); if (mergeRate < 50 && prTotal > 1) suggestions.push('🟡 中优先级 — PR 合并率偏低,建议加快 PR 处理速度'); +if (isMirror && !hasActivity) suggestions.push('ℹ️ 该项目为镜像仓库,部分社区活动数据不可用,评分仅供参考'); if (suggestions.length === 0) suggestions.push('🟢 低优先级 — 项目状态良好,继续保持'); const now = new Date(); const dateStr = now.toISOString().slice(0, 10); const timeStr = now.toISOString().slice(0, 19).replace('T', ' '); +const ageDisplay = ageMonths > 0 ? `约 ${Math.round(ageMonths)} 个月` : '未知'; +const mirrorNote = isMirror ? '\n> ℹ️ 该项目为镜像仓库,部分数据(贡献者/Issue/PR)可能不完整\n' : ''; + // ==== 生成报告 ==== const report = `# 🔬 科研项目洞察报告 — ${fullName} **分析日期:** ${dateStr} -**综合评分:** ${totalScore}/10 ${level} - +**综合评分:** ${totalScore}/10 ${level}${isMirror ? ' 📦 镜像仓库' : ''} +${mirrorNote} ## 一、项目画像 | 维度 | 数据 | |------|------| | 项目名称 | ${name} | | 描述 | ${description || '无描述'} | -| 创建时间 | ${created || '未知'}(约 ${Math.round(ageMonths)} 个月) | +| 创建时间 | ${created || '未知'}(${ageDisplay}) | | Star / Fork | ${stars} / ${forks} | | 默认分支 | ${defaultBranch} | | 主要语言 | ${topLangs || '未知'} | | 研究主题 | ${topics} | -| 贡献者数 | ${uniqueContributors} 人 | +| 贡献者数 | ${contributorsStr} | | Issue 总数 | ${totalIssues} | ## 二、项目成熟度(${maturityScore}/10) **评分因子:** -- 项目年龄:约 ${Math.round(ageMonths)} 个月 → ${ageScore.toFixed(1)} 分(权重 25%) +- 项目年龄:${ageDisplay} → ${ageScore.toFixed(1)} 分(权重 25%) - 社区影响力:${stars} Star / ${forks} Fork → ${Math.round(starScore)} 分(权重 25%) - Fork 活跃度:${forks} Fork → ${Math.round(forkScore)} 分(权重 20%) - 维护活跃度:最近更新 ${updated || '未知'} → ${updateScore} 分(权重 30%) -**分析:** 项目创建约 ${Math.round(ageMonths)} 个月,获得 ${stars} 个 Star 和 ${forks} 个 Fork。${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'} +**分析:** ${stars > 50 ? '项目已有一定社区影响力,处于成长期。' : '项目尚在早期阶段,需加强社区推广。'} ## 三、文档质量(${docScore}/10) | 检查项 | 状态 | 说明 | |--------|------|------| -| README | ${hasReadme} | ${description ? '项目描述已填写' : '建议完善 README'} | -| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '未检测到,建议添加'} | -| DevOps CI | ${hasDevops} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} | +| README | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? '项目描述已填写' : '建议完善 README'} | +| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '未检测到,建议添加'} | +| DevOps CI | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已配置' : '建议配置 CI 流水线'} | **改进建议:** ${docScore < 6 ? '建议补充 README、LICENSE 和 CI 配置以提升项目规范性' : '文档基本完善'} @@ -173,29 +204,29 @@ const report = `# 🔬 科研项目洞察报告 — ${fullName} | 指标 | 数据 | 评分 | |------|------|------| -| 贡献者数量 | ${uniqueContributors} 人 | ${contribScore}/10 | -| 提交活跃度 | 中等 | ${freqScore}/10 | +| 贡献者数量 | ${contributorsStr} | ${isMirror && uniqueContributors <= 1 ? '—(镜像仓库)' : contribScore + '/10'} | +| 提交活跃度 | ${hasActivity ? '中等' : '—(镜像仓库)'} | ${isMirror && !hasActivity ? '—' : freqScore + '/10'} | -**分析:** 项目有 ${uniqueContributors} 位贡献者。${uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'} +**分析:** ${isMirror && uniqueContributors <= 1 ? '该项目为镜像仓库,贡献者数据不代表实际开发团队。' : uniqueContributors < 3 ? '目前贡献者偏少,建议通过 good-first-issue 标签吸引新贡献者。' : '团队协作良好,有多位贡献者共同维护。'} ## 五、社区健康度(${communityScore}/10) | 指标 | 数据 | 评分 | |------|------|------| -| Issue 总数 | ${totalIssues}(关闭 ${closedCount} / 打开 ${totalIssues - closedCount})| — | -| 关闭率 | ${closeRate}% | ${closeRateScore}/10 | -| PR 总数 | ${prTotal}(合并 ${prMerged} / 打开 ${prOpen})| — | -| 合并率 | ${mergeRate}% | ${mergeRateScore}/10 | +| Issue 总数 | ${totalIssuesStr} | — | +| 关闭率 | ${totalIssues > 0 ? Math.round(closedCount / totalIssues * 100) + '%' : '—'} | ${closeRateScore}/10 | +| PR 总数 | ${prTotalStr} | — | +| 合并率 | ${prTotal > 0 ? Math.round(prMerged / prTotal * 100) + '%' : '—'} | ${mergeRateScore}/10 | -**分析:** Issue 关闭率 ${closeRate}%${closeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'};PR 合并率 ${mergeRate}%${mergeRate > 50 ? ' ✅ 良好' : ' ⚠️ 偏低'}。 +**分析:** ${isMirror && totalIssues === 0 && prTotal === 0 ? '该项目为镜像仓库,没有独立 Issue 和 PR 数据。' : (closeRate > 50 ? 'Issue 关闭率 ✅ 良好' : 'Issue 关闭率 ⚠️ 偏低') + ';' + (mergeRate > 50 ? 'PR 合并率 ✅ 良好' : 'PR 合并率 ⚠️ 偏低')} ## 六、可复现性评估 | 检查项 | 状态 | 说明 | |--------|------|------| -| 开源许可证 | ${hasLicense} | ${repo.license_short_name || '建议添加以便他人使用'} | -| 依赖文档化 | — | 需人工检查 go.mod / package.json | -| 运行说明 | ${hasReadme} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} | -| CI 配置 | ${hasDevops} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} | +| 开源许可证 | ${repo.license_short_name ? '✅' : '❌'} | ${repo.license_short_name || '建议添加以便他人使用'} | +| 依赖文档化 | ${hasActivity ? '—' : '⏭️'} | ${hasActivity ? '需人工检查' : '镜像仓库,跳过'} | +| 运行说明 | ${description && description.length > 5 ? '✅' : '❌'} | ${description ? 'README 包含基本说明' : 'README 缺少运行说明'} | +| CI 配置 | ${repo.open_devops ? '✅' : '❌'} | ${repo.open_devops ? '已有 DevOps 流水线' : '建议配置 CI'} | ## 七、改进建议 ${suggestions.map((s, i) => (i + 1) + '. ' + s).join('\n')}