Merge pull request 'workflow的skill增加' (#25) from zk_branch into master
This commit is contained in:
commit
ea86b2b5a8
|
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>科研知识图谱 — 2026-07-06</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 36px 30px; }
|
||||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||||
.header .subtitle { opacity: 0.8; font-size: 14px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 24px; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 18px; box-shadow: 0 2px 8px rgba(0,0,0,.08); text-align: center; }
|
||||
.card .value { font-size: 32px; font-weight: 700; color: #1a237e; }
|
||||
.card .label { font-size: 12px; color: #888; margin-top: 4px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); margin-bottom: 24px; }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
#graphChart { width: 100%; height: 600px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||||
th { background: #f5f7fa; color: #555; font-weight: 600; }
|
||||
.tag { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; margin: 2px; }
|
||||
.tag.rising { background: #e8f5e9; color: #2e7d32; }
|
||||
.tag.stable { background: #e3f2fd; color: #1565c0; }
|
||||
.tag.declining { background: #fce4ec; color: #c62828; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>科研知识图谱</h1>
|
||||
<div class="subtitle">
|
||||
关键词:LLM,Agent —
|
||||
仓库:4 个 —
|
||||
贡献者:0 人 —
|
||||
2026-07-06
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="cards">
|
||||
<div class="card"><div class="value">4</div><div class="label">仓库节点</div></div>
|
||||
<div class="card"><div class="value">0</div><div class="label">贡献者节点</div></div>
|
||||
<div class="card"><div class="value">2</div><div class="label">主题节点</div></div>
|
||||
<div class="card"><div class="value">3</div><div class="label">关系边</div></div>
|
||||
<div class="card"><div class="value">N/A</div><div class="label">最热仓库</div></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>知识图谱 — 力导向布局</h2>
|
||||
<div id="graphChart"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>热度排行榜</h2>
|
||||
<table id="hotnessTable">
|
||||
<thead><tr><th>排名</th><th>仓库</th><th>热度</th><th>语言</th><th>Stars</th><th>趋势</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — 2026-07-06</div>
|
||||
|
||||
<script>
|
||||
var graph = echarts.init(document.getElementById('graphChart'));
|
||||
graph.setOption({
|
||||
tooltip: {
|
||||
formatter: function(p) {
|
||||
if (p.dataType === 'edge') return p.data.source + ' → ' + p.data.target + '<br/>' + p.data.evidence;
|
||||
var d = p.data;
|
||||
return '<b>' + d.label + '</b><br/>' + (d.desc || '') + '<br/>' +
|
||||
(d.stars ? 'Stars: ' + d.stars : '') + (d.repo_count ? ' 关联仓库: ' + d.repo_count : '');
|
||||
}
|
||||
},
|
||||
legend: [{
|
||||
data: ['仓库', '贡献者', '主题', '论文', '组织'],
|
||||
orient: 'vertical', right: 10, top: 20
|
||||
}],
|
||||
series: [{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
roam: true,
|
||||
draggable: true,
|
||||
force: {
|
||||
repulsion: 200,
|
||||
edgeLength: [80, 300],
|
||||
layoutAnimation: true
|
||||
},
|
||||
categories: [
|
||||
{ name: '仓库', itemStyle: { color: '#5470c6' }, symbol: 'roundRect' },
|
||||
{ name: '贡献者', itemStyle: { color: '#91cc75' }, symbol: 'circle' },
|
||||
{ name: '主题', itemStyle: { color: '#fac858' }, symbol: 'diamond' },
|
||||
{ name: '论文', itemStyle: { color: '#ee6666' }, symbol: 'triangle' },
|
||||
{ name: '组织', itemStyle: { color: '#73c0de' }, symbol: 'pin' }
|
||||
],
|
||||
data: [{"id":"topic:llm","type":"topic","label":"LLM\n","symbolSize":30,"category":2},{"id":"topic:agent","type":"topic","label":"Agent\n","symbolSize":30,"category":2}],
|
||||
links: [{"source":"repo:agent","target":"repo:ribo-agent","type":"related_to","weight":0.5,"evidence":"共同主题: Agent\n"},{"source":"repo:doutrip","target":"repo:agent","type":"related_to","weight":0.5,"evidence":"共同主题: Agent\n"},{"source":"repo:ribo-agent","target":"repo:wow-agent","type":"related_to","weight":0.5,"evidence":"共同主题: Agent\n"}],
|
||||
label: { show: true, fontSize: 11, formatter: '{b}' },
|
||||
emphasis: { focus: 'adjacency', label: { fontSize: 14 } },
|
||||
lineStyle: { color: '#ccc', curveness: 0.1 }
|
||||
}]
|
||||
});
|
||||
window.addEventListener('resize', function() { graph.resize(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"metadata": {
|
||||
"generated_at": "2026-07-06T11:51:35+08:00",
|
||||
"search_keywords": [
|
||||
"LLM",
|
||||
"Agent"
|
||||
],
|
||||
"total_repos_scanned": 4,
|
||||
"total_contributors_found": 0,
|
||||
"total_edges_inferred": 3
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "topic:llm",
|
||||
"type": "topic",
|
||||
"label": "LLM\n",
|
||||
"symbolSize": 30,
|
||||
"category": 2
|
||||
},
|
||||
{
|
||||
"id": "topic:agent",
|
||||
"type": "topic",
|
||||
"label": "Agent\n",
|
||||
"symbolSize": 30,
|
||||
"category": 2
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "repo:agent",
|
||||
"target": "repo:ribo-agent",
|
||||
"type": "related_to",
|
||||
"weight": 0.5,
|
||||
"evidence": "共同主题: Agent\n"
|
||||
},
|
||||
{
|
||||
"source": "repo:doutrip",
|
||||
"target": "repo:agent",
|
||||
"type": "related_to",
|
||||
"weight": 0.5,
|
||||
"evidence": "共同主题: Agent\n"
|
||||
},
|
||||
{
|
||||
"source": "repo:ribo-agent",
|
||||
"target": "repo:wow-agent",
|
||||
"type": "related_to",
|
||||
"weight": 0.5,
|
||||
"evidence": "共同主题: Agent\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>zzx-coder/gitlink-cli — 复现性评分卡</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
.chart { width: 100%; height: 350px; }
|
||||
.grade-circle { text-align: center; padding: 20px; }
|
||||
.grade-letter { font-size: 72px; font-weight: 900; }
|
||||
.grade-A { color: #2e7d32; }
|
||||
.grade-B { color: #558b2f; }
|
||||
.grade-C { color: #f57c00; }
|
||||
.grade-D { color: #e65100; }
|
||||
.grade-F { color: #c62828; }
|
||||
.grade-score { font-size: 24px; color: #888; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||||
th { background: #f5f7fa; color: #555; }
|
||||
.bar { height: 8px; border-radius: 4px; background: #e0e0e0; margin-top: 4px; }
|
||||
.bar-fill { height: 100%; border-radius: 4px; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>zzx-coder/gitlink-cli — 科研复现性评分卡</h1>
|
||||
<div style="opacity:0.8;font-size:14px;">2026-07-06</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="panel grade-circle">
|
||||
<div class="grade-letter grade-F">F</div>
|
||||
<div class="grade-score">5.0 / 100</div>
|
||||
<div style="margin-top:12px;color:#888;">
|
||||
|
||||
|
||||
|
||||
|
||||
差 — 几乎不可复现
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>维度雷达图</h2>
|
||||
<div id="radarChart" class="chart"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>维度明细</h2>
|
||||
<table>
|
||||
<tr><th>维度</th><th>评分</th><th>权重</th></tr>
|
||||
<tr><td>许可证</td><td>0%</td><td>15%</td></tr>
|
||||
<tr><td>无密钥/PII</td><td>0%</td><td>15%</td></tr>
|
||||
<tr><td>README 完整</td><td>0%</td><td>15%</td></tr>
|
||||
<tr><td>依赖声明</td><td>0%</td><td>15%</td></tr>
|
||||
<tr><td>构建说明</td><td>50%</td><td>10%</td></tr>
|
||||
<tr><td>CI 配置</td><td>0%</td><td>10%</td></tr>
|
||||
<tr><td>测试证据</td><td>0%</td><td>10%</td></tr>
|
||||
<tr><td>数据可用性</td><td>0%</td><td>10%</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>详细评估与改进建议</h2>
|
||||
<table>
|
||||
<tr><th>维度</th><th>评分</th><th>证据</th><th>建议</th></tr>
|
||||
<tr>
|
||||
<td>许可证</td>
|
||||
<td>❌</td>
|
||||
<td>未扫描(无本地仓库)</td>
|
||||
<td>建议添加 MIT/Apache-2.0/GPL-3.0 许可证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>无密钥/PII</td>
|
||||
<td>⚠️</td>
|
||||
<td>未扫描(无本地仓库)</td>
|
||||
<td>立即移除泄露的密钥,使用环境变量管理敏感信息</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>README 完整</td>
|
||||
<td>❌</td>
|
||||
<td>README 缺失或过于简略</td>
|
||||
<td>补充项目目的、安装、使用、许可和引用章节</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>依赖声明</td>
|
||||
<td>❌</td>
|
||||
<td>无依赖声明</td>
|
||||
<td>添加 package.json/go.mod/requirements.txt 等标准依赖文件</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>构建说明</td>
|
||||
<td>⚠️</td>
|
||||
<td>部分构建说明</td>
|
||||
<td>添加 Makefile/Dockerfile + README 中的构建步骤</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CI 配置</td>
|
||||
<td>❌</td>
|
||||
<td>无 CI 配置</td>
|
||||
<td>配置 GitLink CI 或 GitHub Actions 自动构建和测试</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试证据</td>
|
||||
<td>❌</td>
|
||||
<td>无测试证据</td>
|
||||
<td>添加单元测试和集成测试,在 README 中说明如何运行</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>数据可用性</td>
|
||||
<td>❌</td>
|
||||
<td>无数据可用性声明</td>
|
||||
<td>说明数据集来源,提供 Zenodo/Figshare 链接或生成脚本</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — 2026-07-06</div>
|
||||
|
||||
<script>
|
||||
var radarChart = echarts.init(document.getElementById('radarChart'));
|
||||
radarChart.setOption({
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '许可证', max: 100 },
|
||||
{ name: '无密钥', max: 100 },
|
||||
{ name: 'README', max: 100 },
|
||||
{ name: '依赖', max: 100 },
|
||||
{ name: '构建', max: 100 },
|
||||
{ name: 'CI', max: 100 },
|
||||
{ name: '测试', max: 100 },
|
||||
{ name: '数据', max: 100 }
|
||||
],
|
||||
center: ['50%', '55%'],
|
||||
radius: '70%'
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
50,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
name: '复现性',
|
||||
areaStyle: { color: 'rgba(57,73,171,0.3)' },
|
||||
lineStyle: { color: '#3949ab' }
|
||||
}]
|
||||
}]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>zzx-coder/gitlink-cli — 科研项目洞察报告</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #283593 50%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||||
.header h1 { font-size: 28px; margin-bottom: 8px; }
|
||||
.header .subtitle { opacity: 0.85; font-size: 14px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.card .label { font-size: 12px; color: #888; text-transform: uppercase; margin-bottom: 6px; }
|
||||
.card .value { font-size: 28px; font-weight: 700; }
|
||||
.card .value.hot { color: #e53935; }
|
||||
.card .value.warm { color: #f57c00; }
|
||||
.card .value.cool { color: #1565c0; }
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
.chart { width: 100%; height: 350px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 14px; }
|
||||
th { background: #f5f7fa; color: #555; font-weight: 600; }
|
||||
.tag { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; margin: 2px; }
|
||||
.tag.lang { background: #e3f2fd; color: #1565c0; }
|
||||
.tag.research { background: #e8f5e9; color: #2e7d32; }
|
||||
.tag.warn { background: #fff3e0; color: #e65100; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>zzx-coder/gitlink-cli</h1>
|
||||
<div class="subtitle">科研项目洞察报告 — 2026-07-06</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="label">热度评分</div>
|
||||
<div class="value hot">54.3</div>
|
||||
<div class="label">Hot</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Stars</div>
|
||||
<div class="value">0</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Forks</div>
|
||||
<div class="value">0</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">贡献者</div>
|
||||
<div class="value">3</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">开放 Issues</div>
|
||||
<div class="value">44</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">PR 合并率</div>
|
||||
<div class="value">50.0%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<h2>项目概况</h2>
|
||||
<table>
|
||||
<tr><th>项目名称</th><td>gitlink-cli</td></tr>
|
||||
<tr><th>描述</th><td>No description</td></tr>
|
||||
<tr><th>主要语言</th><td><span class="tag lang">Unknown</span></td></tr>
|
||||
<tr><th>技术栈</th><td><span class="tag lang">Unknown</span></td></tr>
|
||||
<tr><th>创建时间</th><td></td></tr>
|
||||
<tr><th>最后更新</th><td> (365 天前)</td></tr>
|
||||
<tr><th>科研特征</th><td></td></tr>
|
||||
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>活动概览</h2>
|
||||
<div id="activityChart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<h2>健康指标</h2>
|
||||
<table>
|
||||
<tr><th>指标</th><th>数值</th><th>状态</th></tr>
|
||||
<tr><td>Issue 总量</td><td>44 开放 / 44 已关闭</td><td><span class="tag warn">需关注</span></td></tr>
|
||||
<tr><td>PR 合并率</td><td>50.0%</td><td><span class="tag warn">需改进</span></td></tr>
|
||||
<tr><td>Release 数</td><td>6</td><td><span class="tag research">已发布</span></td></tr>
|
||||
<tr><td>CI 通过率</td><td>0% (0 次构建)</td><td><span class="tag warn">不稳定</span></td></tr>
|
||||
<tr><td>贡献者数</td><td>3 人</td><td><span class="tag warn">单人项目</span></td></tr>
|
||||
<tr><td>活跃度</td><td>365 天前更新</td><td><span class="tag warn">不活跃</span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>热度构成</h2>
|
||||
<div id="hotnessChart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — 2026-07-06</div>
|
||||
|
||||
<script>
|
||||
var hotnessChart = echarts.init(document.getElementById('hotnessChart'));
|
||||
hotnessChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 0 },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['45%', '75%'],
|
||||
label: { formatter: '{b}\n{d}%' },
|
||||
data: [
|
||||
{ name: 'Stars', value: 0.0, itemStyle: { color: '#5470c6' } },
|
||||
{ name: 'Forks', value: 0.0, itemStyle: { color: '#91cc75' } },
|
||||
{ name: 'Issues', value: 88.0, itemStyle: { color: '#fac858' } },
|
||||
{ name: 'PRs', value: 133.3, itemStyle: { color: '#ee6666' } },
|
||||
{ name: 'Releases', value: 60.0, itemStyle: { color: '#73c0de' } },
|
||||
{ name: 'Recency', value: 10, itemStyle: { color: '#fc8452' } }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
var activityChart = echarts.init(document.getElementById('activityChart'));
|
||||
activityChart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: ['Issues', 'PRs', 'Releases', 'CI Builds'] },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '开放/进行中', type: 'bar', data: [44, 20, 0, 0], itemStyle: { color: '#fac858' } },
|
||||
{ name: '已完成', type: 'bar', data: [44, 20, 6, 0], itemStyle: { color: '#91cc75' } }
|
||||
]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -399,7 +399,7 @@ func newBatchLabelShortcut() *common.Shortcut {
|
|||
Name: "batch-label",
|
||||
Description: "Change tracker label for multiple issues",
|
||||
Flags: []common.Flag{
|
||||
{Name: "label", Short: "l", Usage: "Target label: bug, feature, support, doc, test, duplicate, question", Required: true},
|
||||
{Name: "label", Short: "l", Usage: "Target label: bug, feature, support, doc, test, duplicate, question, or Chinese names (缺陷/功能/文档/重复/疑问/支持/任务/测试/协助/搁置)", Required: true},
|
||||
{Name: "numbers", Short: "n", Usage: "Comma-separated issue numbers, e.g. 1,2,3"},
|
||||
{Name: "from", Usage: "Read issue numbers from a CSV file"},
|
||||
{Name: "dry-run", Usage: "Preview without making changes", Bool: true, Default: "false"},
|
||||
|
|
@ -444,7 +444,7 @@ func runBatchLabel(ctx *common.RuntimeContext) error {
|
|||
summary.Results = append(summary.Results, result)
|
||||
continue
|
||||
}
|
||||
if err := updateIssueField(ctx, number, map[string]interface{}{"tracker_id": trackerID}); err != nil {
|
||||
if err := updateIssueField(ctx, number, map[string]interface{}{"issue_tag_ids": []int{trackerID}}); err != nil {
|
||||
result.Status = "failed"
|
||||
result.Error = err.Error()
|
||||
summary.Failed++
|
||||
|
|
@ -552,7 +552,14 @@ func parsePriority(p string) (int, error) {
|
|||
}
|
||||
|
||||
func parseTracker(label string) (int, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(label)) {
|
||||
trimmed := strings.TrimSpace(label)
|
||||
|
||||
// Check Chinese tag names first
|
||||
if id, ok := tagIDs[trimmed]; ok {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(trimmed) {
|
||||
case "bug":
|
||||
return trackerBug, nil
|
||||
case "feature":
|
||||
|
|
@ -571,7 +578,7 @@ func parseTracker(label string) (int, error) {
|
|||
if id, err := strconv.Atoi(label); err == nil {
|
||||
return id, nil
|
||||
}
|
||||
return 0, fmt.Errorf("invalid label %q: use bug, feature, support, doc, test, duplicate, or question", label)
|
||||
return 0, fmt.Errorf("invalid label %q: use bug, feature, support, doc, test, duplicate, question, or Chinese names (%s)", label, labelNames(tagIDs))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
name: gitlink-research
|
||||
version: 1.0.0
|
||||
description: "GitLink 科研辅助系统:项目洞察、热点追踪、合规复现、协作匹配、进度预警、论文引用。服务科研工作者、课题组、科研团队。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
triggers:
|
||||
- "科研"
|
||||
- "research"
|
||||
- "论文"
|
||||
- "citation"
|
||||
- "知识图谱"
|
||||
- "knowledge graph"
|
||||
- "热点追踪"
|
||||
- "合规检查"
|
||||
- "复现性"
|
||||
- "reproducibility"
|
||||
- "协作匹配"
|
||||
- "进度跟踪"
|
||||
- "项目洞察"
|
||||
- "引用格式"
|
||||
- "BibTeX"
|
||||
---
|
||||
|
||||
# gitlink-research(科研辅助系统)
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
|
||||
>
|
||||
> **实现方式:** 场景 1/3/5/6 有可执行脚本(`workflows/academic/`);场景 2/4 由 AI Agent 直接执行(需理解、推理、判断)。
|
||||
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh` 操作 GitLink 资源。**
|
||||
|
||||
本 Skill 将 GitLink 平台的代码托管与协作数据转化为科研创新支撑能力,实现科研项目分析、主体画像、热点追踪、创新启发、合规校验等全链路辅助服务。
|
||||
|
||||
## 功能菜单
|
||||
|
||||
```
|
||||
====== GitLink 科研辅助系统 ======
|
||||
|
||||
请选择功能:
|
||||
|
||||
1. 仓库级科研项目洞察
|
||||
→ 仓库深度画像:项目定位、技术栈、活动健康、贡献者网络
|
||||
|
||||
2. 科研热点追踪与知识图谱
|
||||
→ 跨仓库搜索、热点趋势、知识图谱可视化
|
||||
|
||||
3. 科研项目合规与复现性检查
|
||||
→ 许可证合规 + 8维复现性评分卡
|
||||
|
||||
4. 科研协作智能匹配
|
||||
→ 互补项目发现、潜在合作者推荐
|
||||
|
||||
5. 科研进度智能跟踪与预警
|
||||
→ 里程碑跟踪、异常检测、周报生成
|
||||
|
||||
6. 一键生成论文引用格式
|
||||
→ BibTeX / APA / MLA / GB/T 7714 / CITATION.cff
|
||||
|
||||
请输入编号(1-6)或功能名称:
|
||||
```
|
||||
|
||||
## 用户输入 → 场景映射
|
||||
|
||||
| 用户输入 | 执行的场景 | Reference 文件 | 实现方式 |
|
||||
|----------|-----------|---------------|---------|
|
||||
| `1` / "项目洞察" | 仓库级科研项目洞察 | [`research-project-insights.md`](references/research-project-insights.md) | `workflows/academic/06-research-insights.sh` 脚本 |
|
||||
| `2` / "热点追踪" / "知识图谱" | 科研热点追踪与知识图谱 | [`research-hotspot-tracking.md`](references/research-hotspot-tracking.md) | **AI Agent 直接执行** |
|
||||
| `3` / "合规复现" / "合规检查" | 项目合规与复现性检查 | [`research-compliance-repro.md`](references/research-compliance-repro.md) | `workflows/academic/08-research-compliance.sh` 脚本 |
|
||||
| `4` / "协作匹配" | 科研协作智能匹配 | [`research-collab-matching.md`](references/research-collab-matching.md) | **AI Agent 直接执行** |
|
||||
| `5` / "进度预警" / "进度跟踪" | 进度跟踪与预警 | [`research-progress-tracking.md`](references/research-progress-tracking.md) | `workflows/academic/10-research-progress.sh` 脚本 |
|
||||
| `6` / "引用格式" / "论文引用" | 论文引用格式生成 | [`research-citation-format.md`](references/research-citation-format.md) | `workflows/academic/11-research-citation.sh` 脚本 |
|
||||
|
||||
## 执行流程
|
||||
|
||||
1. **展示菜单** — 列出 6 个科研辅助场景
|
||||
2. **获取用户选择** — 用户输入编号或功能名称
|
||||
3. **读取 Reference** — 根据选择读取对应的 reference 文件
|
||||
4. **确认参数** — 询问必要的参数(owner/repo/keywords 等)
|
||||
5. **执行**:
|
||||
- **脚本场景**(1/3/5/6)→ 运行 `workflows/` 下的 Shell 脚本
|
||||
- **AI 场景**(2/4)→ Agent 直接调用 `gitlink-cli` 收集数据,AI 完成分析和生成
|
||||
6. **展示结果** — 输出摘要,HTML 报告自动打开
|
||||
|
||||
## 快捷触发
|
||||
|
||||
用户可以直接说特定意图,跳过菜单:
|
||||
|
||||
| 用户说的话 | 直接执行 |
|
||||
|------------|----------|
|
||||
| "分析一下这个仓库的科研价值" | 场景 1:项目洞察 |
|
||||
| "帮我追踪 NLP 的热点" | 场景 2:热点追踪 |
|
||||
| "检查这个项目的复现性" | 场景 3:合规复现 |
|
||||
| "帮我找合作者" | 场景 4:协作匹配 |
|
||||
| "看一下项目进度有没有风险" | 场景 5:进度预警 |
|
||||
| "生成这个项目的论文引用" | 场景 6:引用格式 |
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 说明 | 获取方式 |
|
||||
|------|------|----------|
|
||||
| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--keywords` | 搜索关键词(逗号分隔) | 询问用户(场景 2、4) |
|
||||
| `--format` | 引用格式(场景 6) | 询问用户,默认 all |
|
||||
| `--org` | 组织名称 | 询问用户(场景 5 多仓库模式) |
|
||||
|
||||
## 输出产物
|
||||
|
||||
每个场景生成的产物:
|
||||
|
||||
| 场景 | 产物 |
|
||||
|------|------|
|
||||
| 1. 项目洞察 | `output/research-insights-{repo}-{date}.html` + Wiki 页面 |
|
||||
| 2. 知识图谱 | `output/knowledge-graph-{date}.json` + `output/knowledge-graph-{date}.html` + Wiki |
|
||||
| 3. 合规复现 | `output/reproducibility-{repo}-{date}.html` + Wiki |
|
||||
| 4. 协作匹配 | `output/collab-match-{date}.html` + Wiki |
|
||||
| 5. 进度预警 | `output/progress-weekly-{date}.html` + Wiki |
|
||||
| 6. 引用格式 | 控制台输出 + `CITATION.cff` 文件(可选) |
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
- [gitlink-workflow](../gitlink-workflow/SKILL.md) — 通用 AI 工作流
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# 场景 6:一键生成论文引用格式
|
||||
|
||||
## 目标
|
||||
|
||||
从 GitLink 仓库提取元数据,自动生成学术论文引用格式(BibTeX / APA / MLA / GB/T 7714 / CITATION.cff)。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 是 | 仓库所有者 |
|
||||
| `--repo` | 是 | 仓库名称 |
|
||||
| `--format` | 否 | bibtex / apa / mla / gbt7714 / cff / all(默认 all) |
|
||||
| `--output` | 否 | 输出文件路径(默认 stdout) |
|
||||
|
||||
## 数据收集步骤
|
||||
|
||||
### Step 1: 获取仓库基本信息
|
||||
```bash
|
||||
gitlink-cli repo +info --owner $OWNER --repo $REPO --format json
|
||||
```
|
||||
提取字段:
|
||||
- `.data.name` → 项目名称
|
||||
- `.data.description` → 描述
|
||||
- `.data.owner.login` → 所有者用户名
|
||||
- `.data.updated_at` / `.data.created_at` → 日期
|
||||
|
||||
### Step 2: 获取最新 Release
|
||||
```bash
|
||||
gitlink-cli release +list --owner $OWNER --repo $REPO --limit 1 --format json
|
||||
```
|
||||
提取字段:
|
||||
- `.data[0].tag_name` → 版本号
|
||||
- `.data[0].created_at` → 发布日期
|
||||
|
||||
### Step 3: 获取贡献者列表
|
||||
```bash
|
||||
gitlink-cli repo +members --owner $OWNER --repo $REPO --limit 20 --format json
|
||||
```
|
||||
提取 `.data[].login` 和 `.data[].name` 组装作者列表。
|
||||
|
||||
### Step 4: 检测 DOI(可选)
|
||||
在仓库描述和 README 中扫描 DOI 模式 `10.\d{4,}/[\w.\-/]+`:
|
||||
```bash
|
||||
gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null
|
||||
```
|
||||
用 grep 提取 DOI。
|
||||
|
||||
### Step 5: 获取仓库 URL
|
||||
```bash
|
||||
git remote get-url origin
|
||||
```
|
||||
|
||||
## 格式模板
|
||||
|
||||
### BibTeX
|
||||
```bibtex
|
||||
@software{${REPO_SHORTNAME},
|
||||
author = {${AUTHOR_LIST_BIBTEX}},
|
||||
title = {${REPO_NAME}},
|
||||
version = {${VERSION}},
|
||||
date = {${RELEASE_DATE}},
|
||||
publisher = {GitLink},
|
||||
url = {${REPO_URL}},
|
||||
note = {${DESCRIPTION}}
|
||||
}
|
||||
```
|
||||
|
||||
### APA 7th
|
||||
```
|
||||
${AUTHOR_LIST_APA} (${YEAR}). ${REPO_NAME} (Version ${VERSION}) [Computer software].
|
||||
GitLink. ${REPO_URL}
|
||||
```
|
||||
|
||||
### MLA 9th
|
||||
```
|
||||
${AUTHOR_LIST_MLA}. ${REPO_NAME}. Version ${VERSION}, GitLink,
|
||||
${RELEASE_DATE}, ${REPO_URL}.
|
||||
```
|
||||
|
||||
### GB/T 7714-2015
|
||||
```
|
||||
[1] ${AUTHOR_LIST_GB}. ${REPO_NAME}[CP/OL]. ${VERSION}. GitLink,
|
||||
${RELEASE_DATE}[${CITE_DATE}]. ${REPO_URL}.
|
||||
```
|
||||
|
||||
### CITATION.cff
|
||||
```yaml
|
||||
cff-version: 1.2.0
|
||||
message: "If you use this software, please cite it as below."
|
||||
authors:
|
||||
- family-names: ${FAMILY_NAME}
|
||||
given-names: ${GIVEN_NAME}
|
||||
title: ${REPO_NAME}
|
||||
version: ${VERSION}
|
||||
date-released: ${RELEASE_DATE}
|
||||
url: ${REPO_URL}
|
||||
repository-code: ${REPO_GIT_URL}
|
||||
```
|
||||
|
||||
## 作者列表格式化规则
|
||||
|
||||
- **BibTeX**: `Last1, First1 and Last2, First2`
|
||||
- **APA**: `Last1, F., & Last2, F. (YYYY)`
|
||||
- **MLA**: `Last1, First1, et al.`(超过 2 人用 et al.)
|
||||
- **GB/T 7714**: `作者1, 作者2`(英文名保留原名,中文名用中文)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 如果仓库没有 Release,版本号用 "v0.0.0-dev",日期用仓库最后更新时间
|
||||
- 作者列表优先使用 `name` 字段,回退到 `login`
|
||||
- 超过 10 个贡献者时,只取前 5 个 + "et al."
|
||||
- DOI 不存在时,BibTeX 省略 `doi` 字段
|
||||
- 确保生成的 `.cff` 文件是合法 YAML
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# 场景 4:科研协作智能匹配
|
||||
|
||||
> **执行方式:AI Agent 直接执行**(无脚本,需语义理解匹配理由)
|
||||
|
||||
## 目标
|
||||
|
||||
分析研究者的代码仓库特征,在 GitLink 上搜索互补项目和潜在合作者,生成有说服力的匹配推荐及理由。
|
||||
|
||||
## 参数获取
|
||||
|
||||
向用户询问:
|
||||
1. **源仓库**(必需):owner 和 repo(可从当前目录 git remote 自动解析)
|
||||
2. **额外搜索关键词**(可选):不填则从仓库自动提取
|
||||
3. **推荐上限**(可选,默认 5)
|
||||
|
||||
## Agent 执行步骤
|
||||
|
||||
### Step 1: 分析源仓库画像
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +info --owner <o> --repo <r> --format json
|
||||
gitlink-cli repo +members --owner <o> --repo <r> --limit 20 --format json
|
||||
gitlink-cli issue +list --owner <o> --repo <r> --state open --limit 30 --format json
|
||||
```
|
||||
|
||||
从返回数据中提取(需 AI 理解):
|
||||
- **技术栈**:从 language 字段 + description 推断具体技术(如 "Go + CLI + DevOps")
|
||||
- **领域主题**:从 description 提取 3-5 个有意义的科研/技术领域词
|
||||
- **现有成员技能画像**:成员数量和活跃度
|
||||
- **help-wanted 需求**:扫描 Issue 标题含 "help wanted" / "求助" / "good first issue" 的
|
||||
- **仓库定位**:工具类 / 库 / 应用 / 论文代码 / 数据集
|
||||
|
||||
### Step 2: 提取搜索关键词
|
||||
|
||||
AI 从源仓库画像中提取 3-5 个搜索关键词:
|
||||
- 互补语言/框架(如源用 Python,则搜索 C++/Rust 高性能库)
|
||||
- 相关领域词(如源做 NLP,则搜索 "text processing", "tokenizer", "embedding")
|
||||
- 不要用过于宽泛的词(如 "code", "test", "tool")
|
||||
|
||||
### Step 3: 搜索候选仓库
|
||||
|
||||
对每个关键词:
|
||||
```bash
|
||||
gitlink-cli search +repos --keyword "<kw>" --limit 5 --format json
|
||||
```
|
||||
|
||||
合并去重(按 full_name),排除源仓库自身。
|
||||
API 返回结构:`{ok, data: {projects: [...]}}`
|
||||
|
||||
如果候选太多(>15),AI 筛选最相关的 10 个进行深度分析。
|
||||
|
||||
### Step 4: 分析候选仓库
|
||||
|
||||
对每个候选仓库获取:
|
||||
```bash
|
||||
gitlink-cli repo +info --owner <co> --repo <cr> --format json
|
||||
gitlink-cli repo +members --owner <co> --repo <cr> --limit 20 --format json
|
||||
gitlink-cli issue +list --owner <co> --repo <cr> --state open --limit 30 --format json
|
||||
```
|
||||
|
||||
### Step 5: AI 匹配评估(核心)
|
||||
|
||||
对每个候选仓库,AI 从五个维度评估并给出 0-100 的匹配评分:
|
||||
|
||||
#### 1. 技能互补性(权重 30%)
|
||||
- 源仓库和候选仓库的技术栈不同 → 互补性高
|
||||
- 完全相同的技术栈 → 互补性低,但可能有协作深化机会
|
||||
- AI 需要判断:技术差异是有意义的互补还是无关
|
||||
|
||||
#### 2. 领域重叠度(权重 20%)
|
||||
- 两个仓库的领域主题有多少重叠
|
||||
- 完全无关的领域 → 低分;相同领域不同方法 → 高分
|
||||
|
||||
#### 3. help-wanted 匹配度(权重 20%)
|
||||
- 候选仓库有没有源方技能可以解决的 help-wanted issue
|
||||
- 不只是计数,要判断 Issue 内容是否匹配源方技能
|
||||
|
||||
#### 4. 生态桥接(权重 15%)
|
||||
- 两个仓库是否在同一个技术生态中
|
||||
- 比如都用 PyTorch、都做数据处理管线、都是 CLI 工具等
|
||||
|
||||
#### 5. 已有合作基础(权重 15%)
|
||||
- 是否有共享的贡献者
|
||||
- 是否相互引用(README 中的 URL 引用)
|
||||
|
||||
**匹配等级:**
|
||||
| 评分 | 等级 | 建议 |
|
||||
|------|------|------|
|
||||
| ≥ 70 | Strong | 强烈推荐,可主动联系 |
|
||||
| 50-69 | Good | 推荐关注 |
|
||||
| 30-49 | Possible | 可保持关注 |
|
||||
| < 30 | Weak | 不推荐 |
|
||||
|
||||
### Step 6: 生成推荐理由
|
||||
|
||||
**关键:每个推荐必须有具体的、有说服力的理由,不能是模板套话。**
|
||||
|
||||
好理由 vs 坏理由:
|
||||
|
||||
| 坏理由(不要写) | 好理由(应该写) |
|
||||
|-----------------|-----------------|
|
||||
| "技术栈互补" | "源方是 Go CLI 工具,候选是 Rust 高性能计算库,可在数据处理管线协作" |
|
||||
| "领域重叠" | "双方都在 NLP 领域,源方做推理优化,候选做模型量化,技术上有直接结合点" |
|
||||
| "有 help-wanted" | "候选有3个关于 API 文档的 help-wanted Issue,源方擅长 CLI 开发正好互补" |
|
||||
|
||||
### Step 7: 输出结果
|
||||
|
||||
1. **控制台摘要**:Top 5 匹配结果表格(排名、仓库、评分、等级、一句话理由)
|
||||
2. **JSON 文件**(可选):`output/collab-match-{date}.json`
|
||||
3. **HTML 卡片**(可选):使用 `skills/gitlink-research/workflows/templates/collab-match.html` 模板
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 推荐质量 > 数量,宁可只推荐 2 个优质匹配,不要堆砌 10 个低质量匹配
|
||||
- 如果候选仓库没有实质内容(空 description、0 成员、僵尸仓库),直接跳过
|
||||
- 匹配理由要用中文、短句、具体
|
||||
- 如果找不到高质量匹配,诚实告知用户"当前领域在 GitLink 上暂无可协作项目"
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# 场景 3:科研项目合规与复现性检查
|
||||
|
||||
## 目标
|
||||
|
||||
检查科研代码仓库的许可证合规性、信息安全风险,并评估项目的可复现性,生成评分卡报告。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 是 | 仓库所有者 |
|
||||
| `--repo` | 是 | 仓库名称 |
|
||||
| `--output` | 否 | 输出文件路径 |
|
||||
| `--local-path` | 否 | 本地仓库路径(用于 compliance 扫描,默认当前目录) |
|
||||
|
||||
## 数据收集步骤
|
||||
|
||||
### Step 1: 合规扫描
|
||||
需要先在本地克隆的仓库目录中运行:
|
||||
```bash
|
||||
cd $LOCAL_REPO_PATH
|
||||
gitlink-cli compliance +scan --format json
|
||||
```
|
||||
|
||||
五个模块:
|
||||
- **license**: 检测 LICENSE 文件是否存在、许可证类型
|
||||
- **deps**: 检查依赖是否与许可证兼容
|
||||
- **secrets**: 扫描硬编码密钥/Access Token
|
||||
- **exposure**: 检测 PII(邮箱、手机号)和内部 URL 暴露
|
||||
- **vocab**: 敏感词汇扫描
|
||||
|
||||
### Step 2: README 完整性
|
||||
```bash
|
||||
gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null
|
||||
```
|
||||
检测以下章节是否存在(每项 0/0.5/1.0):
|
||||
- 项目标题与描述
|
||||
- 安装说明(Install/安装/Setup)
|
||||
- 使用说明(Usage/使用/Quick Start)
|
||||
- 依赖声明(Requirements/依赖/Dependencies)
|
||||
- 许可证信息
|
||||
- 引用/致谢(Citation/Acknowledgement/引用)
|
||||
|
||||
### Step 3: 依赖声明检测
|
||||
```bash
|
||||
gitlink-cli api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" --format json
|
||||
```
|
||||
检测依赖文件存在性:
|
||||
- `package.json` (Node.js)
|
||||
- `go.mod` (Go)
|
||||
- `requirements.txt` / `pyproject.toml` / `Pipfile` (Python)
|
||||
- `Cargo.toml` (Rust)
|
||||
- `CMakeLists.txt` / `conanfile.txt` (C/C++)
|
||||
- `pom.xml` / `build.gradle` (Java)
|
||||
- `Gemfile` (Ruby)
|
||||
- `DESCRIPTION` (R)
|
||||
- `Project.toml` (Julia)
|
||||
|
||||
### Step 4: 构建说明检测
|
||||
- README 中搜索关键词:build, install, compile, make, 构建, 安装, 编译
|
||||
- 检测 Makefile / Dockerfile / docker-compose.yml 的存在
|
||||
- 检测 CI 配置文件(.github/workflows/, .gitlab-ci.yml, Jenkinsfile)
|
||||
|
||||
### Step 5: CI/CD 配置
|
||||
```bash
|
||||
gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 10 --format json
|
||||
```
|
||||
- builds 列表非空 = 有 CI 配置
|
||||
- 最近构建状态 = CI 是否通过
|
||||
|
||||
### Step 6: 测试证据
|
||||
- 检测 test/、tests/、spec/、__tests__/ 目录
|
||||
- README 中搜索 test、测试、validate
|
||||
- 检测测试框架文件(*_test.go, *_test.py, *.test.js, *.spec.ts)
|
||||
|
||||
### Step 7: 数据可用性声明
|
||||
- 扫描 README + 描述中的:
|
||||
- URL 指向 data/ 目录
|
||||
- dataset / 数据集关键词
|
||||
- Zenodo / Figshare / Kaggle / HuggingFace 链接
|
||||
- DOI 引用
|
||||
|
||||
## 复现性评分
|
||||
|
||||
8 维度加权评分(每维 0 / 0.5 / 1.0):
|
||||
|
||||
| 维度 | 权重 | 评分标准 |
|
||||
|------|------|---------|
|
||||
| 许可证 | 15% | 1: 有 OSI 合规许可证; 0.5: 有非标准许可证; 0: 无 |
|
||||
| 无密钥/PII | 15% | 1: 无发现; 0.5: 有低风险发现; 0: 发现密钥 |
|
||||
| README 完整 | 15% | 1: ≥5 个必需章节; 0.5: 3-4 个; 0: <3 个 |
|
||||
| 依赖声明 | 15% | 1: 有标准依赖文件; 0.5: README 中列出依赖; 0: 无 |
|
||||
| 构建说明 | 10% | 1: 详细步骤; 0.5: 简要提及; 0: 无 |
|
||||
| CI 配置 | 10% | 1: CI 存在且通过; 0.5: CI 存在但失败; 0: 无 CI |
|
||||
| 测试证据 | 10% | 1: 有测试目录+说明; 0.5: 其中之一; 0: 无 |
|
||||
| 数据可用性 | 10% | 1: 明确数据引用; 0.5: 隐含提及; 0: 无 |
|
||||
|
||||
总分 100:
|
||||
```
|
||||
Score = SUM(dimension_score_i * weight_i) * 100
|
||||
```
|
||||
|
||||
等级:
|
||||
- **A** (>=85):优秀,高度可复现
|
||||
- **B** (70-84):良好,基本可复现
|
||||
- **C** (55-69):一般,部分可复现
|
||||
- **D** (40-54):不足,复现困难
|
||||
- **F** (<40):差,几乎不可复现
|
||||
|
||||
## 输出
|
||||
|
||||
1. **HTML 评分卡** (`output/reproducibility-{repo}-{date}.html`):
|
||||
- 总体评分仪表盘
|
||||
- 8 维度雷达图
|
||||
- 各维度明细表(评分 + 证据 + 改进建议)
|
||||
- 合规风险汇总
|
||||
|
||||
2. **Wiki Markdown** — 精简评分卡
|
||||
|
||||
## 可执行脚本
|
||||
|
||||
```bash
|
||||
cd /path/to/local/repo
|
||||
bash workflows/08-research-compliance.sh --owner zzx-coder --repo gitlink-cli
|
||||
```
|
||||
|
||||
## 改进建议生成
|
||||
|
||||
根据评分自动生成改进建议:
|
||||
|
||||
| 缺失项 | 建议 |
|
||||
|--------|------|
|
||||
| 无 LICENSE | 建议添加 MIT/Apache-2.0/GPL-3.0 许可证 |
|
||||
| 无 README | 建议添加 README 说明项目目的、安装和使用 |
|
||||
| 无依赖文件 | 建议添加 package.json/go.mod/requirements.txt |
|
||||
| 无 CI | 建议配置 .github/workflows 或 GitLink CI |
|
||||
| 无测试 | 建议添加 unit test 和 smoke test |
|
||||
| 无数据声明 | 建议说明数据集来源或生成方法 |
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
# 场景 2:科研热点追踪与知识图谱构建
|
||||
|
||||
> **执行方式:AI Agent 直接执行**(无脚本,需语义理解和关系推断)
|
||||
|
||||
## 目标
|
||||
|
||||
按关键词搜索 GitLink 上的科研仓库,AI 分析仓库内容后构建领域知识图谱,识别热点趋势,生成 JSON + HTML 力导向图可视化。
|
||||
|
||||
## 参数获取
|
||||
|
||||
向用户询问:
|
||||
1. **关键词**(必需):逗号分隔,如 "LLM,RAG,Agent"
|
||||
2. **每个关键词搜索数**(可选,默认 5,最多 10)
|
||||
3. **是否限制组织**(可选)
|
||||
|
||||
## Agent 执行步骤
|
||||
|
||||
### Step 1: 搜索仓库
|
||||
|
||||
对每个关键词调用:
|
||||
|
||||
```bash
|
||||
gitlink-cli search +repos --keyword "<keyword>" --limit <N> --format json
|
||||
```
|
||||
|
||||
合并所有结果,按 `full_name` 去重。同一仓库可能被多个关键词命中(说明领域关联强)。
|
||||
|
||||
API 返回结构:`{ok, data: {projects: [...]}}`
|
||||
|
||||
### Step 2: 获取每个仓库的深度数据
|
||||
|
||||
对每个仓库(上限 20 个)获取:
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +info --owner <o> --repo <r> --format json
|
||||
gitlink-cli release +list --owner <o> --repo <r> --limit 3 --format json
|
||||
gitlink-cli repo +members --owner <o> --repo <r> --limit 20 --format json
|
||||
```
|
||||
|
||||
### Step 3: AI 语义分析(核心)
|
||||
|
||||
**不要机械匹配,需要 AI 理解:**
|
||||
|
||||
1. **领域主题提取**:阅读每个仓库的 description,从中提取真正的科研领域关键词(不是文件后缀或框架名),去重去噪
|
||||
2. **仓库类型判断**:是论文复现代码 / 工具库 / 数据集 / 实验脚本 / 教学材料?
|
||||
3. **趋势方向**:比较各仓库的最近更新时间、Issue 活跃度,判断 rising / stable / declining
|
||||
4. **关系推断**:
|
||||
- `has_topic`:仓库 ↔ 领域主题
|
||||
- `related_to`:同一主题下的仓库对,证据写明共同主题
|
||||
- `contributes_to`:成员 ↔ 仓库
|
||||
- `similar_tech`:技术栈重叠的仓库对
|
||||
|
||||
### Step 4: 构建知识图谱 JSON
|
||||
|
||||
按以下结构输出到 `output/knowledge-graph-{date}.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"generated_at": "<ISO时间>",
|
||||
"search_keywords": ["关键词列表"],
|
||||
"total_repos_scanned": <数字>,
|
||||
"total_contributors_found": <数字>,
|
||||
"total_edges_inferred": <数字>
|
||||
},
|
||||
"nodes": [
|
||||
{"id": "topic:<keyword>", "type": "topic", "label": "<关键词>", "category": 2, "symbolSize": 30},
|
||||
{"id": "repo:<owner>/<repo>", "type": "repo", "label": "<repo名>",
|
||||
"desc": "<描述截断100字>", "stars": <数字>, "language": "<语言>",
|
||||
"hotness": <评分>, "trend": "rising|stable|declining",
|
||||
"category": 0, "symbolSize": <15+hotness*0.5>},
|
||||
{"id": "contributor:<login>", "type": "contributor", "label": "<login>",
|
||||
"category": 1, "symbolSize": 20}
|
||||
],
|
||||
"edges": [
|
||||
{"source": "repo:...", "target": "topic:...", "type": "has_topic", "weight": 1.0, "evidence": "关键词匹配"},
|
||||
{"source": "repo:...", "target": "repo:...", "type": "related_to", "weight": 0.5, "evidence": "共同主题: LLM"},
|
||||
{"source": "contributor:...", "target": "repo:...", "type": "contributes_to", "weight": 0.8}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: 生成 HTML 可视化
|
||||
|
||||
使用 `workflows/templates/knowledge-graph.html` 模板,替换以下占位符:
|
||||
|
||||
| 占位符 | 内容 |
|
||||
|--------|------|
|
||||
| `{{REPORT_DATE}}` | 当前日期 |
|
||||
| `{{KEYWORDS}}` | 搜索关键词串 |
|
||||
| `{{TOTAL_REPOS}}` | 仓库节点数 |
|
||||
| `{{TOTAL_CONTRIBUTORS}}` | 贡献者节点数 |
|
||||
| `{{TOTAL_TOPICS}}` | 主题节点数 |
|
||||
| `{{TOTAL_EDGES}}` | 关系边总数 |
|
||||
| `{{GRAPH_NODES}}` | nodes JSON(紧凑格式) |
|
||||
| `{{GRAPH_EDGES}}` | edges JSON(紧凑格式) |
|
||||
| `{{TABLE_ROWS}}` | 热度排行 HTML `<tr>` 行 |
|
||||
| `{{HOTTEST_REPO}}` | 热度最高的仓库名 |
|
||||
|
||||
模板路径:`skills/gitlink-research/workflows/templates/knowledge-graph.html`
|
||||
|
||||
### Step 6: 输出摘要
|
||||
|
||||
用中文展示:
|
||||
- 搜索了哪些关键词,找到几个仓库
|
||||
- 热度 Top 5 排行榜(仓库名 + 热度 + 语言 + 趋势方向)
|
||||
- 知识图谱规模:节点数、关系边数
|
||||
- 主要发现:这个领域的活跃度/主流技术/热门方向
|
||||
|
||||
## 热度估算逻辑
|
||||
|
||||
由于 API 不直接返回 30 天数据,用以下方式估算:
|
||||
|
||||
```
|
||||
hotness = stars*0.15 + forks*0.10 + open_issues*0.20 + member_count*0.20
|
||||
+ releases*0.15 + (recency_30d?100:recency_90d?50:10)*0.20
|
||||
```
|
||||
|
||||
趋势:最近 30 天更新 → rising,30-90 天 → stable,>90 天 → declining
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 温度由 AI 判断,不要机械套公式
|
||||
- 边标签(evidence)要有意义,不能只是"共同主题"这种空泛表述
|
||||
- 主题列表控制在 10 个以内,质量胜于数量
|
||||
- 如果模板文件不存在,直接用内联 HTML 生成
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
# 场景 5:科研进度智能跟踪与预警
|
||||
|
||||
## 目标
|
||||
|
||||
跟踪科研项目的开发进度,检测异常信号,生成周报和早期预警。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 是 | 仓库所有者 |
|
||||
| `--repo` | 是 | 仓库名称 |
|
||||
| `--org` | 否 | 组织名称(多仓库模式) |
|
||||
| `--weeks` | 否 | 回溯周数(默认 4) |
|
||||
| `--output` | 否 | 输出文件路径 |
|
||||
|
||||
## 数据收集步骤
|
||||
|
||||
### Step 1: Issue 数据
|
||||
```bash
|
||||
gitlink-cli issue +list --owner $OWNER --repo $REPO --state open --limit 100 --format json
|
||||
gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --limit 100 --format json
|
||||
```
|
||||
提取:
|
||||
- 每个 Issue 的状态、创建时间、更新时间、标签
|
||||
- 按周统计创建数/关闭数
|
||||
- 识别停滞 Issue(open + 60天无更新)
|
||||
- 识别未分配 Issue
|
||||
|
||||
### Step 2: PR 数据
|
||||
```bash
|
||||
gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --limit 100 --format json
|
||||
gitlink-cli pr +list --owner $OWNER --repo $REPO --state open --limit 50 --format json
|
||||
```
|
||||
提取:
|
||||
- 按周统计合并数/新建数
|
||||
- 开放 PR 的平均年龄
|
||||
- PR 瓶颈检测:开放 > 5 且平均年龄 > 14 天
|
||||
|
||||
### Step 3: Release 数据
|
||||
```bash
|
||||
gitlink-cli release +list --owner $OWNER --repo $REPO --limit 20 --format json
|
||||
```
|
||||
- 计算发布间隔
|
||||
- 检测长期无发布(> 180 天)
|
||||
|
||||
### Step 4: CI 数据
|
||||
```bash
|
||||
gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 20 --format json
|
||||
```
|
||||
- 构建成功率
|
||||
- 最近失败次数
|
||||
|
||||
### Step 5: 多仓库模式(可选)
|
||||
如果指定 `--org`:
|
||||
```bash
|
||||
gitlink-cli repo +list --user $ORG --limit 50 --format json
|
||||
```
|
||||
对每个仓库执行 Step 1-4,生成聚合报告。
|
||||
|
||||
## 健康评分
|
||||
|
||||
```
|
||||
Health = issue_velocity * 0.30
|
||||
+ pr_merge_rate * 0.25
|
||||
+ milestone_ok * 0.25
|
||||
+ release_cadence * 0.10
|
||||
+ activity_trend * 0.10
|
||||
```
|
||||
|
||||
### issue_velocity
|
||||
```
|
||||
velocity = issues_closed_28d / 28
|
||||
score = min(velocity / 1.0, 1.0) # 目标:日均关闭 1 个 Issue
|
||||
```
|
||||
|
||||
### pr_merge_rate
|
||||
```
|
||||
score = merged_prs_90d / max(total_prs_90d, 1)
|
||||
```
|
||||
|
||||
### release_cadence
|
||||
```
|
||||
interval = avg_days_between_last_3_releases
|
||||
score = interval <= 30 ? 1.0 : interval <= 90 ? 0.5 : interval <= 180 ? 0.2 : 0
|
||||
```
|
||||
|
||||
### activity_trend
|
||||
```
|
||||
trend = (activity_this_month - activity_last_month) / max(activity_last_month, 1)
|
||||
score = clamp(trend + 0.5, 0, 1)
|
||||
```
|
||||
|
||||
## 异常检测规则
|
||||
|
||||
| 异常类型 | 触发条件 | 严重程度 |
|
||||
|----------|---------|---------|
|
||||
| 🔴 Issue 停滞 | open > 60 天,无更新 > 14 天 | Warning |
|
||||
| 🔴 里程碑逾期 | 超过截止日期,progress < 100% | Critical |
|
||||
| 🟡 活动骤降 | 月活动量环比下降 > 50% | Warning |
|
||||
| 🟡 PR 积压 | open PRs > 5, avg_age > 14 天 | Warning |
|
||||
| 🟠 长期无发布 | 距上次 release > 180 天 | Info |
|
||||
| 🟠 CI 持续失败 | 最近 5 次构建中 ≥ 3 次失败 | Warning |
|
||||
| 🟢 无人认领 | 未分配 Issue > 总数 30% | Info |
|
||||
|
||||
## 输出
|
||||
|
||||
1. **HTML 周报** (`output/progress-weekly-{date}.html`):
|
||||
- 健康评分仪表盘
|
||||
- Issue/PR 流速趋势折线图(4 周窗口)
|
||||
- 开放 vs 关闭 Issue 堆叠柱状图
|
||||
- Release 时间线
|
||||
- 异常预警表(严重程度着色)
|
||||
|
||||
2. **Wiki Markdown** — 周报摘要 + 异常列表
|
||||
|
||||
3. **控制台摘要** — 关键指标一目了然
|
||||
|
||||
## 可执行脚本
|
||||
|
||||
```bash
|
||||
# 单仓库
|
||||
bash workflows/10-research-progress.sh --owner zzx-coder --repo gitlink-cli
|
||||
|
||||
# 多仓库(组织)
|
||||
bash workflows/10-research-progress.sh --org zzx-coder --weeks 4
|
||||
```
|
||||
|
||||
## 风险管理建议
|
||||
|
||||
对于检测到的异常,自动生成建议:
|
||||
|
||||
| 异常 | 建议 |
|
||||
|------|------|
|
||||
| Issue 停滞 | 重新评估优先级,关闭或推进;通知负责人 |
|
||||
| 里程碑逾期 | 重新规划里程碑时间表;拆分为更小的子任务 |
|
||||
| 活动骤降 | 检查团队是否有阻塞因素;组织一次同步会议 |
|
||||
| PR 积压 | 安排 Code Review 时间;简化 PR 粒度 |
|
||||
| 长期无发布 | 考虑发布当前 master 的最小可用版本 |
|
||||
| CI 持续失败 | 优先修复 CI;暂时阻止新 PR 合并直到 CI 恢复 |
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
# 场景 1:仓库级科研项目洞察
|
||||
|
||||
## 目标
|
||||
|
||||
对单个 GitLink 仓库进行深度分析,生成综合项目画像报告,包括项目定位、技术栈、活动健康、贡献者网络和热度评分。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 是 | 仓库所有者 |
|
||||
| `--repo` | 是 | 仓库名称 |
|
||||
| `--output` | 否 | 输出文件路径 |
|
||||
|
||||
## 数据收集步骤
|
||||
|
||||
### Step 1: 仓库元数据
|
||||
```bash
|
||||
gitlink-cli repo +info --owner $OWNER --repo $REPO --format json
|
||||
```
|
||||
提取:name, description, language, stars_count, forks_count, open_issues_count, updated_at, created_at, owner 信息
|
||||
|
||||
### Step 2: 技术栈检测
|
||||
```bash
|
||||
gitlink-cli api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" --format json
|
||||
```
|
||||
扫描根目录文件,匹配技术生态文件:
|
||||
- `go.mod` → Go
|
||||
- `package.json` → Node.js
|
||||
- `requirements.txt` / `pyproject.toml` / `setup.py` / `setup.cfg` → Python
|
||||
- `Cargo.toml` → Rust
|
||||
- `CMakeLists.txt` / `Makefile` → C/C++
|
||||
- `pom.xml` / `build.gradle` / `build.gradle.kts` → Java/Kotlin
|
||||
- `Gemfile` → Ruby
|
||||
- `CITATION.cff` → 有引文文件(科研加分项)
|
||||
|
||||
同时统计文件扩展名分布(.py, .js, .go, .rs, .java, .r, .ipynb 等)
|
||||
|
||||
### Step 3: 项目定位提取
|
||||
```bash
|
||||
gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null
|
||||
```
|
||||
- 截取 README 前 2000 字符
|
||||
- 提取一级/二级标题作为结构摘要
|
||||
- 扫描关键词:research, paper, experiment, dataset, model, benchmark, 研究, 实验, 数据, 模型
|
||||
- 扫描 DOI 链接:`10.\d{4,}/[\w.\-/]+`
|
||||
|
||||
### Step 4: 活动健康指标
|
||||
```bash
|
||||
# Issue 数据
|
||||
gitlink-cli issue +list --owner $OWNER --repo $REPO --state open --limit 100 --format json
|
||||
gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --limit 100 --format json
|
||||
|
||||
# PR 数据
|
||||
gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --limit 100 --format json
|
||||
gitlink-cli pr +list --owner $OWNER --repo $REPO --state open --limit 50 --format json
|
||||
|
||||
# Release 数据
|
||||
gitlink-cli release +list --owner $OWNER --repo $REPO --limit 20 --format json
|
||||
|
||||
# CI 数据
|
||||
gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 20 --format json
|
||||
```
|
||||
|
||||
指标计算:
|
||||
- Issue 流速:最近 30/90 天创建和关闭的 Issue 数
|
||||
- PR 合并率:merged / (merged + closed)
|
||||
- 发布频率:最近 3 次发布的间隔天数
|
||||
- CI 通过率:成功构建数 / 总构建数
|
||||
- 平均 Issue 响应时间(估算)
|
||||
|
||||
### Step 5: 贡献者网络
|
||||
```bash
|
||||
gitlink-cli repo +members --owner $OWNER --repo $REPO --limit 50 --format json
|
||||
```
|
||||
- 提取所有贡献者列表
|
||||
- 从 Issue/PR 数据中提取贡献者共现关系
|
||||
- 构建合作邻接矩阵(两个贡献者参与同一 Issue/PR 则建立边)
|
||||
|
||||
## 热度评分公式
|
||||
|
||||
```
|
||||
Hotness = stars_norm * 0.15
|
||||
+ forks_norm * 0.10
|
||||
+ issues_30d * 0.20
|
||||
+ prs_30d * 0.20
|
||||
+ releases_90d * 0.15
|
||||
+ commits_30d * 0.10
|
||||
+ recency * 0.10
|
||||
```
|
||||
|
||||
其中各项均归一化到 0-100:
|
||||
- stars_norm = min(stars / max_repo_stars * 100, 100)
|
||||
- recency = updated_within_30d ? 100 : (updated_within_90d ? 50 : 10)
|
||||
|
||||
热度等级:Hot (>=50) / Warm (30-49) / Cool (<30)
|
||||
|
||||
## 输出
|
||||
|
||||
1. **HTML 报告** (`output/research-insights-{repo}-{date}.html`):
|
||||
- 摘要卡片(名称、语言、星标、Fork、热度评分)
|
||||
- 技术栈饼图
|
||||
- 活动时间线折线图
|
||||
- 贡献者网络力导向图
|
||||
- 健康评分仪表盘
|
||||
|
||||
2. **Wiki Markdown** — 精简版报告发布到 GitLink Wiki:
|
||||
```bash
|
||||
gitlink-cli wiki +create --owner $OWNER --repo $REPO \
|
||||
--title "[Research:Insights] $REPO 项目洞察 $(date +%Y-%m-%d)" \
|
||||
--content "$WIKI_CONTENT"
|
||||
```
|
||||
|
||||
## 可执行脚本
|
||||
|
||||
直接运行 `workflows/06-research-insights.sh`:
|
||||
```bash
|
||||
bash workflows/06-research-insights.sh --owner zzx-coder --repo gitlink-cli
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景 1 补充:科研项目画像维度
|
||||
|
||||
除了基础的项目洞察外,针对科研项目增加以下分析维度:
|
||||
|
||||
### 科研特征识别
|
||||
|
||||
从 README、描述和代码中识别科研特征:
|
||||
|
||||
| 特征 | 检测方式 |
|
||||
|------|---------|
|
||||
| 引用论文 | DOI 模式 `10.\d{4,}/` |
|
||||
| 数据集 | data/ 目录、dataset 关键词、Zenodo/Figshare 链接 |
|
||||
| 实验脚本 | scripts/ 目录、run_experiment、train、evaluate 关键词 |
|
||||
| 基准测试 | benchmark/ 目录、benchmark 关键词 |
|
||||
| Jupyter Notebooks | .ipynb 文件存在 |
|
||||
| 预训练模型 | .pt/.pth/.h5/.onnx 文件或 model/checkpoint 目录 |
|
||||
| Docker | Dockerfile 或 docker-compose.yml |
|
||||
| 结果可视化 | 图片目录、图表关键词 |
|
||||
|
||||
### 科研影响力量化
|
||||
|
||||
```
|
||||
Research Impact = 0.30 * citation_count_estimate
|
||||
+ 0.25 * dataset_availability
|
||||
+ 0.20 * paper_links
|
||||
+ 0.15 * fork_productivity(fork 后是否产生新研究)
|
||||
+ 0.10 * cross_repo_references
|
||||
```
|
||||
|
|
@ -1,177 +1,100 @@
|
|||
---
|
||||
name: gitlink-workflow
|
||||
version: 2.0.0
|
||||
description: "AI 自动化工作流:代码质量审查(PR Review)。当用户需要 AI 审查 PR 代码质量时触发。"
|
||||
description: "AI 自动化工作流:社区运营、代码审查、项目初始化、多仓库协同、贡献者成长。当用户需要 AI 自动化 GitLink 操作时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
triggers:
|
||||
- "工作流"
|
||||
- "workflow"
|
||||
- "自动化"
|
||||
- "帮我跑"
|
||||
- "执行"
|
||||
---
|
||||
|
||||
# gitlink-workflow(AI 代码质量看门人)
|
||||
# gitlink-workflow(AI 自动化工作流)
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)
|
||||
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
|
||||
## 工作流:代码质量看门人(Code Quality Gatekeeper)
|
||||
本技能提供 Claude Code 可直接执行的高级工作流模板。
|
||||
|
||||
**触发词**:代码审查、PR review、质量检查、gatekeeper、审查 PR
|
||||
|
||||
**参数**:
|
||||
- `--owner`:仓库所属组织或用户
|
||||
- `--repo`:仓库名称
|
||||
- `--pr-id`:指定 PR ID(可选,默认审查所有 open PR)
|
||||
- `--threshold`:质量阈值(默认 80)
|
||||
|
||||
### 流程总览
|
||||
## 功能菜单
|
||||
|
||||
```
|
||||
PR 提交 → 获取详情 → 获取 Diff → AI 四维度评分 → 发布审查评论 → 检查 CI → 自动合并
|
||||
====== GitLink 自动化工作流 ======
|
||||
|
||||
请选择要执行的工作流:
|
||||
|
||||
1. 社区运营自动化
|
||||
→ Issue 自动分类、负责人分配、周报生成、Release Notes
|
||||
|
||||
2. 代码质量审查
|
||||
→ PR Review、AI 四维度评分、自动合并
|
||||
|
||||
3. 项目一键初始化
|
||||
→ 创建仓库、README、CI 配置、初始 Issues、分支保护
|
||||
|
||||
4. 多仓库协同
|
||||
→ 跨仓库 Issue/PR 追踪、状态 Dashboard、协同发版
|
||||
|
||||
5. 贡献者成长体系
|
||||
→ 数据收集、AHP 评分、排行榜、Wiki 发布、Badge 颁发
|
||||
|
||||
请输入编号(1-5)或功能名称:
|
||||
```
|
||||
|
||||
### 审查单个 PR 的步骤
|
||||
## 用户输入 → 工作流映射
|
||||
|
||||
**步骤 1** — 获取 PR 详情:
|
||||
```bash
|
||||
gitlink-cli pr +view --owner {OWNER} --repo {REPO} --id {PR_ID} --format json
|
||||
```
|
||||
提取字段:
|
||||
- title: `.data.title // .data.subject // .data.issue.subject`
|
||||
- author: `.data.author.login // .data.author.username`
|
||||
- state: `.data.state // .data.status`
|
||||
| 用户输入 | 执行的工作流 | Reference 文件 |
|
||||
|----------|-------------|---------------|
|
||||
| `1` 或 "社区运营" | 社区运营自动化 | [`workflow-community-ops.md`](references/workflow-community-ops.md) |
|
||||
| `2` 或 "代码审查" | 代码质量审查 | [`workflow-pr-review.md`](references/workflow-pr-review.md) |
|
||||
| `3` 或 "项目初始化" | 项目一键初始化 | [`workflow-repo-setup.md`](references/workflow-repo-setup.md) |
|
||||
| `4` 或 "多仓库" | 多仓库协同 | [`workflow-multi-repo.md`](references/workflow-multi-repo.md) |
|
||||
| `5` 或 "贡献者" | 贡献者成长体系 | [`workflow-contributor-growth.md`](references/workflow-contributor-growth.md) |
|
||||
|
||||
**步骤 2** — 获取变更文件列表:
|
||||
```bash
|
||||
gitlink-cli pr +files --owner {OWNER} --repo {REPO} --id {PR_ID} --format json
|
||||
```
|
||||
文件列表路径:`.data.files[]`,字段:`.name`(或 `.filename`)、`.additions`、`.deletions`
|
||||
## 执行流程
|
||||
|
||||
**步骤 3** — 获取代码差异:
|
||||
```bash
|
||||
gitlink-cli pr +diff --owner {OWNER} --repo {REPO} --id {PR_ID} --format json
|
||||
```
|
||||
提取 diff:`.data.files[].sections[].lines[].content`,截取前 5000 字符
|
||||
1. **展示菜单** — 列出所有可用工作流
|
||||
2. **获取用户选择** — 用户输入编号或功能名称
|
||||
3. **读取 Reference** — 根据选择读取对应的 reference 文件
|
||||
4. **确认参数** — 询问必要的参数(owner/repo 等)
|
||||
5. **执行工作流** — 按照 reference 文件的步骤执行
|
||||
6. **展示结果** — 输出执行结果和摘要
|
||||
|
||||
**步骤 4** — AI 四维度代码审查(总分 100):
|
||||
## 快捷触发
|
||||
|
||||
| 维度 | 满分 | 检查项 |
|
||||
|------|------|--------|
|
||||
| 代码质量 | 25 | 复杂度、命名规范、注释、格式一致性 |
|
||||
| 安全性 | 25 | SQL注入、XSS、硬编码凭证、认证绕过、输入验证 |
|
||||
| 性能 | 25 | 循环效率、资源泄漏、N+1查询、内存占用、阻塞调用 |
|
||||
| 可维护性 | 25 | 代码重复、职责单一、依赖耦合、测试覆盖 |
|
||||
用户也可以直接说特定意图,跳过菜单直接执行:
|
||||
|
||||
评分标准:
|
||||
- 90-100:优秀,可直接合并
|
||||
- 75-89:良好,建议合并
|
||||
- 60-74:一般,需要改进
|
||||
- <60:较差,不建议合并
|
||||
| 用户说的话 | 直接执行 |
|
||||
|------------|----------|
|
||||
| "帮我跑一下社区运营" | 社区运营自动化 |
|
||||
| "审查一下这个 PR" | 代码质量审查 |
|
||||
| "创建一个新项目" | 项目一键初始化 |
|
||||
| "看看组织下所有仓库" | 多仓库协同 |
|
||||
| "生成贡献者排行榜" | 贡献者成长体系 |
|
||||
|
||||
问题严重级别:
|
||||
- CRITICAL:阻止合并(安全漏洞、数据丢失风险)
|
||||
- HIGH:强烈建议修复(性能问题、逻辑错误)
|
||||
- MEDIUM:建议修复(代码质量、可维护性)
|
||||
- LOW:可选修复(风格、命名)
|
||||
## 参数说明
|
||||
|
||||
**输出要求**:AI 必须输出以下 JSON 结构:
|
||||
```json
|
||||
{
|
||||
"total": 85,
|
||||
"quality": 22,
|
||||
"security": 25,
|
||||
"performance": 20,
|
||||
"maintainability": 18,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "MEDIUM",
|
||||
"category": "quality",
|
||||
"file": "src/main.go",
|
||||
"rule": "naming",
|
||||
"description": "变量名过于简短",
|
||||
"suggestion": "使用更具描述性的变量名"
|
||||
}
|
||||
],
|
||||
"positive_notes": [
|
||||
{"description": "错误处理完善"}
|
||||
],
|
||||
"recommendations": [
|
||||
"建议添加单元测试"
|
||||
],
|
||||
"verdict": "PASS"
|
||||
}
|
||||
```
|
||||
| 参数 | 说明 | 获取方式 |
|
||||
|------|------|----------|
|
||||
| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--org` | 组织名称 | 询问用户(多仓库协同时需要) |
|
||||
|
||||
**AI 不可用时的降级方案**(关键词检测):
|
||||
- 检测到 password/secret/token/api_key/private_key → security -15
|
||||
- 检测到 eval()/exec()/system()/os.system → security -10
|
||||
- 检测到 TODO/FIXME/HACK/XXX → quality -5
|
||||
- 检测到 SELECT */findAll()/.all() → performance -10
|
||||
- 检测到 sleep()/Thread.sleep → performance -5
|
||||
- 变更文件 > 20 个 → maintainability -10
|
||||
## 最佳实践
|
||||
|
||||
**步骤 5** — 组装审查评论 Markdown:
|
||||
- 所有工作流命令使用 `--format json` 以便解析输出
|
||||
- 写入操作前确认用户意图
|
||||
- 批量操作建议先用小范围测试
|
||||
- 保存工作流执行结果以便回溯
|
||||
- 自动从 git remote 解析 owner/repo,解析失败时询问用户
|
||||
- 支持 `--dry-run` 预览模式(部分工作流)
|
||||
|
||||
```markdown
|
||||
## AI Code Quality Review - PR #{PR_ID}
|
||||
## References
|
||||
|
||||
### Scores
|
||||
| Dimension | Score | Max |
|
||||
|-----------|-------|-----|
|
||||
| Code Quality | {QUALITY} | 25 |
|
||||
| Security | {SECURITY} | 25 |
|
||||
| Performance | {PERFORMANCE} | 25 |
|
||||
| Maintainability | {MAINTAINABILITY} | 25 |
|
||||
| **Total** | **{TOTAL}** | **100** |
|
||||
|
||||
### Issues Found
|
||||
- [{SEVERITY}] {CATEGORY}: {DESCRIPTION} ({FILE}) → {SUGGESTION}
|
||||
|
||||
### Positive Notes
|
||||
- {NOTE}
|
||||
|
||||
### Recommendations
|
||||
- {REC}
|
||||
|
||||
### Verdict
|
||||
{PASS/FAIL} - Score {TOTAL} {>=/<} threshold {THRESHOLD}
|
||||
|
||||
---
|
||||
*Auto-reviewed by gitlink-cli code-quality-gatekeeper workflow*
|
||||
```
|
||||
|
||||
**步骤 6** — 发布审查评论:
|
||||
```bash
|
||||
gitlink-cli api POST /{OWNER}/{REPO}/pulls/{PR_ID}/reviews --body '{"body":"{REVIEW_MD}","event":"{EVENT}"}'
|
||||
```
|
||||
- 分数 >= 阈值 → event = "APPROVE"
|
||||
- 分数 < 阈值 → event = "COMMENT"
|
||||
|
||||
**步骤 7** — 检查 CI 构建状态:
|
||||
```bash
|
||||
gitlink-cli ci +builds --owner {OWNER} --repo {REPO} --format json
|
||||
```
|
||||
遍历 `.data.builds[]` 或 `.data[]`,检查 status 是否为 success/passed/completed
|
||||
|
||||
**步骤 8** — 自动合并(条件:分数 >= 阈值 且 CI 全部通过):
|
||||
```bash
|
||||
gitlink-cli pr +merge --owner {OWNER} --repo {REPO} --id {PR_ID} --method merge
|
||||
```
|
||||
|
||||
### 审查所有 open PR
|
||||
|
||||
如果用户没指定 `--pr-id`,先获取列表再逐个审查:
|
||||
```bash
|
||||
gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state open --limit 50 --format json
|
||||
```
|
||||
PR ID 提取:`.data.issues[]` 或 `.data.pulls[]` 或 `.data[]` 中的 `.pull_request_number // .number // .id`
|
||||
|
||||
## 其他自动化工作流
|
||||
|
||||
以下工作流不需要 AI,已实现为 PowerShell 脚本(`workflows/` 目录):
|
||||
|
||||
| 工作流 | 脚本 | 用法 |
|
||||
|--------|------|------|
|
||||
| 社区运营自动化 | `01-community-ops.ps1` | `pwsh workflows/01-community-ops.ps1 -Owner zzx-coder -Repo gitlink-cli` |
|
||||
| 项目一键初始化 | `03-project-init.ps1` | `pwsh workflows/03-project-init.ps1 -Owner org -Name my-app -Desc "描述" -Lang go` |
|
||||
| 多仓库协同 | `04-multi-repo-collab.ps1` | `pwsh workflows/04-multi-repo-collab.ps1 -Org myorg` |
|
||||
| 贡献者成长体系 | `05-contributor-growth.ps1` | `pwsh workflows/05-contributor-growth.ps1 -Owner zzx-coder -Repo gitlink-cli` |
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
- [gitlink-workflows](../gitlink-workflows/SKILL.md) — 工作流总入口(含可执行脚本)
|
||||
|
|
|
|||
|
|
@ -1,579 +0,0 @@
|
|||
# AI Agent 自动化工作流完整示例
|
||||
|
||||
本文档展示如何组合使用多个 gitlink-cli 工作流,实现完整的 AI Agent 自动化项目管理。
|
||||
|
||||
## 🤖 AI Agent 完整工作流
|
||||
|
||||
### 场景:新项目从创建到发布的完整自动化
|
||||
|
||||
这个示例展示 AI Agent 如何自动化管理一个软件项目的完整生命周期,从仓库创建到版本发布。
|
||||
|
||||
## 工作流组合
|
||||
|
||||
### 1. 项目初始化阶段
|
||||
|
||||
```python
|
||||
# AI Agent 项目初始化
|
||||
def initialize_new_project(project_name, description):
|
||||
"""完整的项目初始化工作流"""
|
||||
|
||||
# 1. 创建仓库
|
||||
repo = create_repository(project_name, description)
|
||||
|
||||
# 2. 初始化项目结构
|
||||
setup_project_structure(repo)
|
||||
|
||||
# 3. 配置 CI/CD
|
||||
configure_ci_cd(repo)
|
||||
|
||||
# 4. 创建初始 Issue
|
||||
create_initial_issues(repo)
|
||||
|
||||
return repo
|
||||
|
||||
# 执行
|
||||
project = initialize_new_project(
|
||||
"my-awesome-project",
|
||||
"一个很棒的项目,用于演示自动化工作流"
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 开发阶段自动化
|
||||
|
||||
```python
|
||||
# AI Agent 开发管理
|
||||
def manage_development_workflow(repo):
|
||||
"""开发阶段的自动化管理"""
|
||||
|
||||
while development_in_progress:
|
||||
# 1. 自动分类新 Issue
|
||||
triage_new_issues(repo)
|
||||
|
||||
# 2. 审查新的 PR
|
||||
review_pull_requests(repo)
|
||||
|
||||
# 3. 更新项目进度
|
||||
update_project_status(repo)
|
||||
|
||||
# 4. 检查是否需要发布
|
||||
if should_release(repo):
|
||||
generate_release_notes(repo)
|
||||
create_release(repo)
|
||||
|
||||
sleep(cycle_interval)
|
||||
```
|
||||
|
||||
### 3. Sprint 自动化
|
||||
|
||||
```python
|
||||
# AI Agent Sprint 管理
|
||||
def automate_sprint_management(repo):
|
||||
"""完整的 Sprint 自动化管理"""
|
||||
|
||||
# Sprint 开始
|
||||
sprint_number = start_new_sprint(repo)
|
||||
|
||||
# Sprint 监控
|
||||
monitor_sprint_progress(repo, sprint_number)
|
||||
|
||||
# Sprint 结束
|
||||
end_sprint(repo, sprint_number)
|
||||
generate_sprint_report(repo, sprint_number)
|
||||
|
||||
# 执行 Sprint 工作流
|
||||
sprint_result = automate_sprint_management(project)
|
||||
```
|
||||
|
||||
## 完整的端到端示例
|
||||
|
||||
### 示例:自动化 Issue 到 Release 流程
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# 完整的自动化工作流脚本
|
||||
|
||||
OWNER="ai-agent"
|
||||
REPO="demo-project"
|
||||
PROJECT_NAME="AI Agent Demo"
|
||||
|
||||
echo "🤖 启动 AI Agent 自动化工作流..."
|
||||
|
||||
# 阶段 1: 项目创建
|
||||
echo "📦 阶段 1: 创建项目"
|
||||
gitlink-cli repo +create \
|
||||
--name "$REPO" \
|
||||
--description "$PROJECT_NAME" \
|
||||
--private false
|
||||
|
||||
# 初始化本地仓库
|
||||
cd "$REPO"
|
||||
git init
|
||||
git remote add gitlink "https://www.gitlink.org.cn/$OWNER/$REPO.git"
|
||||
|
||||
# 创建基础文件
|
||||
echo "# $PROJECT_NAME" > README.md
|
||||
echo "MIT License" > LICENSE
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git push -u gitlink master:master
|
||||
|
||||
# 设置分支保护
|
||||
gitlink-cli branch +protect --owner "$OWNER" --repo "$REPO" --name master
|
||||
|
||||
# 阶段 2: 创建开发 Issue
|
||||
echo "🎯 阶段 2: 创建开发 Issue"
|
||||
FEATURES=(
|
||||
"用户认证系统"
|
||||
"数据管理模块"
|
||||
"API 接口开发"
|
||||
"前端界面设计"
|
||||
"测试框架搭建"
|
||||
)
|
||||
|
||||
for feature in "${FEATURES[@]}"; do
|
||||
gitlink-cli issue +create \
|
||||
--owner "$OWNER" \
|
||||
--repo "$REPO" \
|
||||
--title "开发 $feature" \
|
||||
--body "## 任务描述
|
||||
|
||||
实现 $feature 功能
|
||||
|
||||
## 技术要求
|
||||
- 代码规范
|
||||
- 单元测试
|
||||
- 文档完整
|
||||
|
||||
## 验收标准
|
||||
- 功能正常工作
|
||||
- 测试通过
|
||||
- 代码审查通过"
|
||||
done
|
||||
|
||||
# 阶段 3: 模拟开发和 PR 创建
|
||||
echo "🔧 阶段 3: 模拟开发工作"
|
||||
|
||||
# 创建功能分支
|
||||
for feature in "${FEATURES[@]}"; do
|
||||
# 模拟分支名(将中文转为拼音)
|
||||
branch_name="feature-$(echo $feature | md5sum | cut -c1-8)"
|
||||
|
||||
git checkout -b "$branch_name"
|
||||
|
||||
# 模拟开发工作
|
||||
echo "// $feature 实现" > "${feature}.js"
|
||||
git add .
|
||||
git commit -m "Implement $feature"
|
||||
git push gitlink "$branch_name"
|
||||
|
||||
# 创建 PR
|
||||
gitlink-cli pr +create \
|
||||
--owner "$OWNER" \
|
||||
--repo "$REPO" \
|
||||
--title "Feature: $feature" \
|
||||
--head "$branch_name" \
|
||||
--base master \
|
||||
--body "## 功能说明
|
||||
|
||||
实现 $feature 功能
|
||||
|
||||
## 变更内容
|
||||
- 添加核心功能
|
||||
- 实现相关测试
|
||||
- 更新文档
|
||||
|
||||
## 测试情况
|
||||
- 单元测试通过
|
||||
- 集成测试通过
|
||||
- 手工测试完成"
|
||||
|
||||
git checkout master
|
||||
done
|
||||
|
||||
# 阶段 4: 自动 Issue 分类
|
||||
echo "🏷️ 阶段 4: 自动分类 Issue"
|
||||
|
||||
# 获取所有开放 Issue
|
||||
ISSUES=$(gitlink-cli issue +list --owner "$OWNER" --repo "$REPO" --state open --format json)
|
||||
|
||||
# 为 Issue 添加标签
|
||||
echo "$ISSUES" | jq -r '.data.issues[].id' | while read issue_id; do
|
||||
echo "处理 Issue #$issue_id"
|
||||
|
||||
# 获取 Issue 详情
|
||||
ISSUE_DETAIL=$(gitlink-cli issue +view --owner "$OWNER" --repo "$REPO" --id "$issue_id" --format json)
|
||||
TITLE=$(echo "$ISSUE_DETAIL" | jq -r '.data.subject')
|
||||
|
||||
# 基于标题分类
|
||||
if echo "$TITLE" | grep -iq "认证"; then
|
||||
echo " → 分类为: feature + security"
|
||||
# 实际执行时取消注释
|
||||
# gitlink-cli api POST "/$OWNER/$REPO/issues/$issue_id" --body '{"issue_tag_ids":[1,5]}'
|
||||
else
|
||||
echo " → 分类为: feature"
|
||||
# gitlink-cli api POST "/$OWNER/$REPO/issues/$issue_id" --body '{"issue_tag_ids":[1]}'
|
||||
fi
|
||||
done
|
||||
|
||||
# 阶段 5: PR 审查
|
||||
echo "🔍 阶段 5: 自动 PR 审查"
|
||||
|
||||
# 获取开放 PR
|
||||
PRS=$(gitlink-cli pr +list --owner "$OWNER" --repo "$REPO" --state open --format json)
|
||||
|
||||
echo "$PRS" | jq -r '.data.prs[].id' | while read pr_id; do
|
||||
echo "审查 PR #$pr_id"
|
||||
|
||||
# 获取 PR 详情
|
||||
PR_DETAIL=$(gitlink-cli pr +view --owner "$OWNER" --repo "$REPO" --id "$pr_id" --format json)
|
||||
PR_AUTHOR=$(echo "$PR_DETAIL" | jq -r '.data.author.login')
|
||||
PR_TITLE=$(echo "$PR_DETAIL" | jq -r '.data.title')
|
||||
|
||||
# 简单的代码检查(这里只是模拟)
|
||||
REVIEW_COMMENTS="# 🔍 自动审查结果
|
||||
|
||||
## PR 信息
|
||||
- **标题**: $PR_TITLE
|
||||
- **作者**: $PR_AUTHOR
|
||||
- **状态**: 待审查
|
||||
|
||||
## ✅ 自动检查
|
||||
- 代码提交正常
|
||||
- 变更描述清晰
|
||||
- 符合项目规范
|
||||
|
||||
## 💡 建议
|
||||
- 添加单元测试
|
||||
- 更新相关文档
|
||||
- 确认向后兼容性
|
||||
|
||||
## 📋 审查结论
|
||||
代码质量良好,建议合并。"
|
||||
|
||||
echo " → 添加审查评论"
|
||||
# 实际执行时取消注释
|
||||
# gitlink-cli api POST "/$OWNER/$REPO/pulls/$pr_id/reviews" --body "{\"body\":\"$REVIEW_COMMENTS\",\"event\":\"APPROVE\"}"
|
||||
done
|
||||
|
||||
# 阶段 6: 生成 Release Notes
|
||||
echo "📝 阶段 6: 生成 Release Notes"
|
||||
|
||||
# 合并所有 PR(模拟)
|
||||
echo "合并所有功能 PR..."
|
||||
MERGED_PRS=$(gitlink-cli pr +list --owner "$OWNER" --repo "$REPO" --state merged --format json)
|
||||
|
||||
# 生成 Release Notes
|
||||
RELEASE_NOTES="# 🎉 v1.0.0 首个版本发布
|
||||
|
||||
## 📊 版本概述
|
||||
这是 $PROJECT_NAME 的首个稳定版本,包含了核心功能的完整实现。
|
||||
|
||||
## ✨ 新功能
|
||||
- 用户认证系统:完整的登录注册功能
|
||||
- 数据管理模块:高效的数据存储和检索
|
||||
- API 接口:RESTful API 设计
|
||||
- 前端界面:现代化的用户界面
|
||||
- 测试框架:完整的自动化测试
|
||||
|
||||
## 🐛 Bug 修复
|
||||
- 修复认证过程中的边界问题
|
||||
- 解决数据一致性问题
|
||||
- 优化 API 响应性能
|
||||
|
||||
## 🔧 技术改进
|
||||
- 代码结构优化
|
||||
- 性能提升 30%
|
||||
- 安全性增强
|
||||
|
||||
## 📚 文档更新
|
||||
- 用户手册完善
|
||||
- API 文档更新
|
||||
- 开发指南补充
|
||||
|
||||
## 🙏 贡献者
|
||||
感谢所有参与开发的贡献者!
|
||||
|
||||
## 📥 安装方法
|
||||
\`\`\`bash
|
||||
# 使用 npm 安装
|
||||
npm install $OWNER/$REPO@v1.0.0
|
||||
|
||||
# 或使用 yarn
|
||||
yarn add $OWNER/$REPO@v1.0.0
|
||||
\`\`\`
|
||||
|
||||
## 🔄 升级指南
|
||||
从之前的版本升级,请参考迁移指南。
|
||||
|
||||
## 📚 完整文档
|
||||
- 用户指南: https://www.gitlink.org.cn/$OWNER/$REPO/wiki
|
||||
- API 文档: https://www.gitlink.org.cn/$OWNER/$REPO/api-docs
|
||||
|
||||
---
|
||||
**发布日期**: $(date +%Y-%m-%d)
|
||||
**下一版本**: v1.1.0 (计划于 $(date -d "1 month" +%Y-%m-%d) 发布)"
|
||||
|
||||
# 创建 Release
|
||||
echo "创建 Release v1.0.0..."
|
||||
gitlink-cli release +create \
|
||||
--owner "$OWNER" \
|
||||
--repo "$REPO" \
|
||||
--tag "v1.0.0" \
|
||||
--name "v1.0.0" \
|
||||
--body "$RELEASE_NOTES"
|
||||
|
||||
# 阶段 7: Sprint 报告
|
||||
echo "📊 阶段 7: 生成 Sprint 报告"
|
||||
|
||||
SPRINT_START=$(date -d "14 days ago" +%Y-%m-%d)
|
||||
SPRINT_END=$(date +%Y-%m-%d)
|
||||
|
||||
SPRINT_REPORT="# 📊 Sprint 1 完成报告
|
||||
|
||||
## 📅 时间信息
|
||||
- **Sprint 周期**: $SPRINT_START 至 $SPRINT_END
|
||||
- **团队规模**: AI Agent x 1
|
||||
- **工作模式**: 自动化开发
|
||||
|
||||
## 🎯 目标达成
|
||||
### 计划完成度
|
||||
- **计划 Issue**: 5 个
|
||||
- **实际完成**: 5 个
|
||||
- **完成率**: 100%
|
||||
|
||||
### 质量指标
|
||||
- **代码质量**: 优秀
|
||||
- **测试覆盖率**: 95%
|
||||
- **文档完整度**: 100%
|
||||
|
||||
## 💻 工作统计
|
||||
### 代码提交
|
||||
- **总提交数**: 42 次
|
||||
- **日均提交**: 3 次/天
|
||||
- **代码行数**: +2,450 -180 行
|
||||
|
||||
### Issue 处理
|
||||
- **关闭 Issue**: 5 个
|
||||
- **新建 Issue**: 0 个
|
||||
- **平均处理时间**: 2.5 天
|
||||
|
||||
### PR 合并
|
||||
- **合并 PR**: 5 个
|
||||
- **平均审查时间**: 1 小时
|
||||
- **平均合并时间**: 2 小时
|
||||
|
||||
## 🎉 主要成就
|
||||
1. ✅ 完成用户认证系统开发
|
||||
2. ✅ 实现数据管理模块
|
||||
3. ✅ 构建 RESTful API
|
||||
4. ✅ 设计现代化前端界面
|
||||
5. ✅ 建立完整测试体系
|
||||
|
||||
## 📈 性能指标
|
||||
- **开发效率**: 高
|
||||
- **代码质量**: 优秀
|
||||
- **自动化程度**: 95%
|
||||
- **文档完整度**: 100%
|
||||
|
||||
## 🔮 下期规划
|
||||
- 性能优化和改进
|
||||
- 新功能模块开发
|
||||
- 国际化支持
|
||||
- 移动端适配
|
||||
|
||||
---
|
||||
**AI Agent 自动化工作流演示**
|
||||
**报告生成**: $(date +%Y-%m-%d %H:%M:%S)"
|
||||
|
||||
# 保存 Sprint 报告
|
||||
REPORT_FILE="sprint_reports/sprint_1_$(date +%Y%m%d).md"
|
||||
mkdir -p sprint_reports
|
||||
echo "$SPRINT_REPORT" > "$REPORT_FILE"
|
||||
|
||||
echo "🎉 AI Agent 自动化工作流完成!"
|
||||
echo ""
|
||||
echo "📊 项目统计:"
|
||||
echo " 仓库: https://www.gitlink.org.cn/$OWNER/$REPO"
|
||||
echo " Issue: 5 个全部完成"
|
||||
echo " PR: 5 个全部合并"
|
||||
echo " Release: v1.0.0 已发布"
|
||||
echo ""
|
||||
echo "📄 生成的文档:"
|
||||
echo " - Release Notes: https://www.gitlink.org.cn/$OWNER/$REPO/releases/v1.0.0"
|
||||
echo " - Sprint 报告: $REPORT_FILE"
|
||||
```
|
||||
|
||||
## Claude Code 集成示例
|
||||
|
||||
### 在 Claude Code 中使用工作流
|
||||
|
||||
```markdown
|
||||
# 用户指令
|
||||
|
||||
帮助我创建一个新的项目并完成首个版本的发布。
|
||||
|
||||
# Claude Code 执行
|
||||
|
||||
我会使用 gitlink-cli 的自动化工作流来完成这个任务:
|
||||
|
||||
1. **创建仓库** - 使用 workflow-repo-setup
|
||||
2. **管理 Issue** - 使用 workflow-issue-triage
|
||||
3. **审查 PR** - 使用 workflow-pr-review
|
||||
4. **生成 Release** - 使用 workflow-release-notes
|
||||
5. **总结报告** - 使用 workflow-sprint-report
|
||||
|
||||
让我开始执行...
|
||||
```
|
||||
|
||||
### 技能组合使用
|
||||
|
||||
```python
|
||||
# AI Agent 多技能组合
|
||||
class GitLinkAgent:
|
||||
def __init__(self, owner, repo):
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
self.cli = "gitlink-cli"
|
||||
|
||||
def complete_project_workflow(self):
|
||||
"""完整的项目工作流"""
|
||||
|
||||
# 阶段 1: 初始化
|
||||
self.setup_repository()
|
||||
|
||||
# 阶段 2: 开发管理
|
||||
self.manage_development()
|
||||
|
||||
# 阶段 3: 质量控制
|
||||
self.automated_review()
|
||||
|
||||
# 阶段 4: 发布管理
|
||||
self.create_release()
|
||||
|
||||
# 阶段 5: 报告总结
|
||||
self.generate_reports()
|
||||
|
||||
def setup_repository(self):
|
||||
"""仓库初始化"""
|
||||
# 使用 workflow-repo-setup
|
||||
create_repo_cmd = f"{self.cli} repo +create --name {self.repo}"
|
||||
subprocess.run(create_repo_cmd.split())
|
||||
|
||||
protect_branch_cmd = f"{self.cli} branch +protect --name master"
|
||||
subprocess.run(protect_branch_cmd.split())
|
||||
|
||||
def manage_development(self):
|
||||
"""开发管理"""
|
||||
# 监控新 Issue 并自动分类
|
||||
issues = self.get_new_issues()
|
||||
for issue in issues:
|
||||
self.classify_issue(issue)
|
||||
|
||||
# 监控新 PR 并审查
|
||||
prs = self.get_new_prs()
|
||||
for pr in prs:
|
||||
self.review_pr(pr)
|
||||
|
||||
def automated_review(self):
|
||||
"""自动化审查"""
|
||||
# 获取待审查的 PR
|
||||
pending_prs = self.get_pending_prs()
|
||||
|
||||
for pr in pending_prs:
|
||||
review_result = self.analyze_pr(pr)
|
||||
self.submit_review(pr, review_result)
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 工作流选择
|
||||
- **项目创建**: 使用 workflow-repo-setup
|
||||
- **日常维护**: 使用 workflow-issue-triage 和 workflow-pr-review
|
||||
- **版本发布**: 使用 workflow-release-notes
|
||||
- **团队管理**: 使用 workflow-sprint-report
|
||||
|
||||
### 2. 执行顺序
|
||||
典型的执行顺序:
|
||||
1. 项目初始化 → 2. Issue 管理 → 3. PR 审查 → 4. Release 发布 → 5. Sprint 报告
|
||||
|
||||
### 3. 错误处理
|
||||
```python
|
||||
def safe_workflow_execution(workflow_func, *args, **kwargs):
|
||||
"""安全执行工作流"""
|
||||
try:
|
||||
return workflow_func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
# 记录错误
|
||||
log_error(e)
|
||||
# 尝试恢复
|
||||
return handle_workflow_error(e, workflow_func, *args, **kwargs)
|
||||
```
|
||||
|
||||
### 4. 进度跟踪
|
||||
```python
|
||||
class WorkflowProgress:
|
||||
def __init__(self):
|
||||
self.current_step = 0
|
||||
self.total_steps = 5
|
||||
self.completed_steps = []
|
||||
self.failed_steps = []
|
||||
|
||||
def update_progress(self, step_name, success=True):
|
||||
if success:
|
||||
self.completed_steps.append(step_name)
|
||||
else:
|
||||
self.failed_steps.append(step_name)
|
||||
self.current_step += 1
|
||||
|
||||
def get_progress_report(self):
|
||||
progress = self.current_step / self.total_steps * 100
|
||||
return {
|
||||
'progress': f'{progress:.1f}%',
|
||||
'completed': self.completed_steps,
|
||||
'failed': self.failed_steps
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展工作流
|
||||
|
||||
### 自定义工作流
|
||||
|
||||
```python
|
||||
# 创建自定义工作流
|
||||
def custom_workflow(owner, repo, custom_config):
|
||||
"""自定义工作流模板"""
|
||||
|
||||
# 1. 预检查
|
||||
if not validate_environment():
|
||||
return False
|
||||
|
||||
# 2. 执行自定义步骤
|
||||
for step in custom_config['steps']:
|
||||
execute_step(step)
|
||||
|
||||
# 3. 后处理
|
||||
cleanup_environment()
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
| 问题 | 解决方案 |
|
||||
|------|----------|
|
||||
| 权限不足 | 检查 Token 权限 |
|
||||
| API 限制 | 添加重试机制 |
|
||||
| 数据格式错误 | 验证输入数据 |
|
||||
| 执行超时 | 增加超时时间 |
|
||||
|
||||
## References
|
||||
|
||||
- [workflow-issue-triage](../references/workflow-issue-triage.md) — Issue 分类
|
||||
- [workflow-pr-review](../references/workflow-pr-review.md) — PR 审查
|
||||
- [workflow-release-notes](../references/workflow-release-notes.md) — Release Notes
|
||||
- [workflow-repo-setup](../references/workflow-repo-setup.md) — 仓库初始化
|
||||
- [workflow-sprint-report](../references/workflow-sprint-report.md) — Sprint 报告
|
||||
- [gitlink-workflow](../SKILL.md) — 工作流总览
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
# Workflow: Community Ops(社区运营自动化)
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于自动化社区运营。
|
||||
|
||||
AI Agent 自动完成社区运营任务,包括 Issue 分类、负责人分配、周报生成、Release Notes 发布。
|
||||
|
||||
## 工作流概述
|
||||
|
||||
Community Ops 工作流自动化社区运营的四个核心环节:Issue 分析 → 负责人分配 → 周报生成 → Release Notes 发布。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- **Issue 管理**:自动分类和分配新 Issue
|
||||
- **周报生成**:汇总本周数据生成社区周报
|
||||
- **发版管理**:生成 Release Notes 并发布
|
||||
- **定期运营**:每周/每月定期执行社区运营
|
||||
|
||||
## 触发词
|
||||
|
||||
- "社区运营" / "community ops"
|
||||
- "周报" / "weekly report"
|
||||
- "发版" / "release notes"
|
||||
- "Issue 分类" / "issue triage"
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 否 | 仓库所有者(自动从 git remote 解析) |
|
||||
| `--repo` | 否 | 仓库名称(自动从 git remote 解析) |
|
||||
| `--weeks-ago` | 否 | 生成 N 周前的周报(默认 0 = 本周) |
|
||||
| `--dry-run` | 否 | 预览模式,不执行写入操作 |
|
||||
|
||||
## 工作流步骤
|
||||
|
||||
### 阶段 1:Issue 自动分类
|
||||
|
||||
**步骤 1.1** — 获取 open issues:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state open --limit 100 --format json
|
||||
```
|
||||
|
||||
**步骤 1.2** — 分类规则:
|
||||
|
||||
| 类型 | 标签 | 关键词 |
|
||||
|------|------|--------|
|
||||
| Bug | `bug` | bug, error, crash, fault, fix |
|
||||
| Feature | `feature` | feature, enhancement, add, support, request |
|
||||
| Question | `question` | how, question, help |
|
||||
| Docs | `documentation` | doc, readme, guide, tutorial, example |
|
||||
|
||||
**步骤 1.3** — 添加标签:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +label-add --owner {OWNER} --repo {REPO} --number {ISSUE_ID} --labels {LABEL}
|
||||
```
|
||||
|
||||
### 阶段 2:负责人分配(基于贡献度 AHP 评分)
|
||||
|
||||
**引用 [`workflow-contributor-growth.md`](workflow-contributor-growth.md) 的 AHP 评分模型**
|
||||
|
||||
负责人分配基于贡献者的 AHP 评分,优先分配给贡献度高的成员。
|
||||
|
||||
**步骤 2.1** — 收集数据:
|
||||
|
||||
```bash
|
||||
# 获取仓库成员
|
||||
gitlink-cli repo +members --owner {OWNER} --repo {REPO} --limit 50 --format json
|
||||
|
||||
# 获取 merged PR(统计贡献)
|
||||
gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json
|
||||
|
||||
# 获取 closed Issues(统计参与)
|
||||
gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json
|
||||
```
|
||||
|
||||
**步骤 2.2** — 计算每个成员的 AHP 分数:
|
||||
|
||||
| 维度 | 权重 | 计算方式 |
|
||||
|------|------|----------|
|
||||
| Issues Created | 15% | 该成员创建的 Issue 数 / 最大值 |
|
||||
| PRs Merged | 25% | 该成员合并的 PR 数 / 最大值 |
|
||||
| Code Changes | 30% | 该成员代码变更行数 / 最大值 |
|
||||
| Issue Comments | 15% | 该成员评论数 / 最大值 |
|
||||
| Team Member | 15% | 是成员=1,非成员=0 |
|
||||
|
||||
```
|
||||
Score = NI×15 + NM×25 + NL×30 + NC×15 + MS×15
|
||||
```
|
||||
|
||||
**步骤 2.3** — 筛选待分配 Issue 并分配:
|
||||
|
||||
```bash
|
||||
# 只分配 Bug 和 Feature 类型的 Issue
|
||||
# Bug → 标签含 "bug"/"缺陷"
|
||||
# Feature → 标签含 "feature"/"功能"
|
||||
|
||||
# 按分数从高到低排序成员
|
||||
# Bug:优先分配给最高分成员(确保快速解决)
|
||||
# Feature:按分数轮流分配(鼓励参与)
|
||||
|
||||
# 分配命令
|
||||
gitlink-cli issue +batch-assign --owner {OWNER} --repo {REPO} --numbers {ISSUE_NUMBERS} --assignee {LOGIN}
|
||||
```
|
||||
|
||||
**分配策略**:
|
||||
- Bug 类型:优先分配给贡献度最高的成员(快速响应)
|
||||
- Feature 类型:按贡献度轮流分配(鼓励更多人参与)
|
||||
- 成员不足时用 Round-robin 补充
|
||||
|
||||
### 阶段 3:周报生成
|
||||
|
||||
**步骤 3.1** — 收集数据:
|
||||
|
||||
```bash
|
||||
# Closed issues
|
||||
gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json
|
||||
|
||||
# Merged PRs
|
||||
gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json
|
||||
```
|
||||
|
||||
**步骤 3.2** — 周报模板:
|
||||
|
||||
```markdown
|
||||
# Community Weekly Report: {WEEK_START} ~ {WEEK_END}
|
||||
|
||||
## Summary
|
||||
- New Issues: **{NEW_COUNT}**
|
||||
- Closed Issues: **{CLOSED_COUNT}**
|
||||
- Merged PRs: **{MERGED_COUNT}**
|
||||
|
||||
## Issue Classification
|
||||
| Type | Count |
|
||||
|------|-------|
|
||||
| Bug | {BUG_COUNT} |
|
||||
| Feature | {FEATURE_COUNT} |
|
||||
| Question | {QUESTION_COUNT} |
|
||||
| Docs | {DOCS_COUNT} |
|
||||
|
||||
## Highlights
|
||||
- Auto-classified and labeled {TOTAL_CLASSIFIED} issues
|
||||
- Assigned responsible persons for bug and feature issues
|
||||
|
||||
---
|
||||
*Auto-generated by gitlink-cli community-ops workflow*
|
||||
```
|
||||
|
||||
**步骤 3.3** — 发布到 Wiki:
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +create --owner {OWNER} --repo {REPO} --title "{REPORT_TITLE}" --content "{REPORT_BODY}"
|
||||
```
|
||||
|
||||
### 阶段 4:Release Notes 生成
|
||||
|
||||
**引用 [`../workflow-release-notes.md`](workflow-release-notes.md)**
|
||||
|
||||
Release Notes 的生成遵循 Release Notes 工作流的规范:
|
||||
|
||||
1. **收集数据**:获取 commits、merged PR、closed Issue
|
||||
2. **分类整理**:按类型归类(新功能/Bug修复/改进/破坏性变更)
|
||||
3. **生成发布**:套用模板生成 Notes,创建 Release
|
||||
|
||||
**快捷命令**:
|
||||
|
||||
```bash
|
||||
gitlink-cli release +create --tag {TAG} --name "{NAME}" --body "{NOTES}"
|
||||
```
|
||||
|
||||
## 完整流程图
|
||||
|
||||
```
|
||||
Issue 分类 → 负责人分配 → 周报生成 → Release Notes
|
||||
↓ ↓ ↓ ↓
|
||||
添加标签 API PATCH Wiki 发布 Release 创建
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### AI 交互式
|
||||
|
||||
用户说:"帮我跑一下社区运营"
|
||||
|
||||
AI 应该:
|
||||
1. 确认 owner/repo(自动从 git remote 解析或询问用户)
|
||||
2. 依次执行四个阶段
|
||||
3. 展示每阶段的结果
|
||||
4. 询问是否需要调整
|
||||
|
||||
## 注意事项
|
||||
|
||||
- Issue 分类基于关键词匹配,可能不准确,建议人工审核
|
||||
- 负责人分配只针对 Bug 和 Feature 类型
|
||||
- 周报数据基于时间范围筛选,不是所有 open issues
|
||||
- Release Notes 生成引用 changelog 工作流的规范
|
||||
|
||||
## References
|
||||
|
||||
- [workflow-release-notes](workflow-release-notes.md) — Release Notes 生成
|
||||
- [workflow-issue-triage](workflow-issue-triage.md) — Issue 分类详情
|
||||
- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作
|
||||
- [gitlink-wiki](../../gitlink-wiki/SKILL.md) — Wiki 操作
|
||||
- [gitlink-release](../../gitlink-release/SKILL.md) — Release 操作
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
# Workflow: Contributor Growth(贡献者成长体系)
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于评估和激励团队贡献者。
|
||||
|
||||
AI Agent 自动收集贡献者数据,使用 AHP 权重模型计算贡献分数,生成排行榜和可视化报告,并可选择颁发 Badge。
|
||||
|
||||
## 工作流概述
|
||||
|
||||
Contributor Growth 工作流通过收集 Issue、PR、代码变更、评论等数据,使用 AHP(层次分析法)权重模型计算每个贡献者的综合分数,生成排行榜、HTML 报告和 Wiki 页面,并可自动颁发 Badge。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- **贡献评估**:量化团队成员的贡献程度
|
||||
- **排行榜生成**:生成贡献者排行榜
|
||||
- **激励机制**:通过 Badge 颁发激励贡献者
|
||||
- **团队协调**:了解团队成员的参与度
|
||||
|
||||
## 触发词
|
||||
|
||||
- "贡献者" / "contributor"
|
||||
- "排行榜" / "leaderboard"
|
||||
- "badge" / "徽章"
|
||||
- "成长" / "growth"
|
||||
- "评分" / "score"
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner` | 否 | 仓库所有者(自动从 git remote 解析) |
|
||||
| `--repo` | 否 | 仓库名称(自动从 git remote 解析) |
|
||||
| `--sample` | 否 | 采样 PR 数量获取代码统计(默认 10) |
|
||||
| `--award` | 否 | 自动创建 Badge 颁发 Issue |
|
||||
|
||||
## AHP 评分模型
|
||||
|
||||
| 维度 | 权重 | 数据来源 | 提取字段 |
|
||||
|------|------|----------|----------|
|
||||
| Issues Created | 15% | `issue +list` (open + closed) | `.data.issues[].author.login` 按作者统计 |
|
||||
| PRs Merged | 25% | `pr +list state=merged` | `.data.issues[].author_login` 按作者统计 |
|
||||
| Code Changes | 30% | `pr +files`(采样) | `.data.files[].addition` / `.deletion`(单数) |
|
||||
| Issue Comments | 15% | `issue +list` 直接提取 | `.data.issues[].comment_journals_count` |
|
||||
| Team Member | 15% | `repo +members` | `.data.members[].login` 判断是否成员 |
|
||||
|
||||
## Badge 等级
|
||||
|
||||
| Badge | 分数要求 | 说明 |
|
||||
|-------|----------|------|
|
||||
| Champion | >= 80 | 卓越贡献 |
|
||||
| Core Contributor | >= 60 | 核心贡献者 |
|
||||
| Active Contributor | >= 40 | 活跃贡献者 |
|
||||
| Contributor | >= 20 | 贡献者 |
|
||||
| Newcomer | < 20 | 新人 |
|
||||
|
||||
## 工作流步骤
|
||||
|
||||
### 步骤 1:收集数据
|
||||
|
||||
```bash
|
||||
# Open Issues
|
||||
gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state open --limit 100 --format json
|
||||
|
||||
# Closed Issues
|
||||
gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json
|
||||
|
||||
# Merged PRs
|
||||
gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json
|
||||
|
||||
# 仓库成员
|
||||
gitlink-cli repo +members --owner {OWNER} --repo {REPO} --limit 100 --format json
|
||||
```
|
||||
|
||||
### 步骤 2:构建贡献者数据
|
||||
|
||||
遍历数据,为每个贡献者统计:
|
||||
- Issues:创建的 Issue 数量
|
||||
- Merged:合并的 PR 数量
|
||||
- Additions/Deletions:代码变更行数(采样)
|
||||
- Comments:评论数量(采样)
|
||||
|
||||
### 步骤 3:采样代码变更
|
||||
|
||||
对 merged PR 采样,获取代码变更统计:
|
||||
|
||||
```bash
|
||||
# 获取 PR 变更文件
|
||||
gitlink-cli pr +files --owner {OWNER} --repo {REPO} --id {PR_ID} --format json
|
||||
```
|
||||
|
||||
提取字段:`.data.files[].addition`、`.data.files[].deletion`(**注意:是单数形式,不是 additions/deletions**)
|
||||
|
||||
**已知问题**:
|
||||
- `pr +files` 输出的 JSON 在中文 Windows 下可能因编码问题导致解析失败(GBK vs UTF-8)
|
||||
- 如果 Python 解析报错 `JSONDecodeError` 或 `UnicodeDecodeError`,需要指定 `encoding='utf-8'`
|
||||
- 部分 PR 的 files API 可能返回异常,用 try/except 跳过即可
|
||||
- 降级方案:用 PR 数量估算代码变更量(每个 PR 估 100 行)
|
||||
|
||||
### 步骤 4:采样评论数量
|
||||
|
||||
有两种方式获取评论数据:
|
||||
|
||||
**方式 A(推荐)**:直接从 `issue +list` 返回的数据中提取
|
||||
- 字段:`.data.issues[].comment_journals_count`
|
||||
- 无需额外 API 调用,效率更高
|
||||
|
||||
**方式 B**:逐个 Issue 采样
|
||||
```bash
|
||||
gitlink-cli issue +view --owner {OWNER} --repo {REPO} --number {ISSUE_ID} --format json
|
||||
```
|
||||
- 字段:`.data.comment_journals_count`
|
||||
|
||||
**补充**:PR 的评论数可从 `pr +list` 返回的 `.data.issues[].journals_count` 获取
|
||||
|
||||
### 步骤 5:计算 AHP 分数
|
||||
|
||||
```bash
|
||||
# 归一化处理
|
||||
NI = Issues / MaxIssues
|
||||
NM = Merged / MaxMerged
|
||||
NL = Lines / MaxLines
|
||||
NC = Comments / MaxComments
|
||||
MS = IsMember ? 1 : 0
|
||||
|
||||
# 加权求和
|
||||
Score = NI * 15 + NM * 25 + NL * 30 + NC * 15 + MS * 15
|
||||
```
|
||||
|
||||
### 步骤 6:生成排行榜
|
||||
|
||||
输出格式:
|
||||
|
||||
```
|
||||
Rank Contributor Issues Merged +/- Lines Comments Score Badge
|
||||
---- ------------------ ------ ------ --------- -------- ------ -------------
|
||||
1 @contributor1 15 8 +1200/-300 45 82.5 Champion
|
||||
2 @contributor2 10 12 +800/-200 30 68.2 Core Contributor
|
||||
3 @contributor3 5 6 +400/-100 20 45.1 Active Contributor
|
||||
```
|
||||
|
||||
### 步骤 7:生成 HTML 报告
|
||||
|
||||
HTML 报告包含:
|
||||
- 汇总统计卡片
|
||||
- ECharts 饼图(分数分布)
|
||||
- 详细排行榜表格
|
||||
- AHP 权重说明
|
||||
|
||||
### 步骤 8:发布到 Wiki
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +create \
|
||||
--owner {OWNER} \
|
||||
--repo {REPO} \
|
||||
--title "Contributor Leaderboard {DATE}" \
|
||||
--content "{WIKI_CONTENT}"
|
||||
```
|
||||
|
||||
Wiki 内容包含:
|
||||
- 评分体系说明
|
||||
- 排行榜表格
|
||||
- 生成时间
|
||||
|
||||
### 步骤 9:颁发 Badge(可选)
|
||||
|
||||
如果指定了 `--award` 参数,为获奖者创建 Issue:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +create \
|
||||
--owner {OWNER} \
|
||||
--repo {REPO} \
|
||||
--title "Badge Award: {BADGE}" \
|
||||
--body "{ISSUE_BODY}"
|
||||
|
||||
gitlink-cli issue +label-add \
|
||||
--owner {OWNER} \
|
||||
--repo {REPO} \
|
||||
--number {ISSUE_ID} \
|
||||
--labels badge
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
|
||||
### 控制台输出
|
||||
|
||||
```
|
||||
====== Contributor Growth System: zzx-coder/gitlink-cli ======
|
||||
|
||||
[STEP] Collecting data...
|
||||
[ OK] Issues(open:15 closed:30) PRs(merged:25) Members:8
|
||||
|
||||
[STEP] Building contributor profiles...
|
||||
[STEP] Analyzing PR code changes (sampling 10)...
|
||||
[STEP] Sampling issue comments...
|
||||
|
||||
[STEP] Calculating scores...
|
||||
|
||||
====== Contributor Rankings ======
|
||||
|
||||
Rank Contributor Issues Merged +/- Lines Comments Score Badge
|
||||
---- ------------------ ------ ------ --------- -------- ------ -------------
|
||||
1 @zzx-coder 12 8 +1500/-400 35 85.2 Champion
|
||||
2 @contributor1 8 6 +800/-200 25 62.1 Core Contributor
|
||||
3 @contributor2 5 4 +400/-100 15 42.3 Active Contributor
|
||||
|
||||
====== Generating HTML Report ======
|
||||
[ OK] HTML report: contrib-report-zzx-coder-gitlink-cli.html
|
||||
|
||||
[STEP] Publishing to Wiki...
|
||||
[ OK] Published to Wiki: Contributor Leaderboard 2026-07-02
|
||||
|
||||
====== Awarding Badges ======
|
||||
[ OK] Badge issue created: #45 - Badge Award: Champion (1 recipients)
|
||||
[ OK] Badge issue created: #46 - Badge Award: Core Contributor (1 recipients)
|
||||
|
||||
====== Complete ======
|
||||
|
||||
Contributors: 8
|
||||
HTML Report: contrib-report-zzx-coder-gitlink-cli.html
|
||||
Wiki: Contributor Leaderboard 2026-07-02
|
||||
Badges: Awarded
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 代码变更统计基于采样,不是全量数据
|
||||
- Badge 颁发会创建 issue,需要确认权限
|
||||
- HTML 报告使用 ECharts 需要网络连接
|
||||
- 建议定期运行(如每月)跟踪贡献趋势
|
||||
|
||||
## 已知问题与解决方案
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|----------|
|
||||
| Python `UnicodeDecodeError: 'gbk' codec` | 中文 Windows 默认 GBK 编码,gitlink-cli 输出 UTF-8 | `open(file, encoding='utf-8')` |
|
||||
| `pr +files` JSON 解析失败 | 输出含特殊字符,GBK 解码破坏 UTF-8 序列 | 用 `encoding='utf-8'` 读取,或用 try/except 跳过 |
|
||||
| `pr +files` 字段名为 `addition` 不是 `additions` | GitLink API 使用单数形式 | 用 `f.get('addition', f.get('additions', 0))` 兼容 |
|
||||
| Issue 数据中 `comment_journals_count` 为 0 | 某些 Issue 确实没有评论 | 正常现象,不影响评分 |
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作
|
||||
- [gitlink-pr](../../gitlink-pr/SKILL.md) — PR 操作
|
||||
- [gitlink-repo](../../gitlink-repo/SKILL.md) — 仓库操作
|
||||
- [gitlink-wiki](../../gitlink-wiki/SKILL.md) — Wiki 操作
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
# Workflow: Multi-Repo Collaboration(多仓库协同)
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于跨仓库协同管理。
|
||||
|
||||
AI Agent 自动汇总组织下所有仓库的 Issue、PR、Release 状态,生成可视化 Dashboard,支持协同发版。
|
||||
|
||||
## 工作流概述
|
||||
|
||||
Multi-Repo Collaboration 工作流通过遍历组织下的所有仓库,收集各仓库的 Issue、PR、Release 数据,生成统一的状态 Dashboard,并支持跨仓库协同发版。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- **状态总览**:查看组织下所有仓库的健康状态
|
||||
- **跨仓库追踪**:追踪跨仓库的 Issue 和 PR
|
||||
- **协同发版**:多个仓库同时发布同一版本
|
||||
- **团队协调**:协调团队在多个项目间的工作
|
||||
|
||||
## 触发词
|
||||
|
||||
- "多仓库" / "multi repo"
|
||||
- "协同" / "collaboration"
|
||||
- "dashboard" / "看板"
|
||||
- "组织仓库" / "org repos"
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--org` | 是 | 组织名称 |
|
||||
| `--repos` | 否 | 指定仓库列表(逗号分隔),默认遍历所有仓库 |
|
||||
| `--release` | 否 | 协同发版的版本号 |
|
||||
| `--output` | 否 | Dashboard 输出文件(默认 dashboard.html) |
|
||||
|
||||
## 工作流步骤
|
||||
|
||||
### 步骤 1:列出组织仓库
|
||||
|
||||
```bash
|
||||
# 获取组织下所有仓库
|
||||
gitlink-cli repo +list --user {ORG} --limit 100 --format json
|
||||
```
|
||||
|
||||
提取仓库列表:`.data.projects[]` 或 `.data[]`,字段:`.name` 或 `.identifier`
|
||||
|
||||
### 步骤 2:收集各仓库数据
|
||||
|
||||
遍历每个仓库,收集 Issue、PR、Release 数据:
|
||||
|
||||
```bash
|
||||
# Open Issues
|
||||
gitlink-cli issue +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json
|
||||
|
||||
# Closed Issues
|
||||
gitlink-cli issue +list --owner {ORG} --repo {REPO} --state closed --limit 50 --format json
|
||||
|
||||
# Open PRs
|
||||
gitlink-cli pr +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json
|
||||
|
||||
# Merged PRs
|
||||
gitlink-cli pr +list --owner {ORG} --repo {REPO} --state merged --limit 50 --format json
|
||||
|
||||
# Latest Release
|
||||
gitlink-cli release +list --owner {ORG} --repo {REPO} --limit 1 --format json
|
||||
```
|
||||
|
||||
### 步骤 3:生成 Dashboard
|
||||
|
||||
Dashboard 包含:
|
||||
- **汇总卡片**:总仓库数、Open Issues、Open PRs、总活动量
|
||||
- **仓库状态表**:每个仓库的 Issue/PR/Release 状态和健康度
|
||||
|
||||
**健康度判断**:
|
||||
- Open Issues <= 10 → Healthy(绿色)
|
||||
- Open Issues 11-20 → Moderate(橙色)
|
||||
- Open Issues > 20 → Needs Attention(红色)
|
||||
|
||||
### 步骤 4:协同发版(可选)
|
||||
|
||||
如果指定了 `--release` 参数,为所有仓库创建 Release:
|
||||
|
||||
```bash
|
||||
gitlink-cli release +create \
|
||||
--owner {ORG} \
|
||||
--repo {REPO} \
|
||||
--tag {VERSION} \
|
||||
--name "Release {VERSION}" \
|
||||
--body "Coordinated release {VERSION} for {REPO}"
|
||||
```
|
||||
|
||||
## 数据提取
|
||||
|
||||
### Issue 数量
|
||||
|
||||
```bash
|
||||
# Open Issues 数量
|
||||
OPEN_COUNT=$(gitlink-cli issue +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json | jq '.data.issues | length')
|
||||
|
||||
# Closed Issues 数量
|
||||
CLOSED_COUNT=$(gitlink-cli issue +list --owner {ORG} --repo {REPO} --state closed --limit 50 --format json | jq '.data.issues | length')
|
||||
```
|
||||
|
||||
### PR 数量
|
||||
|
||||
```bash
|
||||
# Open PRs 数量
|
||||
OPEN_PRS=$(gitlink-cli pr +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json | jq '.data.issues | length')
|
||||
|
||||
# Merged PRs 数量
|
||||
MERGED_PRS=$(gitlink-cli pr +list --owner {ORG} --repo {REPO} --state merged --limit 50 --format json | jq '.data.issues | length')
|
||||
```
|
||||
|
||||
### Release 状态
|
||||
|
||||
```bash
|
||||
# 最新 Release
|
||||
LATEST_RELEASE=$(gitlink-cli release +list --owner {ORG} --repo {REPO} --limit 1 --format json | jq -r '.data.releases[0].tag_name // "none"')
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
|
||||
### Dashboard HTML
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Multi-Repo Collaboration Dashboard</title>
|
||||
<style>
|
||||
/* 样式定义 */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Multi-Repo Collaboration Dashboard</h1>
|
||||
<div class="summary">
|
||||
<div class="card">Total Repos: 10</div>
|
||||
<div class="card">Open Issues: 45</div>
|
||||
<div class="card">Open PRs: 12</div>
|
||||
<div class="card">Total Activity: 156</div>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Repository</th>
|
||||
<th>Open Issues</th>
|
||||
<th>Closed Issues</th>
|
||||
<th>Open PRs</th>
|
||||
<th>Merged PRs</th>
|
||||
<th>Latest Release</th>
|
||||
<th>Health</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 仓库状态行 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 控制台输出
|
||||
|
||||
```
|
||||
====== Multi-Repo Collaboration Dashboard ======
|
||||
|
||||
[STEP] Fetching repositories for org: myorg...
|
||||
[ OK] Found 10 repositories
|
||||
|
||||
[STEP] Processing repo1...
|
||||
[ OK] repo1 : Issues(open:5 closed:12) PRs(open:2 merged:8) Release:v1.2.0
|
||||
|
||||
[STEP] Processing repo2...
|
||||
[ OK] repo2 : Issues(open:15 closed:20) PRs(open:5 merged:15) Release:v2.0.0
|
||||
|
||||
====== Generating Dashboard ======
|
||||
[ OK] Dashboard saved to: dashboard.html
|
||||
|
||||
====== Multi-Repo Dashboard Complete ======
|
||||
|
||||
Repos processed: 10
|
||||
Total issues: 156 (open: 45)
|
||||
Total PRs: 89 (open: 12)
|
||||
Dashboard: dashboard.html
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 大量仓库时注意 API 限流,建议添加延时
|
||||
- Release 状态可能需要二次验证(GitLink API bug)
|
||||
- Dashboard HTML 文件可以用浏览器打开查看
|
||||
- 协同发版前建议先预览各仓库状态
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-repo](../../gitlink-repo/SKILL.md) — 仓库操作
|
||||
- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作
|
||||
- [gitlink-pr](../../gitlink-pr/SKILL.md) — PR 操作
|
||||
- [gitlink-release](../../gitlink-release/SKILL.md) — Release 操作
|
||||
- [gitlink-org](../../gitlink-org/SKILL.md) — 组织管理
|
||||
|
|
@ -140,106 +140,6 @@ done)"
|
|||
gitlink-cli release +create --tag $NEW_TAG --name "$NEW_TAG" --body "$RELEASE_NOTES"
|
||||
```
|
||||
|
||||
## 完整工作流示例
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Release Notes 自动生成脚本
|
||||
|
||||
OWNER="myuser"
|
||||
REPO="myproject"
|
||||
NEW_VERSION=$1
|
||||
|
||||
if [ -z "$NEW_VERSION" ]; then
|
||||
echo "使用方法: $0 <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "为版本 $NEW_VERSION 生成 Release Notes..."
|
||||
|
||||
# 1. 获取上一个版本
|
||||
PREV_VERSION=$(gitlink-cli release +list --owner $OWNER --repo $REPO --format json | \
|
||||
jq -r '.data.releases[0].tag_name')
|
||||
|
||||
echo "上一个版本: $PREV_VERSION"
|
||||
echo "新版本: $NEW_VERSION"
|
||||
|
||||
# 2. 获取提交比较
|
||||
COMPARE_DATA=$(gitlink-cli api GET "/$OWNER/$REPO/compare/$PREV_VERSION...$NEW_VERSION" --format json)
|
||||
COMMITS=$(echo "$COMPARE_DATA" | jq -r '.data.commits')
|
||||
|
||||
# 3. 获取已关闭的 Issue
|
||||
ISSUES=$(gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --format json | \
|
||||
jq '.data.issues[]')
|
||||
|
||||
# 4. 获取已合并的 PR
|
||||
PRS=$(gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --format json | \
|
||||
jq '.data.prs[]')
|
||||
|
||||
# 5. 分析变更数据
|
||||
FEATURE_COUNT=0
|
||||
BUG_FIX_COUNT=0
|
||||
ENHANCEMENT_COUNT=0
|
||||
BREAKING_COUNT=0
|
||||
|
||||
# 分析 Issue
|
||||
FEATURES=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "feature") | "- \(.subject) (#\(.id))"')
|
||||
BUG_FIXES=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "bug") | "- \(.subject) (#\(.id))"')
|
||||
ENHANCEMENTS=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "enhancement") | "- \(.subject) (#\(.id))"')
|
||||
|
||||
# 分析提交中的破坏性变更
|
||||
BREAKING_CHANGES=$(echo "$COMMITS" | jq -r 'select(.commit.message | contains("BREAKING")) | "- \(.commit.message | split("\n")[0])"')
|
||||
|
||||
# 统计数量
|
||||
FEATURE_COUNT=$(echo "$FEATURES" | grep -c "^-" || echo "0")
|
||||
BUG_FIX_COUNT=$(echo "$BUG_FIXES" | grep -c "^-" || echo "0")
|
||||
ENHANCEMENT_COUNT=$(echo "$ENHANCEMENTS" | grep -c "^-" || echo "0")
|
||||
BREAKING_COUNT=$(echo "$BREAKING_CHANGES" | grep -c "^-" || echo "0")
|
||||
|
||||
# 6. 生成 Release Notes
|
||||
RELEASE_NOTES="# 🎉 Release $NEW_VERSION
|
||||
|
||||
## 📊 变更统计
|
||||
- **新功能**: $FEATURE_COUNT 个
|
||||
- **Bug 修复**: $BUG_FIX_COUNT 个
|
||||
- **功能改进**: $ENHANCEMENT_COUNT 个
|
||||
- **破坏性变更**: $BREAKING_COUNT 个
|
||||
|
||||
## ✨ 新功能
|
||||
$FEATURES
|
||||
|
||||
## 🐛 Bug 修复
|
||||
$BUG_FIXES
|
||||
|
||||
## 🔧 功能改进
|
||||
$ENHANCEMENTS
|
||||
|
||||
## ⚠️ 破坏性变更
|
||||
$BREAKING_CHANGES
|
||||
|
||||
## 🙏 贡献者
|
||||
感谢所有参与此版本开发的贡献者!
|
||||
|
||||
## 📥 安装
|
||||
\`\`\`bash
|
||||
npm install $OWNER/$REPO@$NEW_VERSION
|
||||
\`\`\`
|
||||
|
||||
## 📚 文档
|
||||
完整文档请查看: https://www.gitlink.org.cn/$OWNER/$REPO/wiki
|
||||
|
||||
---
|
||||
**完整变更日志**: https://www.gitlink.org.cn/$OWNER/$REPO/compare/$PREV_VERSION...$NEW_VERSION"
|
||||
|
||||
# 7. 创建 Release
|
||||
echo "创建 Release $NEW_VERSION..."
|
||||
gitlink-cli release +create --owner $OWNER --repo $REPO \
|
||||
--tag $NEW_VERSION --name "$NEW_VERSION" --body "$RELEASE_NOTES"
|
||||
|
||||
echo "Release $NEW_VERSION 创建完成!"
|
||||
```
|
||||
|
||||
## AI Agent 集成示例
|
||||
|
||||
Claude Code 等 AI Agent 可以深度集成此工作流:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
# ----------------------------------------------------------------
|
||||
# ----------------------------------------------------------------
|
||||
# Scenario 1: Community Operations Automation
|
||||
# Flow: Issue auto-classify -> Assign responsible -> Weekly report -> Release notes
|
||||
#
|
||||
# Commands chained:
|
||||
# 1. issue +list -- fetch open issues
|
||||
# 2. issue +label-add -- add classification labels
|
||||
# 3. repo +members -- get repo members
|
||||
# 4. api PATCH -- assign responsible person
|
||||
# 5. pr +list -- collect merged PRs for weekly report
|
||||
# 6. wiki +create -- publish community weekly report
|
||||
# 7. release +create -- publish release notes
|
||||
# ----------------------------------------------------------------
|
||||
#Requires -Version 5.1
|
||||
|
||||
|
|
@ -26,11 +17,6 @@ Import-Module "$PSScriptRoot/lib/common.psm1" -Force
|
|||
|
||||
if ($Help) {
|
||||
Write-Host "Usage: powershell 01-community-ops.ps1 -Owner OWNER -Repo REPO [-WeeksAgo N] [-DryRun]"
|
||||
Write-Host ""
|
||||
Write-Host " -Owner OWNER Repository owner (org or user)"
|
||||
Write-Host " -Repo REPO Repository name"
|
||||
Write-Host " -WeeksAgo N Generate report for N weeks ago (default: 0 = this week)"
|
||||
Write-Host " -DryRun Preview actions without executing"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +24,6 @@ Check-Auth
|
|||
$r = Resolve-OwnerRepo $Owner $Repo
|
||||
$Owner = $r.Owner; $Repo = $r.Repo
|
||||
|
||||
# Classification keywords
|
||||
$BugKw = @('bug','error','crash','fault','fix')
|
||||
$FeatureKw = @('feature','enhancement','add','support','request')
|
||||
$QuestionKw = @('how','question','help')
|
||||
|
|
@ -49,7 +34,7 @@ Log-Title "Phase 1: Issue Auto-Classification"
|
|||
# ----------------------------------------------------------------
|
||||
|
||||
Log-Step "Fetching open issues..."
|
||||
$issuesJson = Invoke-GLCheck issue,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,100
|
||||
$issuesJson = Invoke-GLCheck "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100"
|
||||
if (-not $issuesJson) { Log-Err "Failed to fetch issues"; exit 1 }
|
||||
|
||||
$issues = @($issuesJson.data.issues)
|
||||
|
|
@ -60,7 +45,6 @@ $BugIds = @(); $FeatureIds = @(); $QuestionIds = @(); $DocsIds = @()
|
|||
|
||||
if ($issueCount -gt 0) {
|
||||
Log-Step "Classifying issues by content..."
|
||||
|
||||
foreach ($issue in $issues) {
|
||||
$id = $issue.id
|
||||
$title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "" }
|
||||
|
|
@ -96,7 +80,6 @@ if ($issueCount -gt 0) {
|
|||
Log-Info " Questions: $($QuestionIds.Count)"
|
||||
Log-Info " Docs: $($DocsIds.Count)"
|
||||
|
||||
# Apply labels
|
||||
$labelGroups = @(
|
||||
@{ Ids = $BugIds; Label = "bug" },
|
||||
@{ Ids = $FeatureIds; Label = "feature" },
|
||||
|
|
@ -107,7 +90,7 @@ if ($issueCount -gt 0) {
|
|||
if ($g.Ids.Count -gt 0) {
|
||||
Log-Step "Labeling $($g.Label) issues..."
|
||||
foreach ($id in $g.Ids) {
|
||||
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$id,--labels,$g.Label | Out-Null
|
||||
Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Repo, "--number", $id, "--labels", $g.Label | Out-Null
|
||||
}
|
||||
Log-Ok "Labeled $($g.Ids.Count) $($g.Label) issues"
|
||||
}
|
||||
|
|
@ -119,7 +102,7 @@ Log-Title "Phase 2: Assign Responsible Persons"
|
|||
# ----------------------------------------------------------------
|
||||
|
||||
Log-Step "Fetching repo members..."
|
||||
$membersJson = Invoke-GL repo,+members,--owner,$Owner,--repo,$Repo,--limit,50
|
||||
$membersJson = Invoke-GL "repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "50"
|
||||
$members = @()
|
||||
if ($membersJson) {
|
||||
$md = $membersJson.data
|
||||
|
|
@ -141,7 +124,7 @@ if ($memberLogins.Count -gt 0) {
|
|||
foreach ($id in $assignIds) {
|
||||
$assignee = $memberLogins[$idx % $memberLogins.Count]
|
||||
$bodyJson = "{`"assigned_to_id`": `"$assignee`"}"
|
||||
Invoke-GL api,PATCH,"/v1/$Owner/$Repo/issues/$id",--body,$bodyJson | Out-Null
|
||||
Invoke-GL "api", "PATCH", "/v1/$Owner/$Repo/issues/$id", "--body", $bodyJson | Out-Null
|
||||
Log-Info " Assigned #$id -> @$assignee"
|
||||
$idx++
|
||||
}
|
||||
|
|
@ -160,38 +143,128 @@ $weekEnd = Get-DateToday
|
|||
|
||||
Log-Step "Collecting weekly data (week of $weekStart)..."
|
||||
|
||||
$closedJson = Invoke-GL issue,+list,--owner,$Owner,--repo,$Repo,--state,closed,--limit,100
|
||||
$closedJson = Invoke-GL "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100"
|
||||
$closedCount = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 }
|
||||
|
||||
$mergedJson = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,merged,--limit,100
|
||||
$mergedJson = Invoke-GL "pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100"
|
||||
$mergedData = @()
|
||||
$mergedCount = 0
|
||||
if ($mergedJson) {
|
||||
$d = $mergedJson.data
|
||||
if ($d.issues) { $mergedData = @($d.issues) }
|
||||
elseif ($d.pulls) { $mergedData = @($d.pulls) }
|
||||
elseif ($d -is [array]) { $mergedData = $d }
|
||||
$mergedCount = $mergedData.Count
|
||||
}
|
||||
$mergedCount = $mergedData.Count
|
||||
|
||||
$newIssuesCount = $issueCount
|
||||
$totalClassified = $BugIds.Count + $FeatureIds.Count + $QuestionIds.Count + $DocsIds.Count
|
||||
|
||||
$reportTitle = "Community Weekly Report: $weekStart ~ $weekEnd"
|
||||
$reportBody = "# $reportTitle" + "`n`n"
|
||||
$reportBody += "## Summary" + "`n"
|
||||
$reportBody += "- New Issues: **$newIssuesCount**" + "`n"
|
||||
$reportBody += "- Closed Issues: **$closedCount**" + "`n"
|
||||
$reportBody += "- Merged PRs: **$mergedCount**" + "`n`n"
|
||||
$reportBody += "## Issue Classification" + "`n"
|
||||
$reportBody += "| Type | Count |" + "`n"
|
||||
$reportBody += "|------|-------|" + "`n"
|
||||
$reportBody += "## 概览" + "`n"
|
||||
$reportBody += "- 仓库: **$Owner/$Repo**" + "`n"
|
||||
$reportBody += "- 当前开放 Issue: **$newIssuesCount**" + "`n"
|
||||
$reportBody += "- 已关闭 Issue: **$closedCount**" + "`n"
|
||||
$reportBody += "- 已合并 PR: **$mergedCount**" + "`n`n"
|
||||
|
||||
# 分类汇总
|
||||
$reportBody += "## Issue 分类汇总" + "`n"
|
||||
$reportBody += "| 类型 | 数量 |" + "`n"
|
||||
$reportBody += "|------|------|" + "`n"
|
||||
$reportBody += "| Bug | $($BugIds.Count) |" + "`n"
|
||||
$reportBody += "| Feature | $($FeatureIds.Count) |" + "`n"
|
||||
$reportBody += "| Question | $($QuestionIds.Count) |" + "`n"
|
||||
$reportBody += "| Docs | $($DocsIds.Count) |" + "`n`n"
|
||||
$reportBody += "## Highlights" + "`n"
|
||||
$reportBody += "- Auto-classified and labeled $totalClassified issues" + "`n"
|
||||
$reportBody += "- Assigned responsible persons for bug and feature issues" + "`n`n"
|
||||
|
||||
# 开放 Issue 清单(含分类标记)
|
||||
$reportBody += "## 当前开放 Issue 清单" + "`n"
|
||||
$reportBody += "| # | 标题 | 分类 | 创建时间 |" + "`n"
|
||||
$reportBody += "|---|------|------|----------|" + "`n"
|
||||
foreach ($issue in $issues) {
|
||||
$id = $issue.id
|
||||
$title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "-" }
|
||||
$title = ($title -replace '\|', '\\|')
|
||||
$cat = if ($BugIds -contains $id) { "Bug" }
|
||||
elseif ($FeatureIds -contains $id) { "Feature" }
|
||||
elseif ($QuestionIds -contains $id) { "Question" }
|
||||
elseif ($DocsIds -contains $id) { "Docs" }
|
||||
else { "-" }
|
||||
$created = if ($issue.created_at) { $issue.created_at } else { "-" }
|
||||
$reportBody += "| #$id | $title | $cat | $created |" + "`n"
|
||||
}
|
||||
$reportBody += "`n"
|
||||
|
||||
# 已关闭 Issue 清单
|
||||
$reportBody += "## 近期已关闭 Issue" + "`n"
|
||||
if ($closedCount -gt 0) {
|
||||
$closedIssues = @($closedJson.data.issues)
|
||||
$closedLimit = [Math]::Min($closedCount, 15)
|
||||
$reportBody += "| # | 标题 |" + "`n"
|
||||
$reportBody += "|---|------|" + "`n"
|
||||
for ($i = 0; $i -lt $closedLimit; $i++) {
|
||||
$it = $closedIssues[$i]
|
||||
$cid = if ($it.id) { $it.id } elseif ($it.number) { $it.number } else { "-" }
|
||||
$ctitle = if ($it.subject) { $it.subject } elseif ($it.title) { $it.title } else { "-" }
|
||||
$ctitle = ($ctitle -replace '\|', '\\|')
|
||||
$reportBody += "| #$cid | $ctitle |" + "`n"
|
||||
}
|
||||
if ($closedCount -gt $closedLimit) {
|
||||
$reportBody += "| ... | 还有 $($closedCount - $closedLimit) 条 |`n"
|
||||
}
|
||||
} else {
|
||||
$reportBody += "_本周无关闭记录_" + "`n"
|
||||
}
|
||||
$reportBody += "`n"
|
||||
|
||||
# 已合并 PR 清单(含作者)
|
||||
$reportBody += "## 近期已合并 PR" + "`n"
|
||||
if ($mergedCount -gt 0) {
|
||||
$prLimit = [Math]::Min($mergedCount, 15)
|
||||
$reportBody += "| # | 标题 | 作者 |" + "`n"
|
||||
$reportBody += "|---|------|------|" + "`n"
|
||||
for ($i = 0; $i -lt $prLimit; $i++) {
|
||||
$pr = $mergedData[$i]
|
||||
$prId = if ($pr.id) { $pr.id } elseif ($pr.number) { $pr.number } else { "-" }
|
||||
$ptitle = if ($pr.subject) { $pr.subject } elseif ($pr.title) { $pr.title } else { "-" }
|
||||
$ptitle = ($ptitle -replace '\|', '\\|')
|
||||
$pauthor = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { "-" }
|
||||
$reportBody += "| #$prId | $ptitle | @$pauthor |" + "`n"
|
||||
}
|
||||
if ($mergedCount -gt $prLimit) {
|
||||
$reportBody += "| ... | 还有 $($mergedCount - $prLimit) 条合并 PR |`n"
|
||||
}
|
||||
} else {
|
||||
$reportBody += "_本周无合并记录_" + "`n"
|
||||
}
|
||||
$reportBody += "`n"
|
||||
|
||||
# 贡献者排行(按合并 PR 数)
|
||||
$reportBody += "## 贡献者排行(按合并 PR 数)" + "`n"
|
||||
if ($mergedCount -gt 0) {
|
||||
$contributorMap = @{}
|
||||
foreach ($pr in $mergedData) {
|
||||
$login = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { $null }
|
||||
if ($login) {
|
||||
if ($contributorMap.ContainsKey($login)) { $contributorMap[$login]++ }
|
||||
else { $contributorMap[$login] = 1 }
|
||||
}
|
||||
}
|
||||
$reportBody += "| 排名 | 贡献者 | 合并 PR 数 |" + "`n"
|
||||
$reportBody += "|------|--------|------------|" + "`n"
|
||||
$rank = 1
|
||||
foreach ($kv in ($contributorMap.GetEnumerator() | Sort-Object Value -Descending)) {
|
||||
$reportBody += "| $rank | @$($kv.Name) | $($kv.Value) |" + "`n"
|
||||
$rank++
|
||||
}
|
||||
} else {
|
||||
$reportBody += "_本周无合并记录_" + "`n"
|
||||
}
|
||||
$reportBody += "`n"
|
||||
|
||||
$reportBody += "## 本周自动化执行" + "`n"
|
||||
$reportBody += "- 自动分类并打标 Issue: **$totalClassified** 条" + "`n"
|
||||
$reportBody += "- 已为 Bug/Feature 类 Issue 指派负责人" + "`n`n"
|
||||
$reportBody += "---" + "`n"
|
||||
$reportBody += "*Auto-generated by gitlink-cli community-ops workflow*"
|
||||
|
||||
|
|
@ -200,12 +273,8 @@ Write-Host ""
|
|||
Write-Host $reportBody
|
||||
|
||||
Log-Step "Publishing weekly report to Wiki..."
|
||||
$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Repo,--title,$reportTitle,--body,$reportBody
|
||||
if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
|
||||
Log-Ok "Weekly report published to Wiki"
|
||||
} else {
|
||||
Log-Warn "Wiki publish may have failed (wiki module might not be enabled)"
|
||||
}
|
||||
$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $reportTitle, "--content", $reportBody
|
||||
if ($wikiResult -and $wikiResult.ok) { Log-Ok "Weekly report published to Wiki" } else { Log-Warn "Wiki publish may have failed" }
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
Log-Title "Phase 4: Auto-Publish Release Notes"
|
||||
|
|
@ -242,12 +311,8 @@ if ($closedCount -gt 0) {
|
|||
$releaseBody += "`n`n---`n*Auto-generated by gitlink-cli community-ops workflow*"
|
||||
|
||||
Log-Step "Creating release: $tagName..."
|
||||
$releaseResult = Invoke-GL release,+create,--owner,$Owner,--repo,$Repo,--tag,$tagName,--name,$releaseName,--body,$releaseBody
|
||||
if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) {
|
||||
Log-Ok "Release $tagName created successfully"
|
||||
} else {
|
||||
Log-Warn "Release creation may have failed (tag might already exist)"
|
||||
}
|
||||
$releaseResult = Invoke-GL "release", "+create", "--owner", $Owner, "--repo", $Repo, "--tag", $tagName, "--name", $releaseName, "--body", $releaseBody
|
||||
if ($releaseResult -and $releaseResult.ok) { Log-Ok "Release $tagName created successfully" } else { Log-Warn "Release creation may have failed (tag might already exist)" }
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
Log-Title "Community Operations Complete"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# ----------------------------------------------------------------
|
||||
# ----------------------------------------------------------------
|
||||
# Scenario 3: One-Click Project Initialization
|
||||
# Flow: Input description -> Create repo -> README/CONTRIBUTING/CI config ->
|
||||
# Initial Issues -> Branch protection -> Initial Release
|
||||
|
|
@ -53,7 +53,7 @@ Divider
|
|||
# -- Step 1: Create Repository --
|
||||
Log-Step "Creating repository..."
|
||||
$privateStr = if ($Private) { "true" } else { "false" }
|
||||
$repoResult = Invoke-GLCheck repo,+create,--owner,$Owner,--name,$Name,--description,$Description,--private,$privateStr
|
||||
$repoResult = Invoke-GLCheck "repo", "+create", "--owner", $Owner, "--name", $Name, "--description", $Description, "--private", $privateStr
|
||||
if ($repoResult) {
|
||||
Log-Ok "Repository created: $Owner/$Name"
|
||||
} else {
|
||||
|
|
@ -92,8 +92,8 @@ $readmeContent += "This project is licensed under the MIT License."
|
|||
|
||||
$wikiOk = $false
|
||||
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
||||
$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"README",--body,$readmeContent
|
||||
if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
|
||||
$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "README", "--content", $readmeContent
|
||||
if ($wikiResult -and (Get-JsonOk $wikiResult)) {
|
||||
Log-Ok "README created"
|
||||
$wikiOk = $true
|
||||
break
|
||||
|
|
@ -127,8 +127,8 @@ $contribContent += "- Include environment details"
|
|||
|
||||
$wikiOk = $false
|
||||
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
||||
$wikiContrib = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"CONTRIBUTING",--body,$contribContent
|
||||
if ($wikiContrib -and (Get-JsonOk ($wikiContrib | ConvertFrom-Json))) {
|
||||
$wikiContrib = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "CONTRIBUTING", "--content", $contribContent
|
||||
if ($wikiContrib -and (Get-JsonOk $wikiContrib)) {
|
||||
Log-Ok "CONTRIBUTING guide created"
|
||||
$wikiOk = $true
|
||||
break
|
||||
|
|
@ -150,7 +150,7 @@ $ciContent += "3. **Deploy**: Deploy to staging (master branch only)" + "`n`n"
|
|||
$ciContent += "### Configuration" + "`n`n"
|
||||
$ciContent += "Create a ``.gitlink-ci.yml`` file in the repository root."
|
||||
|
||||
Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"CI/CD Configuration",--body,$ciContent | Out-Null
|
||||
Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "CI/CD Configuration", "--content", $ciContent | Out-Null
|
||||
Log-Ok "CI/CD configuration guide created"
|
||||
|
||||
# -- Step 5: Create Initial Issues --
|
||||
|
|
@ -165,13 +165,13 @@ $issuesToCreate = @(
|
|||
)
|
||||
|
||||
foreach ($entry in $issuesToCreate) {
|
||||
$issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Name,--title,$entry.Title,--body,$entry.Body
|
||||
$issueResult = Invoke-GL "issue", "+create", "--owner", $Owner, "--repo", $Name, "--title", $entry.Title, "--body", $entry.Body
|
||||
if ($issueResult) {
|
||||
try {
|
||||
$issueJson = $issueResult | ConvertFrom-Json
|
||||
$issueJson = $issueResult
|
||||
$issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null }
|
||||
if ($issueNum) {
|
||||
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Name,--number,$issueNum,--labels,$entry.Label | Out-Null
|
||||
Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Name, "--number", $issueNum, "--labels", $entry.Label | Out-Null
|
||||
Log-Ok "Issue created: #$issueNum - $($entry.Title)"
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -182,8 +182,8 @@ foreach ($entry in $issuesToCreate) {
|
|||
|
||||
# -- Step 6: Protect Default Branch --
|
||||
Log-Step "Protecting master branch..."
|
||||
$protectResult = Invoke-GL branch,+protect,--owner,$Owner,--repo,$Name,--name,master
|
||||
if ($protectResult -and (Get-JsonOk ($protectResult | ConvertFrom-Json))) {
|
||||
$protectResult = Invoke-GL "branch", "+protect", "--owner", $Owner, "--repo", $Name, "--name", "master"
|
||||
if ($protectResult -and (Get-JsonOk $protectResult)) {
|
||||
Log-Ok "Branch 'master' protected"
|
||||
} else {
|
||||
Log-Warn "Branch protection may have failed (may require admin permissions)"
|
||||
|
|
@ -206,8 +206,8 @@ $releaseBody += "- [ ] Complete documentation" + "`n"
|
|||
$releaseBody += "- [ ] First feature implementation" + "`n`n"
|
||||
$releaseBody += "---`n*Auto-initialized by gitlink-cli project-init workflow*"
|
||||
|
||||
$releaseResult = Invoke-GL release,+create,--owner,$Owner,--repo,$Name,--tag,"v0.1.0",--name,"Initial Release",--body,$releaseBody
|
||||
if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) {
|
||||
$releaseResult = Invoke-GL "release", "+create", "--owner", $Owner, "--repo", $Name, "--tag", "v0.1.0", "--name", "Initial Release", "--body", $releaseBody
|
||||
if ($releaseResult -and (Get-JsonOk $releaseResult)) {
|
||||
Log-Ok "Release v0.1.0 created"
|
||||
} else {
|
||||
Log-Warn "Release creation may have failed"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# ----------------------------------------------------------------
|
||||
# ----------------------------------------------------------------
|
||||
# Scenario 4: Multi-Repo Collaboration
|
||||
# Flow: Cross-repo issue tracking -> PR status dashboard -> Coordinated release
|
||||
#
|
||||
|
|
@ -36,7 +36,7 @@ Check-Auth
|
|||
Log-Title "Multi-Repo Collaboration Dashboard"
|
||||
|
||||
Log-Step "Fetching repositories for org: $Org..."
|
||||
$reposJson = Invoke-GLCheck repo,+list,--user,$Org,--limit,100
|
||||
$reposJson = Invoke-GLCheck "repo", "+list", "--user", $Org, "--limit", "100"
|
||||
if (-not $reposJson) { Log-Err "Failed to fetch repos"; exit 1 }
|
||||
|
||||
$allRepos = @()
|
||||
|
|
@ -69,13 +69,13 @@ foreach ($repo in $repoList) {
|
|||
Divider
|
||||
Log-Step "Processing $Org/$repo..."
|
||||
|
||||
$issuesJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50
|
||||
$issuesJson = Invoke-GL "issue", "+list", "--owner", $Org, "--repo", $repo, "--state", "open", "--limit", "50"
|
||||
$openIssues = if ($issuesJson) { @($issuesJson.data.issues).Count } else { 0 }
|
||||
|
||||
$closedJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,closed,--limit,50
|
||||
$closedJson = Invoke-GL "issue", "+list", "--owner", $Org, "--repo", $repo, "--state", "closed", "--limit", "50"
|
||||
$closedIssues = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 }
|
||||
|
||||
$prsJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50
|
||||
$prsJson = Invoke-GL "pr", "+list", "--owner", $Org, "--repo", $repo, "--state", "open", "--limit", "50"
|
||||
$openPRs = 0
|
||||
if ($prsJson) {
|
||||
$pd = $prsJson.data
|
||||
|
|
@ -84,7 +84,7 @@ foreach ($repo in $repoList) {
|
|||
elseif ($pd -is [array]) { $openPRs = $pd.Count }
|
||||
}
|
||||
|
||||
$mergedJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,merged,--limit,50
|
||||
$mergedJson = Invoke-GL "pr", "+list", "--owner", $Org, "--repo", $repo, "--state", "merged", "--limit", "50"
|
||||
$mergedPRs = 0
|
||||
if ($mergedJson) {
|
||||
$md = $mergedJson.data
|
||||
|
|
@ -93,7 +93,7 @@ foreach ($repo in $repoList) {
|
|||
elseif ($md -is [array]) { $mergedPRs = $md.Count }
|
||||
}
|
||||
|
||||
$releaseJson = Invoke-GL release,+list,--owner,$Org,--repo,$repo,--limit,1
|
||||
$releaseJson = Invoke-GL "release", "+list", "--owner", $Org, "--repo", $repo, "--limit", "1"
|
||||
$latestRelease = "none"
|
||||
if ($releaseJson -and $releaseJson.data.releases) {
|
||||
$releases = @($releaseJson.data.releases)
|
||||
|
|
@ -186,8 +186,8 @@ if ($Release) {
|
|||
foreach ($repo in $repoList) {
|
||||
Log-Step "Creating release for $Org/$repo..."
|
||||
$relBody = "Coordinated release $Release for $Org/$repo"
|
||||
$relResult = Invoke-GL release,+create,--owner,$Org,--repo,$repo,--tag,$Release,--name,"Release $Release",--body,$relBody
|
||||
if ($relResult -and (Get-JsonOk ($relResult | ConvertFrom-Json))) {
|
||||
$relResult = Invoke-GL "release", "+create", "--owner", $Org, "--repo", $repo, "--tag", $Release, "--name", "Release $Release", "--body", $relBody
|
||||
if ($relResult -and (Get-JsonOk $relResult)) {
|
||||
Log-Ok "Release $Release created for $repo"
|
||||
} else {
|
||||
Log-Warn "Release creation failed for $repo (tag may already exist)"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# ----------------------------------------------------------------
|
||||
# ----------------------------------------------------------------
|
||||
# Scenario 5: Contributor Growth System
|
||||
# Flow: Collect data -> Calculate scores -> Generate HTML -> Publish Wiki -> Award badges
|
||||
#
|
||||
|
|
@ -54,12 +54,12 @@ Log-Title "Contributor Growth System: $Owner/$Repo"
|
|||
# -- Step 1: Collect Data --
|
||||
Log-Step "Collecting data..."
|
||||
|
||||
$issuesOpen = Invoke-GLCheck issue,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,100
|
||||
$issuesClosed = Invoke-GL issue,+list,--owner,$Owner,--repo,$Repo,--state,closed,--limit,100
|
||||
$issuesOpen = Invoke-GLCheck "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100"
|
||||
$issuesClosed = Invoke-GL "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100"
|
||||
$openCount = if ($issuesOpen) { @($issuesOpen.data.issues).Count } else { 0 }
|
||||
$closedCount = if ($issuesClosed) { @($issuesClosed.data.issues).Count } else { 0 }
|
||||
|
||||
$prsMerged = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,merged,--limit,100
|
||||
$prsMerged = Invoke-GL "pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100"
|
||||
$prMergedData = @()
|
||||
if ($prsMerged) {
|
||||
$pd = $prsMerged.data
|
||||
|
|
@ -69,7 +69,7 @@ if ($prsMerged) {
|
|||
}
|
||||
$prMergedCount = $prMergedData.Count
|
||||
|
||||
$membersJson = Invoke-GL repo,+members,--owner,$Owner,--repo,$Repo,--limit,100
|
||||
$membersJson = Invoke-GL "repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "100"
|
||||
$memberData = @()
|
||||
if ($membersJson) {
|
||||
$md = $membersJson.data
|
||||
|
|
@ -119,7 +119,7 @@ for ($i = 0; $i -lt $prMergedCount; $i++) {
|
|||
$contribData[$author].Merged++
|
||||
}
|
||||
if ($i -lt $prSample -and $prId -and $author) {
|
||||
$filesJson = Invoke-GL pr,+files,--owner,$Owner,--repo,$Repo,--id,$prId
|
||||
$filesJson = Invoke-GL "pr", "+files", "--owner", $Owner, "--repo", $Repo, "--id", $prId
|
||||
if ($filesJson -and $filesJson.data.files) {
|
||||
foreach ($f in $filesJson.data.files) {
|
||||
$add = if ($f.additions) { $f.additions } elseif ($f.addition) { $f.addition } else { 0 }
|
||||
|
|
@ -148,7 +148,7 @@ if ($issuesOpen) {
|
|||
for ($i = 0; $i -lt $commentSample; $i++) {
|
||||
$id = $openIssuesArr[$i].id
|
||||
if (-not $id) { continue }
|
||||
$detail = Invoke-GL issue,+view,--owner,$Owner,--repo,$Repo,--number,$id
|
||||
$detail = Invoke-GL "issue", "+view", "--owner", $Owner, "--repo", $Repo, "--number", $id
|
||||
if ($detail) {
|
||||
$commentCount = if ($detail.data.comment_journals_count) { $detail.data.comment_journals_count } else { 0 }
|
||||
if ($commentCount -gt 0) {
|
||||
|
|
@ -371,8 +371,8 @@ $wikiContent += $wikiRankRows + "`n"
|
|||
$wikiContent += "---" + "`n"
|
||||
$wikiContent += "*Auto-generated by gitlink-cli*"
|
||||
|
||||
$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Repo,--title,$wikiTitle,--body,$wikiContent
|
||||
if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) {
|
||||
$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $wikiTitle, "--content", $wikiContent
|
||||
if ($wikiResult -and (Get-JsonOk $wikiResult)) {
|
||||
Log-Ok "Published to Wiki: $wikiTitle"
|
||||
} else {
|
||||
Log-Warn "Wiki publish failed"
|
||||
|
|
@ -406,13 +406,13 @@ if ($Award) {
|
|||
}
|
||||
$issueBody += "`n`n---`n*Auto-awarded by gitlink-cli contributor-growth workflow*"
|
||||
|
||||
$issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Repo,--title,$issueTitle,--body,$issueBody
|
||||
$issueResult = Invoke-GL "issue", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $issueTitle, "--body", $issueBody
|
||||
if ($issueResult) {
|
||||
try {
|
||||
$issueJson = $issueResult | ConvertFrom-Json
|
||||
$issueJson = $issueResult
|
||||
$issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null }
|
||||
if ($issueNum) {
|
||||
Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$issueNum,--labels,"badge" | Out-Null
|
||||
Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Repo, "--number", $issueNum, "--labels", "badge" | Out-Null
|
||||
Log-Ok "Badge issue created: #$issueNum - $issueTitle ($($users.Count) recipients)"
|
||||
}
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
name: gitlink-workflows
|
||||
version: 1.1.0
|
||||
description: "GitLink 自动化工作流总入口:提供社区运营、代码审查、项目初始化、多仓库协同、贡献者成长、Release Notes、科研辅助等自动化功能的选择菜单。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
triggers:
|
||||
- "工作流"
|
||||
- "workflow"
|
||||
- "自动化"
|
||||
- "帮我跑"
|
||||
- "执行"
|
||||
- "科研"
|
||||
- "research"
|
||||
- "论文"
|
||||
- "citation"
|
||||
- "知识图谱"
|
||||
- "knowledge graph"
|
||||
- "热点追踪"
|
||||
- "合规检查"
|
||||
- "复现性"
|
||||
- "reproducibility"
|
||||
- "协作匹配"
|
||||
- "进度跟踪"
|
||||
- "项目洞察"
|
||||
- "引用格式"
|
||||
---
|
||||
|
||||
# gitlink-workflows(自动化工作流总入口)
|
||||
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../skills/gitlink-shared/SKILL.md`](../skills/gitlink-shared/SKILL.md) 了解认证和全局参数。
|
||||
|
||||
## 功能菜单
|
||||
|
||||
当用户说"工作流"、"自动化"、"帮我跑"等触发词时,展示以下菜单让用户选择。
|
||||
|
||||
当用户说"科研"、"论文"、"知识图谱"、"合规检查"等触发词时,直接跳转到科研辅助菜单。
|
||||
|
||||
```
|
||||
====== GitLink 自动化工作流 ======
|
||||
|
||||
请选择要执行的工作流:
|
||||
|
||||
1. 社区运营自动化
|
||||
→ Issue 自动分类、负责人分配、周报生成、Release Notes
|
||||
|
||||
2. 代码质量审查
|
||||
→ PR Review、AI 四维度评分、自动合并
|
||||
|
||||
3. 项目一键初始化
|
||||
→ 创建仓库、README、CI 配置、初始 Issues、分支保护
|
||||
|
||||
4. 多仓库协同
|
||||
→ 跨仓库 Issue/PR 追踪、状态 Dashboard、协同发版
|
||||
|
||||
5. 贡献者成长体系
|
||||
→ 数据收集、AHP 评分、排行榜、Wiki 发布、Badge 颁发
|
||||
|
||||
6. Release Notes 生成
|
||||
→ 收集 commits/PR/Issue、分类整理、生成 Notes
|
||||
|
||||
7. 科研辅助系统 🆕
|
||||
→ 项目洞察、热点追踪、合规复现、协作匹配、进度预警、论文引用
|
||||
|
||||
请输入编号(1-7)或功能名称:
|
||||
```
|
||||
|
||||
## 用户输入 → 工作流映射
|
||||
|
||||
| 用户输入 | 执行的工作流 |
|
||||
|----------|--------------|
|
||||
| `1` 或 "社区运营" | 读取 `workflow-community-ops.md` |
|
||||
| `2` 或 "代码审查" | 读取 `workflow-pr-review.md` |
|
||||
| `3` 或 "项目初始化" | 读取 `workflow-repo-setup.md` |
|
||||
| `4` 或 "多仓库" | 读取 `workflow-multi-repo.md` |
|
||||
| `5` 或 "贡献者" | 读取 `workflow-contributor-growth.md` |
|
||||
| `6` 或 "Release Notes" | 读取 `workflow-release-notes.md` |
|
||||
| `7` 或 "科研" / "科研辅助" | 读取 `gitlink-research` Skill → 展示科研辅助菜单 |
|
||||
|
||||
## 执行流程
|
||||
|
||||
1. **展示菜单** — 列出所有可用工作流
|
||||
2. **获取用户选择** — 用户输入编号或功能名称
|
||||
3. **读取 Reference** — 根据选择读取对应的 reference 文件
|
||||
4. **确认参数** — 询问必要的参数(owner/repo 等)
|
||||
5. **执行工作流** — 按照 reference 文件的步骤执行
|
||||
6. **展示结果** — 输出执行结果和摘要
|
||||
|
||||
## 工作流 Reference 文件
|
||||
|
||||
所有工作流的详细步骤在以下 reference 文件中:
|
||||
|
||||
| 工作流 | Reference 文件 |
|
||||
|--------|----------------|
|
||||
| 社区运营 | [`workflow-community-ops.md`](../skills/gitlink-workflow/references/workflow-community-ops.md) |
|
||||
| 代码审查 | [`workflow-pr-review.md`](../skills/gitlink-workflow/references/workflow-pr-review.md) |
|
||||
| 项目初始化 | [`workflow-repo-setup.md`](../skills/gitlink-workflow/references/workflow-repo-setup.md) |
|
||||
| 多仓库协同 | [`workflow-multi-repo.md`](../skills/gitlink-workflow/references/workflow-multi-repo.md) |
|
||||
| 贡献者成长 | [`workflow-contributor-growth.md`](../skills/gitlink-workflow/references/workflow-contributor-growth.md) |
|
||||
| Release Notes | [`workflow-release-notes.md`](../skills/gitlink-workflow/references/workflow-release-notes.md) |
|
||||
| 科研辅助 | [`SKILL.md`](../skills/gitlink-research/SKILL.md) — GitLink 科研辅助系统总入口 |
|
||||
|
||||
## 快捷触发
|
||||
|
||||
用户也可以直接说特定意图,跳过菜单直接执行:
|
||||
|
||||
| 用户说的话 | 直接执行 |
|
||||
|------------|----------|
|
||||
| "帮我跑一下社区运营" | `workflow-community-ops` |
|
||||
| "审查一下这个 PR" | `workflow-pr-review` |
|
||||
| "创建一个新项目" | `workflow-repo-setup` |
|
||||
| "看看组织下所有仓库" | `workflow-multi-repo` |
|
||||
| "生成贡献者排行榜" | `workflow-contributor-growth` |
|
||||
| "生成 Release Notes" | `workflow-release-notes` |
|
||||
| "分析这个仓库的科研价值" | `gitlink-research` 场景 1 |
|
||||
| "帮我追踪 NLP 热点" | `gitlink-research` 场景 2 |
|
||||
| "检查项目的可复现性" | `gitlink-research` 场景 3 |
|
||||
| "找科研合作者" | `gitlink-research` 场景 4 |
|
||||
| "看看项目进度有没有风险" | `gitlink-research` 场景 5 |
|
||||
| "生成这个项目的论文引用" | `gitlink-research` 场景 6 |
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 说明 | 获取方式 |
|
||||
|------|------|----------|
|
||||
| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 |
|
||||
| `--org` | 组织名称 | 询问用户(多仓库协同时需要) |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 所有写入操作前必须确认用户意图
|
||||
- 自动从 git remote 解析 owner/repo,解析失败时询问用户
|
||||
- 每个工作流的具体步骤见对应的 reference 文件
|
||||
- 支持 `--dry-run` 预览模式(部分工作流)
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-shared](../skills/gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
- [gitlink-workflow](../skills/gitlink-workflow/SKILL.md) — AI 工作流详情
|
||||
- [gitlink-changelog](../skills/gitlink-changelog/SKILL.md) — Release Notes 生成
|
||||
- [gitlink-research](../skills/gitlink-research/SKILL.md) — GitLink 科研辅助系统(6 大场景)
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# GitLink 科研辅助 — 场景 1:仓库级科研项目洞察 (PowerShell)
|
||||
param(
|
||||
[string]$Owner, [string]$Repo, [string]$Output = "",
|
||||
[switch]$NoWiki, [switch]$DryRun
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Import-Module "$ScriptDir\lib\common.psm1" -Force
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
Check-Auth
|
||||
$resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo
|
||||
$Owner = $resolved.Owner; $Repo = $resolved.Repo
|
||||
$Today = Get-Date -Format "yyyy-MM-dd"
|
||||
$OutputDir = Join-Path $ScriptDir "..\output"
|
||||
if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null }
|
||||
$OutputFile = if ($Output) { $Output } else { Join-Path $OutputDir "research-insights-${Repo}-${Today}.html" }
|
||||
|
||||
Log-Title "GitLink 科研辅助 — 仓库级项目洞察"
|
||||
|
||||
# 1. Repo metadata
|
||||
Log-Step "1/6 获取仓库元数据..."
|
||||
$repoJson = Invoke-GLCheck @("repo", "+info", "--owner", $Owner, "--repo", $Repo)
|
||||
$repoName = $repoJson.data.name ?? $repoJson.data.full_name ?? $Repo
|
||||
$repoDesc = $repoJson.data.description ?? "No description"
|
||||
$repoLang = $repoJson.data.language ?? "Unknown"
|
||||
$stars = [int]($repoJson.data.stars_count ?? $repoJson.data.stars ?? 0)
|
||||
$forks = [int]($repoJson.data.forks_count ?? $repoJson.data.forks ?? 0)
|
||||
$openIssues = [int]($repoJson.data.open_issues_count ?? 0)
|
||||
$updatedAt = $repoJson.data.updated_at ?? ""
|
||||
|
||||
Log-Info " 名称: $repoName | 语言: $repoLang | Stars: $stars"
|
||||
|
||||
# 2. Issues
|
||||
Log-Step "2/6 收集 Issue 数据..."
|
||||
$openIssuesJson = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100")
|
||||
$closedIssuesJson = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100")
|
||||
$totalOpen = if ($openIssuesJson.ok) { @($openIssuesJson.data.issues ?? $openIssuesJson.data).Count } else { 0 }
|
||||
$totalClosed = if ($closedIssuesJson.ok) { @($closedIssuesJson.data.issues ?? $closedIssuesJson.data).Count } else { 0 }
|
||||
|
||||
# 3. PRs
|
||||
Log-Step "3/6 收集 PR 数据..."
|
||||
$mergedPrsJson = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100")
|
||||
$openPrsJson = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "50")
|
||||
$totalMerged = if ($mergedPrsJson.ok) { @($mergedPrsJson.data.issues ?? $mergedPrsJson.data.pulls ?? $mergedPrsJson.data).Count } else { 0 }
|
||||
$totalOpenPrs = if ($openPrsJson.ok) { @($openPrsJson.data.issues ?? $openPrsJson.data.pulls ?? $openPrsJson.data).Count } else { 0 }
|
||||
|
||||
# 4. Releases
|
||||
Log-Step "4/6 收集 Release 数据..."
|
||||
$releasesJson = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "20")
|
||||
$releaseCount = if ($releasesJson.ok) { @($releasesJson.data).Count } else { 0 }
|
||||
|
||||
# 5. CI
|
||||
Log-Step "5/6 收集 CI 数据..."
|
||||
$ciJson = Invoke-GL @("ci", "+builds", "--owner", $Owner, "--repo", $Repo, "--limit", "20")
|
||||
$ciBuilds = if ($ciJson.ok) { @($ciJson.data).Count } else { 0 }
|
||||
|
||||
# 6. Members
|
||||
Log-Step "6/6 获取贡献者..."
|
||||
$membersJson = Invoke-GL @("repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "50")
|
||||
$memberCount = if ($membersJson.ok) {
|
||||
$d = if ($membersJson.data.members) { $membersJson.data.members } else { $membersJson.data }
|
||||
if ($d -is [array]) { $d.Count } else { 0 }
|
||||
} else { 0 }
|
||||
|
||||
# Hotness calculation
|
||||
$starsNorm = [Math]::Min($stars / 1000 * 100, 100)
|
||||
$forksNorm = [Math]::Min($forks / 200 * 100, 100)
|
||||
$issuesScore = [Math]::Min($totalOpen / 50 * 100, 100)
|
||||
$prsScore = [Math]::Min(($totalMerged + $totalOpenPrs) / 30 * 100, 100)
|
||||
$relScore = [Math]::Min($releaseCount / 10 * 100, 100)
|
||||
$daysSince = if ($updatedAt) { try { ((Get-Date) - [DateTime]$updatedAt.Substring(0, 10)).Days } catch { 365 } } else { 365 }
|
||||
$recency = if ($daysSince -le 30) { 100 } elseif ($daysSince -le 90) { 50 } else { 10 }
|
||||
$hotness = [Math]::Round($starsNorm * 0.15 + $forksNorm * 0.10 + $issuesScore * 0.20 + $prsScore * 0.20 + $relScore * 0.15 + $recency * 0.10, 1)
|
||||
|
||||
$prMergeRate = if (($totalMerged + $totalOpenPrs) -gt 0) { [Math]::Round($totalMerged / ($totalMerged + $totalOpenPrs) * 100, 1) } else { 0 }
|
||||
|
||||
Log-Ok "热度评分: ${hotness}/100"
|
||||
Log-Info " Issues: $totalOpen 开放 / $totalClosed 关闭"
|
||||
Log-Info " PR 合并率: ${prMergeRate}% | Releases: $releaseCount | 贡献者: $memberCount"
|
||||
|
||||
# Summary output
|
||||
Divider
|
||||
Write-Host "====== 报告摘要 ======" -ForegroundColor White
|
||||
Write-Host " 仓库: $Owner/$Repo"
|
||||
Write-Host " 语言: $repoLang"
|
||||
Write-Host " 热度评分: $hotness/100"
|
||||
Write-Host " Issues: $totalOpen 开放 / $totalClosed 关闭"
|
||||
Write-Host " PR 合并率: ${prMergeRate}%"
|
||||
Write-Host " 贡献者: $memberCount 人"
|
||||
Divider
|
||||
Log-Ok "分析完成"
|
||||
|
|
@ -0,0 +1,521 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# GitLink 科研辅助 — 场景 1:仓库级科研项目洞察
|
||||
# ============================================================
|
||||
# 对单个 GitLink 仓库深度分析:项目定位、技术栈、活动健康、
|
||||
# 贡献者网络、热度评分,生成综合 HTML 报告 + Wiki 页面
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
trap '' PIPE
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
# ── Configuration ────────────────────────────────────────────────────
|
||||
OUTPUT_DIR="${SCRIPT_DIR}/../../output"
|
||||
OUTPUT_FILE=""
|
||||
PUBLISH_WIKI="true"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: 06-research-insights.sh --owner <owner> --repo <repo> [options]
|
||||
|
||||
Options:
|
||||
--owner <owner> Repository owner (required)
|
||||
--repo <repo> Repository name (required)
|
||||
--output <file> Output HTML file path (default: output/research-insights-{repo}-{date}.html)
|
||||
--no-wiki Skip publishing to Wiki
|
||||
--dry-run Preview mode
|
||||
|
||||
Examples:
|
||||
06-research-insights.sh --owner zzx-coder --repo gitlink-cli
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_common_args "$@"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||||
--no-wiki) PUBLISH_WIKI="false"; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Helper: count array elements in JSON ──────────────────────────────
|
||||
json_count() {
|
||||
echo "$1" | jq -r "$2 | length" 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
# ── Helper: extract keywords from text ────────────────────────────────
|
||||
extract_topics() {
|
||||
local text="$1"
|
||||
local topics=""
|
||||
for kw in "machine learning" "deep learning" "neural network" "NLP" "computer vision" \
|
||||
"reinforcement learning" "GAN" "transformer" "LLM" "RAG" "agent" \
|
||||
"bioinformatics" "genomics" "computational biology" "drug discovery" \
|
||||
"robotics" "autonomous" "simulation" "optimization" "benchmark" \
|
||||
"dataset" "pre-trained" "fine-tuning" "distributed" "federated" \
|
||||
"graph neural" "knowledge graph" "recommender" "anomaly detection" \
|
||||
"scientific computing" "high performance" "HPC" "quantum" \
|
||||
"climate" "weather" "physics" "chemistry" "materials" \
|
||||
"machine learning" "深度学习" "神经网络" "自然语言" "计算机视觉" \
|
||||
"机器学习" "强化学习" "大语言模型" "知识图谱" "推荐系统"; do
|
||||
if echo "$text" | grep -qi "$kw"; then
|
||||
topics="${topics}${kw}, "
|
||||
fi
|
||||
done
|
||||
echo "${topics%, }"
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
log_title "GitLink 科研辅助 — 仓库级项目洞察"
|
||||
|
||||
check_auth
|
||||
require_owner_repo
|
||||
local today
|
||||
today=$(date_today)
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
local REPO_FULL="${OWNER}/${REPO}"
|
||||
OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/research-insights-${REPO}-${today}.html}"
|
||||
|
||||
# ═══ Step 1: Repo Metadata ═══
|
||||
log_step "1/6 获取仓库元数据..."
|
||||
local repo_json
|
||||
repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO")
|
||||
local repo_name repo_desc repo_lang stars forks open_issues created_at updated_at
|
||||
repo_name=$(json_get "$repo_json" '.data.name // .data.full_name // "'"$REPO"' "')
|
||||
repo_desc=$(json_get "$repo_json" '.data.description // "No description"')
|
||||
repo_lang=$(json_get "$repo_json" '.data.language // "Unknown"')
|
||||
stars=$(json_get "$repo_json" '.data.stars_count // .data.stars // 0')
|
||||
forks=$(json_get "$repo_json" '.data.forks_count // .data.forks // 0')
|
||||
open_issues=$(json_get "$repo_json" '.data.open_issues_count // .data.open_issues // 0')
|
||||
created_at=$(json_get "$repo_json" '.data.created_at // ""')
|
||||
updated_at=$(json_get "$repo_json" '.data.updated_at // ""')
|
||||
|
||||
log_info " 名称: $repo_name"
|
||||
log_info " 语言: $repo_lang"
|
||||
log_info " Stars: $stars | Forks: $forks | Open Issues: $open_issues"
|
||||
|
||||
# ═══ Step 2: Tech Stack Detection ═══
|
||||
log_step "2/6 检测技术栈..."
|
||||
local tech_stack="" file_count=0
|
||||
# Tech stack from repo language (API-based file listing not available on GitLink)
|
||||
tech_stack="$repo_lang"
|
||||
local research_features=""
|
||||
local names_list="" ext_counts="" file_count=0
|
||||
|
||||
# Attempt to get file listing via repo info (limited info available)
|
||||
local sub_json
|
||||
sub_json=$(gl_run api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" 2>/dev/null)
|
||||
# sub_entries may return HTML page if API not available; guard carefully
|
||||
if [[ "$(json_ok "$sub_json")" == "true" ]]; then
|
||||
# Check if .data is actually an array (not HTML string)
|
||||
local data_type
|
||||
data_type=$(echo "$sub_json" | jq -r '(.data | type) // "string"' 2>/dev/null)
|
||||
if [[ "$data_type" == "array" ]]; then
|
||||
file_count=$(json_count "$sub_json" '.data')
|
||||
ext_counts=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null | awk -F. '{if(NF>1) print $NF}' | sort | uniq -c | sort -rn | head -15)
|
||||
names_list=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null)
|
||||
|
||||
local ecosystem=""
|
||||
if echo "$names_list" | grep -q "go.mod"; then ecosystem="$ecosystem Go"; fi
|
||||
if echo "$names_list" | grep -q "package.json"; then ecosystem="$ecosystem Node.js"; fi
|
||||
if echo "$names_list" | grep -qE "requirements.txt|pyproject.toml|setup.py|setup.cfg|Pipfile"; then ecosystem="$ecosystem Python"; fi
|
||||
if echo "$names_list" | grep -q "Cargo.toml"; then ecosystem="$ecosystem Rust"; fi
|
||||
if echo "$names_list" | grep -q "CMakeLists.txt"; then ecosystem="$ecosystem C/C++"; fi
|
||||
if echo "$names_list" | grep -qE "pom.xml|build.gradle"; then ecosystem="$ecosystem Java/Kotlin"; fi
|
||||
if echo "$names_list" | grep -q "CITATION.cff"; then ecosystem="$ecosystem +CITATION.cff"; fi
|
||||
tech_stack=$(echo "$ecosystem" | sed 's/^ *//')
|
||||
[[ -z "$tech_stack" ]] && tech_stack="$repo_lang"
|
||||
|
||||
# Detect research features
|
||||
if echo "$names_list" | grep -qE "Dockerfile|docker-compose"; then research_features="$research_features Docker"; fi
|
||||
if echo "$names_list" | grep -qE "^data/|^datasets/"; then research_features="$research_features 数据集目录"; fi
|
||||
if echo "$names_list" | grep -q "\.ipynb"; then research_features="$research_features Jupyter"; fi
|
||||
if echo "$names_list" | grep -qE "^scripts/|^experiments/"; then research_features="$research_features 实验脚本"; fi
|
||||
fi
|
||||
fi
|
||||
log_info " 技术栈: ${tech_stack:-未知}"
|
||||
|
||||
# ═══ Step 3: Project Positioning ═══
|
||||
log_step "3/6 提取项目定位..."
|
||||
local readme_text="" project_topics="" doi_found=""
|
||||
readme_text=$(gl_run api GET "raw/$OWNER/$REPO/master/README.md" 2>/dev/null)
|
||||
if [[ "$(json_ok "$readme_text")" == "true" ]]; then
|
||||
readme_text=$(echo "$readme_text" | jq -r '.data // ""' 2>/dev/null)
|
||||
else
|
||||
readme_text=""
|
||||
fi
|
||||
|
||||
project_topics=$(extract_topics "$repo_desc $readme_text")
|
||||
doi_found=$(echo "$repo_desc $readme_text" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1 || echo "")
|
||||
|
||||
log_info " 领域关键词: ${project_topics:-未检测到}"
|
||||
[[ -n "$doi_found" ]] && log_info " DOI: $doi_found"
|
||||
|
||||
# ═══ Step 4: Activity Health ═══
|
||||
log_step "4/6 计算活动健康指标..."
|
||||
|
||||
# Issues
|
||||
local open_issues_json closed_issues_json
|
||||
open_issues_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state open --limit 100)
|
||||
closed_issues_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state closed --limit 100)
|
||||
|
||||
local total_open total_closed
|
||||
total_open=0; total_closed=0
|
||||
if [[ "$(json_ok "$open_issues_json")" == "true" ]]; then
|
||||
total_open=$(json_count "$open_issues_json" '(.data.issues // .data)')
|
||||
fi
|
||||
if [[ "$(json_ok "$closed_issues_json")" == "true" ]]; then
|
||||
total_closed=$(json_count "$closed_issues_json" '(.data.issues // .data)')
|
||||
fi
|
||||
|
||||
# PRs
|
||||
local merged_prs_json open_prs_json
|
||||
merged_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state merged --limit 100)
|
||||
open_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state open --limit 50)
|
||||
|
||||
local total_merged=0 total_open_prs=0
|
||||
if [[ "$(json_ok "$merged_prs_json")" == "true" ]]; then
|
||||
total_merged=$(json_count "$merged_prs_json" '(.data.issues // .data.pulls // .data)')
|
||||
fi
|
||||
if [[ "$(json_ok "$open_prs_json")" == "true" ]]; then
|
||||
total_open_prs=$(json_count "$open_prs_json" '(.data.issues // .data.pulls // .data)')
|
||||
fi
|
||||
|
||||
# Releases
|
||||
local releases_json release_count=0
|
||||
releases_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 20)
|
||||
if [[ "$(json_ok "$releases_json")" == "true" ]]; then
|
||||
release_count=$(json_count "$releases_json" '.data.releases')
|
||||
fi
|
||||
|
||||
# CI
|
||||
local ci_json ci_builds=0 ci_success=0
|
||||
ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 20)
|
||||
if [[ "$(json_ok "$ci_json")" == "true" ]]; then
|
||||
ci_builds=$(json_count "$ci_json" '.data')
|
||||
ci_success=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
local pr_merge_rate=0
|
||||
if [[ $((total_merged + total_open_prs)) -gt 0 ]]; then
|
||||
pr_merge_rate=$(awk "BEGIN { printf \"%.1f\", $total_merged / ($total_merged + $total_open_prs) * 100 }")
|
||||
fi
|
||||
|
||||
local ci_pass_rate=0
|
||||
if [[ $ci_builds -gt 0 ]]; then
|
||||
ci_pass_rate=$(awk "BEGIN { printf \"%.1f\", $ci_success / $ci_builds * 100 }")
|
||||
fi
|
||||
|
||||
log_info " Issues: $total_open 开放 / $total_closed 已关闭"
|
||||
log_info " PRs: $total_open_prs 开放 / $total_merged 已合并 (合并率: ${pr_merge_rate}%)"
|
||||
log_info " Releases: $release_count | CI 通过率: ${ci_pass_rate}%"
|
||||
|
||||
# ═══ Step 5: Contributor Network ═══
|
||||
log_step "5/6 构建贡献者网络..."
|
||||
local members_json member_count=0 members_list="[]"
|
||||
members_json=$(gl_run repo +members --owner "$OWNER" --repo "$REPO" --limit 50)
|
||||
if [[ "$(json_ok "$members_json")" == "true" ]]; then
|
||||
member_count=$(json_count "$members_json" '(.data.members // .data)')
|
||||
# Extract member logins as JSON array
|
||||
members_list=$(echo "$members_json" | jq -c '(.data.members // .data | if type == "array" then [.[].login // .[].user.login // empty] else [] end)' 2>/dev/null || echo "[]")
|
||||
fi
|
||||
log_info " 贡献者数: $member_count"
|
||||
|
||||
# ═══ Step 6: Hotness Score ═══
|
||||
log_step "6/6 计算热度评分..."
|
||||
|
||||
# Compute hotness
|
||||
local stars_norm forks_norm issues_active prs_active releases_active recency_factor
|
||||
local max_stars=1000 max_forks=200
|
||||
stars_norm=$(awk "BEGIN { printf \"%.1f\", ($stars / $max_stars > 1) ? 100 : ($stars / $max_stars * 100) }")
|
||||
forks_norm=$(awk "BEGIN { printf \"%.1f\", ($forks / $max_forks > 1) ? 100 : ($forks / $max_forks * 100) }")
|
||||
issues_active=$total_open
|
||||
[[ $issues_active -gt 50 ]] && issues_active=50
|
||||
issues_active=$(awk "BEGIN { printf \"%.1f\", $issues_active / 50 * 100 }")
|
||||
prs_active=$(awk "BEGIN { printf \"%.1f\", ($total_merged + $total_open_prs) / 30 * 100 }")
|
||||
[[ $(echo "$prs_active > 100" | bc 2>/dev/null || echo "0") -eq 1 ]] && prs_active=100
|
||||
releases_active=$(awk "BEGIN { printf \"%.1f\", $release_count / 10 * 100 }")
|
||||
[[ $(echo "$releases_active > 100" | bc 2>/dev/null || echo "0") -eq 1 ]] && releases_active=100
|
||||
|
||||
# Recency: days since last update
|
||||
local days_since_update=365
|
||||
if [[ -n "$updated_at" ]]; then
|
||||
days_since_update=$(days_between "${updated_at:0:10}" "$today")
|
||||
[[ -z "$days_since_update" ]] && days_since_update=365
|
||||
fi
|
||||
if [[ $days_since_update -le 30 ]]; then recency_factor=100
|
||||
elif [[ $days_since_update -le 90 ]]; then recency_factor=50
|
||||
else recency_factor=10; fi
|
||||
|
||||
local hotness
|
||||
hotness=$(weighted_sum "$stars_norm" 0.15 "$forks_norm" 0.10 "$issues_active" 0.20 "$prs_active" 0.20 "$releases_active" 0.15 0 0.10 "$recency_factor" 0.10)
|
||||
hotness=$(printf "%.1f" "$hotness")
|
||||
|
||||
local hotness_label
|
||||
if awk "BEGIN { exit ($hotness >= 50) ? 0 : 1 }"; then hotness_label="Hot"
|
||||
elif awk "BEGIN { exit ($hotness >= 30) ? 0 : 1 }"; then hotness_label="Warm"
|
||||
else hotness_label="Cool"; fi
|
||||
|
||||
log_ok "热度评分: ${hotness}/100 (${hotness_label})"
|
||||
|
||||
# ═══ Generate Report ═══
|
||||
log_step "生成综合报告..."
|
||||
|
||||
# JSON data for HTML embedding
|
||||
local json_data
|
||||
json_data=$(cat <<JSONEOF
|
||||
{
|
||||
"repo": "${REPO_FULL}",
|
||||
"name": "$(echo "$repo_name" | sed 's/"/\\"/g')",
|
||||
"description": "$(echo "$repo_desc" | sed 's/"/\\"/g')",
|
||||
"language": "${repo_lang}",
|
||||
"tech_stack": "${tech_stack:-$repo_lang}",
|
||||
"stars": ${stars:-0},
|
||||
"forks": ${forks:-0},
|
||||
"open_issues": ${open_issues:-0},
|
||||
"created_at": "${created_at:0:10}",
|
||||
"updated_at": "${updated_at:0:10}",
|
||||
"hotness": ${hotness:-0},
|
||||
"hotness_label": "${hotness_label}",
|
||||
"total_open_issues": ${total_open:-0},
|
||||
"total_closed_issues": ${total_closed:-0},
|
||||
"total_merged_prs": ${total_merged:-0},
|
||||
"total_open_prs": ${total_open_prs:-0},
|
||||
"pr_merge_rate": ${pr_merge_rate:-0},
|
||||
"release_count": ${release_count:-0},
|
||||
"ci_builds": ${ci_builds:-0},
|
||||
"ci_pass_rate": ${ci_pass_rate:-0},
|
||||
"member_count": ${member_count:-0},
|
||||
"research_features": "$(echo "$research_features" | sed 's/^ *//')",
|
||||
"project_topics": "$(echo "$project_topics" | sed 's/"/\\"/g')",
|
||||
"doi": "${doi_found}",
|
||||
"days_since_update": ${days_since_update:-0},
|
||||
"report_date": "${today}"
|
||||
}
|
||||
JSONEOF
|
||||
)
|
||||
|
||||
# Generate HTML
|
||||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
cat > "$OUTPUT_FILE" <<HTMLEOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${REPO_FULL} — 科研项目洞察报告</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #283593 50%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||||
.header h1 { font-size: 28px; margin-bottom: 8px; }
|
||||
.header .subtitle { opacity: 0.85; font-size: 14px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.card .label { font-size: 12px; color: #888; text-transform: uppercase; margin-bottom: 6px; }
|
||||
.card .value { font-size: 28px; font-weight: 700; }
|
||||
.card .value.hot { color: #e53935; }
|
||||
.card .value.warm { color: #f57c00; }
|
||||
.card .value.cool { color: #1565c0; }
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
.chart { width: 100%; height: 350px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 14px; }
|
||||
th { background: #f5f7fa; color: #555; font-weight: 600; }
|
||||
.tag { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; margin: 2px; }
|
||||
.tag.lang { background: #e3f2fd; color: #1565c0; }
|
||||
.tag.research { background: #e8f5e9; color: #2e7d32; }
|
||||
.tag.warn { background: #fff3e0; color: #e65100; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>${REPO_FULL}</h1>
|
||||
<div class="subtitle">科研项目洞察报告 — ${today}</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="label">热度评分</div>
|
||||
<div class="value $(echo "$hotness_label" | tr '[:upper:]' '[:lower:]')">${hotness}</div>
|
||||
<div class="label">${hotness_label}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Stars</div>
|
||||
<div class="value">${stars}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Forks</div>
|
||||
<div class="value">${forks}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">贡献者</div>
|
||||
<div class="value">${member_count}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">开放 Issues</div>
|
||||
<div class="value">${total_open}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">PR 合并率</div>
|
||||
<div class="value">${pr_merge_rate}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<h2>项目概况</h2>
|
||||
<table>
|
||||
<tr><th>项目名称</th><td>${repo_name}</td></tr>
|
||||
<tr><th>描述</th><td>${repo_desc}</td></tr>
|
||||
<tr><th>主要语言</th><td><span class="tag lang">${repo_lang}</span></td></tr>
|
||||
<tr><th>技术栈</th><td>$(echo "$tech_stack" | sed 's/ /\n/g' | while read -r t; do [[ -n "$t" ]] && echo "<span class=\"tag lang\">$t</span>"; done)</td></tr>
|
||||
<tr><th>创建时间</th><td>${created_at:0:10}</td></tr>
|
||||
<tr><th>最后更新</th><td>${updated_at:0:10} (${days_since_update} 天前)</td></tr>
|
||||
<tr><th>科研特征</th><td>$(echo "$research_features" | sed 's/ /\n/g' | while read -r f; do [[ -n "$f" ]] && echo "<span class=\"tag research\">$f</span>"; done)</td></tr>
|
||||
$( [[ -n "$doi_found" ]] && echo "<tr><th>DOI</th><td><a href=\"https://doi.org/${doi_found}\">${doi_found}</a></td></tr>" )
|
||||
$( [[ -n "$project_topics" ]] && echo "<tr><th>领域主题</th><td>$(echo "$project_topics" | tr ',' '\n' | while read -r t; do [[ -n "$t" ]] && echo "<span class=\"tag lang\">$(echo $t | xargs)</span>"; done)</td></tr>" )
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>活动概览</h2>
|
||||
<div id="activityChart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<h2>健康指标</h2>
|
||||
<table>
|
||||
<tr><th>指标</th><th>数值</th><th>状态</th></tr>
|
||||
<tr><td>Issue 总量</td><td>${total_open} 开放 / ${total_closed} 已关闭</td><td>$( [[ $total_open -gt 20 ]] && echo "<span class=\"tag warn\">需关注</span>" || echo "<span class=\"tag research\">正常</span>" )</td></tr>
|
||||
<tr><td>PR 合并率</td><td>${pr_merge_rate}%</td><td>$( awk "BEGIN { if(${pr_merge_rate} > 70) print \"<span class=\\\"tag research\\\">健康</span>\"; else print \"<span class=\\\"tag warn\\\">需改进</span>\" }" )</td></tr>
|
||||
<tr><td>Release 数</td><td>${release_count}</td><td>$( [[ $release_count -gt 0 ]] && echo "<span class=\"tag research\">已发布</span>" || echo "<span class=\"tag warn\">未发布</span>" )</td></tr>
|
||||
<tr><td>CI 通过率</td><td>${ci_pass_rate}% (${ci_builds} 次构建)</td><td>$( awk "BEGIN { if(${ci_pass_rate} > 80) print \"<span class=\\\"tag research\\\">稳定</span>\"; else print \"<span class=\\\"tag warn\\\">不稳定</span>\" }" )</td></tr>
|
||||
<tr><td>贡献者数</td><td>${member_count} 人</td><td>$( [[ $member_count -gt 3 ]] && echo "<span class=\"tag research\">活跃社区</span>" || echo "<span class=\"tag warn\">单人项目</span>" )</td></tr>
|
||||
<tr><td>活跃度</td><td>${days_since_update} 天前更新</td><td>$( [[ $days_since_update -le 30 ]] && echo "<span class=\"tag research\">活跃</span>" || echo "<span class=\"tag warn\">不活跃</span>" )</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>热度构成</h2>
|
||||
<div id="hotnessChart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — ${today}</div>
|
||||
|
||||
<script>
|
||||
var hotnessChart = echarts.init(document.getElementById('hotnessChart'));
|
||||
hotnessChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: 0 },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['45%', '75%'],
|
||||
label: { formatter: '{b}\n{d}%' },
|
||||
data: [
|
||||
{ name: 'Stars', value: ${stars_norm:-0}, itemStyle: { color: '#5470c6' } },
|
||||
{ name: 'Forks', value: ${forks_norm:-0}, itemStyle: { color: '#91cc75' } },
|
||||
{ name: 'Issues', value: ${issues_active:-0}, itemStyle: { color: '#fac858' } },
|
||||
{ name: 'PRs', value: ${prs_active:-0}, itemStyle: { color: '#ee6666' } },
|
||||
{ name: 'Releases', value: ${releases_active:-0}, itemStyle: { color: '#73c0de' } },
|
||||
{ name: 'Recency', value: ${recency_factor:-0}, itemStyle: { color: '#fc8452' } }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
var activityChart = echarts.init(document.getElementById('activityChart'));
|
||||
activityChart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
xAxis: { type: 'category', data: ['Issues', 'PRs', 'Releases', 'CI Builds'] },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '开放/进行中', type: 'bar', data: [${total_open:-0}, ${total_open_prs:-0}, 0, 0], itemStyle: { color: '#fac858' } },
|
||||
{ name: '已完成', type: 'bar', data: [${total_closed:-0}, ${total_merged:-0}, ${release_count:-0}, ${ci_builds:-0}], itemStyle: { color: '#91cc75' } }
|
||||
]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
log_ok "HTML 报告已生成: $OUTPUT_FILE"
|
||||
else
|
||||
log_warn "[DRY RUN] Would generate: $OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
# ═══ Wiki Publishing ═══
|
||||
if [[ "$PUBLISH_WIKI" == "true" ]] && [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
log_step "发布到 GitLink Wiki..."
|
||||
local wiki_content wiki_title
|
||||
wiki_title="[Research:Insights] ${REPO} 项目洞察 ${today}"
|
||||
wiki_content=$(cat <<WIKIEOF
|
||||
## ${REPO_FULL} — 科研项目洞察
|
||||
|
||||
| 维度 | 数据 |
|
||||
|------|------|
|
||||
| 主要语言 | ${repo_lang} |
|
||||
| 技术栈 | ${tech_stack:-$repo_lang} |
|
||||
| Stars / Forks | ${stars} / ${forks} |
|
||||
| 开放 Issues | ${total_open} |
|
||||
| PR 合并率 | ${pr_merge_rate}% |
|
||||
| Release 数量 | ${release_count} |
|
||||
| 贡献者数 | ${member_count} |
|
||||
| **热度评分** | **${hotness}/100 (${hotness_label})** |
|
||||
|
||||
### 科研特征
|
||||
${research_features:-未检测到显著科研特征}
|
||||
|
||||
### 领域主题
|
||||
${project_topics:-未检测到}
|
||||
|
||||
### DOI
|
||||
${doi_found:-未检测到}
|
||||
|
||||
---
|
||||
*报告生成时间:${today}*
|
||||
WIKIEOF
|
||||
)
|
||||
local wiki_json
|
||||
wiki_json=$(gl_run wiki +create --owner "$OWNER" --repo "$REPO" \
|
||||
--title "$wiki_title" --content "$wiki_content")
|
||||
if [[ "$(json_ok "$wiki_json")" == "true" ]]; then
|
||||
log_ok "Wiki 页面已发布"
|
||||
else
|
||||
log_warn "Wiki 发布失败(可手动创建)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ═══ Summary ═══
|
||||
echo ""
|
||||
divider
|
||||
log_title "报告摘要"
|
||||
echo " 仓库: ${REPO_FULL}"
|
||||
echo " 语言: ${repo_lang}"
|
||||
echo " 技术栈: ${tech_stack:-$repo_lang}"
|
||||
echo " 热度评分: ${hotness}/100 (${hotness_label})"
|
||||
echo " Issues: ${total_open} 开放 / ${total_closed} 已关闭"
|
||||
echo " PR 合并率: ${pr_merge_rate}%"
|
||||
echo " CI 通过率: ${ci_pass_rate}%"
|
||||
echo " 贡献者: ${member_count} 人"
|
||||
echo " 科研特征: ${research_features:-无}"
|
||||
[[ -n "$doi_found" ]] && echo " DOI: ${doi_found}"
|
||||
divider
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
# GitLink 科研辅助 — 场景 3:合规与复现性检查 (PowerShell)
|
||||
param(
|
||||
[string]$Owner, [string]$Repo, [string]$LocalPath = ".",
|
||||
[string]$Output = "", [switch]$NoWiki, [switch]$DryRun
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Import-Module "$ScriptDir\lib\common.psm1" -Force
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
Check-Auth
|
||||
$resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo
|
||||
$Owner = $resolved.Owner; $Repo = $resolved.Repo
|
||||
$Today = Get-Date -Format "yyyy-MM-dd"
|
||||
$OutputDir = Join-Path $ScriptDir "..\output"
|
||||
if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null }
|
||||
|
||||
Log-Title "GitLink 科研辅助 — 合规与复现性检查"
|
||||
|
||||
# Scoring: 0, 0.5, 1.0 per dimension
|
||||
$dLicense = @{Score=0; Detail=""}; $dNoSecret = @{Score=0; Detail=""}
|
||||
$dReadme = @{Score=0; Detail=""}; $dDeps = @{Score=0; Detail=""}
|
||||
$dBuild = @{Score=0; Detail=""}; $dCI = @{Score=0; Detail=""}
|
||||
$dTest = @{Score=0; Detail=""}; $dData = @{Score=0; Detail=""}
|
||||
|
||||
# 1. Compliance scan
|
||||
Log-Step "1/7 合规扫描..."
|
||||
if (Test-Path (Join-Path $LocalPath ".git")) {
|
||||
Push-Location $LocalPath
|
||||
$compResult = Invoke-GL @("compliance", "+scan")
|
||||
Pop-Location
|
||||
if ($compResult.ok) {
|
||||
$licenseOk = $compResult.data.license.status ?? ""
|
||||
if ($licenseOk -match "ok|clean|found") { $dLicense.Score = 1.0; $dLicense.Detail = "检测到合规许可证" }
|
||||
elseif ($licenseOk -eq "warning") { $dLicense.Score = 0.5; $dLicense.Detail = "有许可证但非标准" }
|
||||
else { $dLicense.Score = 0; $dLicense.Detail = "未检测到 LICENSE" }
|
||||
|
||||
$secCount = @($compResult.data.secrets.findings ?? @()).Count
|
||||
$piiCount = @($compResult.data.exposure.findings ?? @()).Count
|
||||
if ($secCount -eq 0 -and $piiCount -eq 0) { $dNoSecret.Score = 1.0; $dNoSecret.Detail = "未发现密钥/PII" }
|
||||
elseif ($secCount + $piiCount -le 3) { $dNoSecret.Score = 0.5; $dNoSecret.Detail = "发现少量可疑项" }
|
||||
else { $dNoSecret.Score = 0; $dNoSecret.Detail = "发现多处密钥/PII泄露" }
|
||||
}
|
||||
} else { Log-Warn "本地仓库路径无 .git,跳过合规扫描" }
|
||||
|
||||
# 2. README
|
||||
Log-Step "2/7 README 完整性..."
|
||||
try {
|
||||
$readmeResult = Invoke-GL @("api", "GET", "raw/$Owner/$Repo/master/README.md")
|
||||
if ($readmeResult.ok) { $readmeText = $readmeResult.data ?? "" } else { $readmeText = "" }
|
||||
} catch { $readmeText = "" }
|
||||
|
||||
$sectionCount = 0
|
||||
foreach ($kw in @("# ", "## ", "Install", "Usage", "License", "Contribut", "Citation")) {
|
||||
if ($readmeText -match $kw) { $sectionCount++ }
|
||||
}
|
||||
if ($sectionCount -ge 5) { $dReadme.Score = 1.0 }
|
||||
elseif ($sectionCount -ge 3) { $dReadme.Score = 0.5 }
|
||||
else { $dReadme.Score = 0 }
|
||||
$dReadme.Detail = "README 章节数: $sectionCount"
|
||||
Log-Info " $($dReadme.Detail)"
|
||||
|
||||
# 3. Dependencies
|
||||
Log-Step "3/7 依赖声明..."
|
||||
$subResult = Invoke-GL @("api", "GET", "/v1/$Owner/$Repo/sub_entries?ref=master")
|
||||
$depFiles = 0
|
||||
if ($subResult.ok) {
|
||||
$names = @($subResult.data | ForEach-Object { $_.name ?? "" })
|
||||
foreach ($df in @("package.json","go.mod","requirements.txt","pyproject.toml","Cargo.toml","CMakeLists.txt","pom.xml","build.gradle")) {
|
||||
if ($names -contains $df) { $depFiles++ }
|
||||
}
|
||||
}
|
||||
if ($depFiles -ge 1) { $dDeps.Score = 1.0 }
|
||||
elseif ($readmeText -match "dependenc|requirement|依赖|install") { $dDeps.Score = 0.5 }
|
||||
else { $dDeps.Score = 0 }
|
||||
$dDeps.Detail = "依赖文件数: $depFiles"
|
||||
|
||||
# 4. Build
|
||||
Log-Step "4/7 构建说明..."
|
||||
$buildScore = 0
|
||||
if ($readmeText -match "build|install|compile|make|构建|安装|编译") { $buildScore++ }
|
||||
if ($subResult.ok -and (@($subResult.data | Where-Object { $_.name -match "Makefile|Dockerfile" })).Count -gt 0) { $buildScore++ }
|
||||
if ($buildScore -ge 2) { $dBuild.Score = 1.0 } elseif ($buildScore -ge 1) { $dBuild.Score = 0.5 } else { $dBuild.Score = 0 }
|
||||
$dBuild.Detail = "构建说明得分: $buildScore/2"
|
||||
|
||||
# 5. CI
|
||||
Log-Step "5/7 CI 配置..."
|
||||
$ciResult = Invoke-GL @("ci", "+builds", "--owner", $Owner, "--repo", $Repo, "--limit", "10")
|
||||
$ciBuilds = if ($ciResult.ok) { @($ciResult.data).Count } else { 0 }
|
||||
if ($ciBuilds -gt 0) { $dCI.Score = 1.0; $dCI.Detail = "CI 已配置 ($ciBuilds 次构建)" }
|
||||
else { $dCI.Score = 0; $dCI.Detail = "无 CI 配置" }
|
||||
|
||||
# 6. Tests
|
||||
Log-Step "6/7 测试证据..."
|
||||
$testScore = 0
|
||||
if ($subResult.ok) {
|
||||
if (@($subResult.data | Where-Object { $_.name -match "^test/|^tests/|^spec/" }).Count -gt 0) { $testScore++ }
|
||||
if (@($subResult.data | Where-Object { $_.name -match "_test\.|\.test\.|_spec\." }).Count -gt 0) { $testScore++ }
|
||||
}
|
||||
if ($readmeText -match "test|测试|validate") { $testScore++ }
|
||||
if ($testScore -ge 3) { $dTest.Score = 1.0 } elseif ($testScore -ge 1) { $dTest.Score = 0.5 } else { $dTest.Score = 0 }
|
||||
$dTest.Detail = "测试证据得分: $testScore/3"
|
||||
|
||||
# 7. Data
|
||||
Log-Step "7/7 数据可用性..."
|
||||
$dataScore = 0
|
||||
if ($readmeText -match "dataset|data/|数据|zenodo|figshare|kaggle") { $dataScore++ }
|
||||
if ($readmeText -match "10\.\d{4,}/[\w.\-/]+") { $dataScore++ }
|
||||
if ($dataScore -ge 2) { $dData.Score = 1.0 } elseif ($dataScore -ge 1) { $dData.Score = 0.5 } else { $dData.Score = 0 }
|
||||
$dData.Detail = "数据声明得分: $dataScore/2"
|
||||
|
||||
# Total score
|
||||
$weights = @(0.15, 0.15, 0.15, 0.15, 0.10, 0.10, 0.10, 0.10)
|
||||
$scores = @($dLicense.Score, $dNoSecret.Score, $dReadme.Score, $dDeps.Score, $dBuild.Score, $dCI.Score, $dTest.Score, $dData.Score)
|
||||
$totalScore = 0
|
||||
for ($i = 0; $i -lt 8; $i++) { $totalScore += $scores[$i] * $weights[$i] }
|
||||
$totalScore = [Math]::Round($totalScore * 100, 1)
|
||||
|
||||
$grade = if ($totalScore -ge 85) { "A" } elseif ($totalScore -ge 70) { "B" } elseif ($totalScore -ge 55) { "C" } elseif ($totalScore -ge 40) { "D" } else { "F" }
|
||||
|
||||
Divider
|
||||
Write-Host "====== 复现性评分卡 ======" -ForegroundColor White
|
||||
Write-Host " 综合评分: $totalScore/100 — $grade"
|
||||
$dims = @("许可证","无密钥/PII","README","依赖","构建","CI","测试","数据")
|
||||
$dets = @($dLicense, $dNoSecret, $dReadme, $dDeps, $dBuild, $dCI, $dTest, $dData)
|
||||
for ($i = 0; $i -lt 8; $i++) {
|
||||
$icon = if ($scores[$i] -eq 1.0) { "OK" } elseif ($scores[$i] -eq 0.5) { "~" } else { "!!" }
|
||||
Write-Host " $icon $($dims[$i]) ($([Math]::Round($scores[$i]*100))%): $($dets[$i].Detail)"
|
||||
}
|
||||
Divider
|
||||
Log-Ok "检查完成"
|
||||
|
|
@ -0,0 +1,507 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# GitLink 科研辅助 — 场景 3:合规与复现性检查
|
||||
# ============================================================
|
||||
# 8维度复现性评分:许可证、密钥/PII、README、依赖声明、
|
||||
# 构建说明、CI配置、测试证据、数据可用性
|
||||
# 输出雷达图 + 仪表盘 HTML 评分卡
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
trap '' PIPE
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
# ── Configuration ────────────────────────────────────────────────────
|
||||
OUTPUT_DIR="${SCRIPT_DIR}/../../output"
|
||||
OUTPUT_FILE=""
|
||||
LOCAL_PATH="."
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: 08-research-compliance.sh --owner <owner> --repo <repo> [options]
|
||||
|
||||
Options:
|
||||
--owner <owner> Repository owner (required)
|
||||
--repo <repo> Repository name (required)
|
||||
--local-path <path> Local repo path for compliance scan (default: .)
|
||||
--output <file> Output HTML file path
|
||||
--no-wiki Skip publishing to Wiki
|
||||
--dry-run Preview mode
|
||||
|
||||
Examples:
|
||||
08-research-compliance.sh --owner zzx-coder --repo gitlink-cli --local-path .
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_common_args "$@"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||||
--local-path) LOCAL_PATH="$2"; shift 2 ;;
|
||||
--no-wiki) PUBLISH_WIKI="false"; shift ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
PUBLISH_WIKI="${PUBLISH_WIKI:-true}"
|
||||
|
||||
# ── Scoring helpers ──────────────────────────────────────────────────
|
||||
score_dim() {
|
||||
# score_dim score weight label
|
||||
awk -v s="$1" -v w="$2" 'BEGIN { printf "%.4f", s * w }'
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
log_title "GitLink 科研辅助 — 合规与复现性检查"
|
||||
|
||||
check_auth
|
||||
require_owner_repo
|
||||
local today
|
||||
today=$(date_today)
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Get repo metadata for description and DOI detection
|
||||
local repo_json repo_desc
|
||||
repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO")
|
||||
repo_desc=$(json_get "$repo_json" '.data.description // ""')
|
||||
local doi_found=""
|
||||
doi_found=$(echo "$repo_desc" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1 || echo "")
|
||||
|
||||
local REPO_FULL="${OWNER}/${REPO}"
|
||||
OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/reproducibility-${REPO}-${today}.html}"
|
||||
|
||||
# Each dimension: score (0, 0.5, 1.0), weight, label, detail
|
||||
local dim_license_s=0 dim_nosecret_s=0 dim_readme_s=0 dim_deps_s=0
|
||||
local dim_build_s=0 dim_ci_s=0 dim_test_s=0 dim_data_s=0
|
||||
local dim_license_d="" dim_nosecret_d="" dim_readme_d="" dim_deps_d=""
|
||||
local dim_build_d="" dim_ci_d="" dim_test_d="" dim_data_d=""
|
||||
|
||||
# ═══ Step 1: Compliance Scan ═══
|
||||
log_step "1/7 合规扫描..."
|
||||
local comp_json
|
||||
if [[ -d "$LOCAL_PATH/.git" ]]; then
|
||||
comp_json=$(cd "$LOCAL_PATH" && gl_run compliance +scan --format json 2>/dev/null)
|
||||
else
|
||||
log_warn "本地仓库路径无 .git 目录,跳过合规扫描"
|
||||
comp_json=""
|
||||
fi
|
||||
|
||||
if [[ "$(json_ok "$comp_json")" == "true" ]]; then
|
||||
# License check
|
||||
local license_ok
|
||||
license_ok=$(echo "$comp_json" | jq -r '.data.license.status // "unknown"' 2>/dev/null)
|
||||
if [[ "$license_ok" == "ok" || "$license_ok" == "clean" || "$license_ok" == "found" ]]; then
|
||||
dim_license_s=1.0; dim_license_d="检测到合规许可证"
|
||||
elif [[ "$license_ok" == "warning" ]]; then
|
||||
dim_license_s=0.5; dim_license_d="有许可证但类型非标准"
|
||||
else
|
||||
dim_license_s=0; dim_license_d="未检测到 LICENSE 文件"
|
||||
fi
|
||||
|
||||
# Secrets check
|
||||
local secrets_count
|
||||
secrets_count=$(echo "$comp_json" | jq -r '.data.secrets.findings | length // 0' 2>/dev/null)
|
||||
if [[ "$secrets_count" == "0" || -z "$secrets_count" ]]; then
|
||||
dim_nosecret_s=1.0; dim_nosecret_d="未发现硬编码密钥"
|
||||
elif [[ "$secrets_count" -le 2 ]]; then
|
||||
dim_nosecret_s=0.5; dim_nosecret_d="发现 ${secrets_count} 处可疑密钥"
|
||||
else
|
||||
dim_nosecret_s=0; dim_nosecret_d="发现 ${secrets_count} 处密钥泄露"
|
||||
fi
|
||||
|
||||
# PII / Exposure
|
||||
local pii_count
|
||||
pii_count=$(echo "$comp_json" | jq -r '.data.exposure.findings | length // 0' 2>/dev/null)
|
||||
if [[ "$pii_count" == "0" || -z "$pii_count" ]]; then
|
||||
# Keep secrets score; PII clean doesn't change it
|
||||
dim_nosecret_d="${dim_nosecret_d} / 无 PII 泄露"
|
||||
else
|
||||
dim_nosecret_d="${dim_nosecret_d} / 发现 ${pii_count} 处 PII"
|
||||
if [[ ${dim_nosecret_s%%.*} -eq 1 ]]; then
|
||||
dim_nosecret_s=0.5
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info " License: $( [[ $dim_license_s == "1.0" ]] && echo "OK" || echo "ISSUE")"
|
||||
log_info " Secrets/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "OK" || echo "ISSUE")"
|
||||
else
|
||||
log_warn "合规扫描未返回有效结果,跳过该维度"
|
||||
dim_license_s=0; dim_license_d="未扫描(无本地仓库)"
|
||||
dim_nosecret_s=0; dim_nosecret_d="未扫描(无本地仓库)"
|
||||
fi
|
||||
|
||||
# ═══ Step 2: README Completeness ═══
|
||||
log_step "2/7 检查 README 完整性..."
|
||||
local readme_text
|
||||
readme_text=$(gl_run api GET "raw/$OWNER/$REPO/master/README.md" 2>/dev/null)
|
||||
if [[ "$(json_ok "$readme_text")" == "true" ]]; then
|
||||
readme_text=$(echo "$readme_text" | jq -r '.data // ""' 2>/dev/null)
|
||||
else
|
||||
readme_text=""
|
||||
fi
|
||||
|
||||
local section_count=0 sections_found=""
|
||||
for kw in "# " "## " "Install" "Usage" "License" "Contribut" "Citation" "安装" "使用" "许可" "引用"; do
|
||||
if echo "$readme_text" | grep -qi "$kw"; then
|
||||
section_count=$((section_count + 1))
|
||||
sections_found="${sections_found}${kw}, "
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $section_count -ge 5 ]]; then
|
||||
dim_readme_s=1.0; dim_readme_d="README 结构完整,含 ${section_count} 个关键章节"
|
||||
elif [[ $section_count -ge 3 ]]; then
|
||||
dim_readme_s=0.5; dim_readme_d="README 部分完整,${section_count} 个关键章节"
|
||||
else
|
||||
dim_readme_s=0; dim_readme_d="README 缺失或过于简略"
|
||||
fi
|
||||
log_info " README 章节数: ${section_count}"
|
||||
|
||||
# ═══ Step 3: Dependency Declaration ═══
|
||||
log_step "3/7 检查依赖声明..."
|
||||
local sub_json dep_files=0 dep_list="" names=""
|
||||
sub_json=$(gl_run api GET "/v1/$OWNER/$REPO/sub_entries?ref=master")
|
||||
if [[ "$(json_ok "$sub_json")" == "true" ]]; then
|
||||
local data_type
|
||||
data_type=$(echo "$sub_json" | jq -r '(.data | type) // "string"' 2>/dev/null)
|
||||
if [[ "$data_type" == "array" ]]; then
|
||||
names=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$names" ]]; then
|
||||
for dep_file in "package.json" "go.mod" "requirements.txt" "pyproject.toml" \
|
||||
"Cargo.toml" "CMakeLists.txt" "pom.xml" "build.gradle" "Gemfile" \
|
||||
"Makefile" "DESCRIPTION" "Project.toml"; do
|
||||
if echo "$names" | grep -qFx "$dep_file"; then
|
||||
dep_files=$((dep_files + 1))
|
||||
dep_list="${dep_list}${dep_file}, "
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $dep_files -ge 1 ]]; then
|
||||
dim_deps_s=1.0; dim_deps_d="有标准依赖文件: ${dep_list%, }"
|
||||
elif echo "$readme_text" | grep -qiE "dependenc|requirement|依赖|安装|install"; then
|
||||
dim_deps_s=0.5; dim_deps_d="README 中提及依赖"
|
||||
else
|
||||
dim_deps_s=0; dim_deps_d="无依赖声明"
|
||||
fi
|
||||
log_info " 依赖文件数: ${dep_files}"
|
||||
|
||||
# ═══ Step 4: Build Instructions ═══
|
||||
log_step "4/7 检查构建说明..."
|
||||
local build_score=0
|
||||
# Check README for build keywords
|
||||
if echo "$readme_text" | grep -qiE "build|install|compile|make|构建|安装|编译|run|运行"; then
|
||||
build_score=$((build_score + 1))
|
||||
fi
|
||||
# Check for Makefile/Dockerfile
|
||||
if echo "$names" | grep -qE "Makefile|Dockerfile|docker-compose"; then
|
||||
build_score=$((build_score + 1))
|
||||
fi
|
||||
# Check for CI config
|
||||
if echo "$names" | grep -qE "\.github/workflows|\.gitlab-ci|Jenkinsfile"; then
|
||||
build_score=$((build_score + 1))
|
||||
fi
|
||||
|
||||
if [[ $build_score -ge 3 ]]; then
|
||||
dim_build_s=1.0; dim_build_d="详细的构建说明和自动化配置"
|
||||
elif [[ $build_score -ge 1 ]]; then
|
||||
dim_build_s=0.5; dim_build_d="部分构建说明"
|
||||
else
|
||||
dim_build_s=0; dim_build_d="无构建说明"
|
||||
fi
|
||||
log_info " 构建说明得分: ${build_score}/3"
|
||||
|
||||
# ═══ Step 5: CI Configuration ═══
|
||||
log_step "5/7 检查 CI 配置..."
|
||||
local ci_json ci_builds=0 ci_ok=0
|
||||
ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 10)
|
||||
if [[ "$(json_ok "$ci_json")" == "true" ]]; then
|
||||
ci_builds=$(echo "$ci_json" | jq -r '(.data | length) // 0' 2>/dev/null)
|
||||
ci_ok=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
if [[ $ci_builds -gt 0 ]] && [[ $ci_ok -ge 1 ]]; then
|
||||
dim_ci_s=1.0; dim_ci_d="CI 已配置且通过 (${ci_ok}/${ci_builds})"
|
||||
elif [[ $ci_builds -gt 0 ]]; then
|
||||
dim_ci_s=0.5; dim_ci_d="CI 存在但最近构建失败"
|
||||
else
|
||||
dim_ci_s=0; dim_ci_d="无 CI 配置"
|
||||
fi
|
||||
log_info " CI 构建数: ${ci_builds}"
|
||||
|
||||
# ═══ Step 6: Test Evidence ═══
|
||||
log_step "6/7 检查测试证据..."
|
||||
local test_score=0
|
||||
# Check for test directories
|
||||
if echo "$names" | grep -qE "^test/|^tests/|^spec/|^__tests__/"; then
|
||||
test_score=$((test_score + 1))
|
||||
fi
|
||||
# Check for test files
|
||||
if echo "$names" | grep -qE "_test\.|\.test\.|_spec\.|\.spec\.|test_"; then
|
||||
test_score=$((test_score + 1))
|
||||
fi
|
||||
# Check README for test instructions
|
||||
if echo "$readme_text" | grep -qiE "test|测试|validate|验证"; then
|
||||
test_score=$((test_score + 1))
|
||||
fi
|
||||
|
||||
if [[ $test_score -ge 3 ]]; then
|
||||
dim_test_s=1.0; dim_test_d="有测试目录 + 测试文件 + 测试说明"
|
||||
elif [[ $test_score -ge 1 ]]; then
|
||||
dim_test_s=0.5; dim_test_d="部分测试证据"
|
||||
else
|
||||
dim_test_s=0; dim_test_d="无测试证据"
|
||||
fi
|
||||
log_info " 测试证据得分: ${test_score}/3"
|
||||
|
||||
# ═══ Step 7: Data Availability ═══
|
||||
log_step "7/7 检查数据可用性声明..."
|
||||
local data_score=0 data_evidence=""
|
||||
if echo "$readme_text $repo_desc" | grep -qiE "dataset|data/|数据|zenodo|figshare|kaggle|huggingface"; then
|
||||
data_score=$((data_score + 1))
|
||||
data_evidence="有关键词提及"
|
||||
fi
|
||||
if echo "$readme_text $repo_desc" | grep -qiE "https?://[^\s]+(?:zenodo|figshare|data\.|dataset)[^\s]*" 2>/dev/null; then
|
||||
data_score=$((data_score + 1))
|
||||
data_evidence="${data_evidence}, 有数据链接"
|
||||
fi
|
||||
if [[ -n "$doi_found" ]]; then
|
||||
data_score=$((data_score + 1))
|
||||
data_evidence="${data_evidence}, 有 DOI/论文引用"
|
||||
fi
|
||||
|
||||
if [[ $data_score -ge 2 ]]; then
|
||||
dim_data_s=1.0; dim_data_d="明确的数据可用性声明${data_evidence}"
|
||||
elif [[ $data_score -ge 1 ]]; then
|
||||
dim_data_s=0.5; dim_data_d="部分数据声明${data_evidence}"
|
||||
else
|
||||
dim_data_s=0; dim_data_d="无数据可用性声明"
|
||||
fi
|
||||
log_info " 数据声明得分: ${data_score}/3"
|
||||
|
||||
# ═══ Calculate Total Score ═══
|
||||
log_step "计算复现性评分..."
|
||||
|
||||
local total_score
|
||||
total_score=$(weighted_sum \
|
||||
"$dim_license_s" 0.15 \
|
||||
"$dim_nosecret_s" 0.15 \
|
||||
"$dim_readme_s" 0.15 \
|
||||
"$dim_deps_s" 0.15 \
|
||||
"$dim_build_s" 0.10 \
|
||||
"$dim_ci_s" 0.10 \
|
||||
"$dim_test_s" 0.10 \
|
||||
"$dim_data_s" 0.10)
|
||||
total_score=$(awk -v s="$total_score" 'BEGIN { printf "%.1f", s * 100 }')
|
||||
|
||||
local grade
|
||||
if awk "BEGIN { exit ($total_score >= 85) ? 0 : 1 }"; then grade="A"
|
||||
elif awk "BEGIN { exit ($total_score >= 70) ? 0 : 1 }"; then grade="B"
|
||||
elif awk "BEGIN { exit ($total_score >= 55) ? 0 : 1 }"; then grade="C"
|
||||
elif awk "BEGIN { exit ($total_score >= 40) ? 0 : 1 }"; then grade="D"
|
||||
else grade="F"; fi
|
||||
|
||||
log_ok "复现性评分: ${total_score}/100 — 等级 ${grade}"
|
||||
|
||||
# ═══ Generate HTML ═══
|
||||
log_step "生成 HTML 评分卡..."
|
||||
|
||||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
cat > "$OUTPUT_FILE" <<HTMLEOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${REPO_FULL} — 复现性评分卡</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
.chart { width: 100%; height: 350px; }
|
||||
.grade-circle { text-align: center; padding: 20px; }
|
||||
.grade-letter { font-size: 72px; font-weight: 900; }
|
||||
.grade-A { color: #2e7d32; }
|
||||
.grade-B { color: #558b2f; }
|
||||
.grade-C { color: #f57c00; }
|
||||
.grade-D { color: #e65100; }
|
||||
.grade-F { color: #c62828; }
|
||||
.grade-score { font-size: 24px; color: #888; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||||
th { background: #f5f7fa; color: #555; }
|
||||
.bar { height: 8px; border-radius: 4px; background: #e0e0e0; margin-top: 4px; }
|
||||
.bar-fill { height: 100%; border-radius: 4px; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>${REPO_FULL} — 科研复现性评分卡</h1>
|
||||
<div style="opacity:0.8;font-size:14px;">${today}</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="panel grade-circle">
|
||||
<div class="grade-letter grade-${grade}">${grade}</div>
|
||||
<div class="grade-score">${total_score} / 100</div>
|
||||
<div style="margin-top:12px;color:#888;">
|
||||
$( [[ "$grade" == "A" ]] && echo "优秀 — 高度可复现" )
|
||||
$( [[ "$grade" == "B" ]] && echo "良好 — 基本可复现" )
|
||||
$( [[ "$grade" == "C" ]] && echo "一般 — 部分可复现" )
|
||||
$( [[ "$grade" == "D" ]] && echo "不足 — 复现困难" )
|
||||
$( [[ "$grade" == "F" ]] && echo "差 — 几乎不可复现" )
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>维度雷达图</h2>
|
||||
<div id="radarChart" class="chart"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>维度明细</h2>
|
||||
<table>
|
||||
<tr><th>维度</th><th>评分</th><th>权重</th></tr>
|
||||
<tr><td>许可证</td><td>$(awk -v s="$dim_license_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||||
<tr><td>无密钥/PII</td><td>$(awk -v s="$dim_nosecret_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||||
<tr><td>README 完整</td><td>$(awk -v s="$dim_readme_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||||
<tr><td>依赖声明</td><td>$(awk -v s="$dim_deps_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>15%</td></tr>
|
||||
<tr><td>构建说明</td><td>$(awk -v s="$dim_build_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||||
<tr><td>CI 配置</td><td>$(awk -v s="$dim_ci_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||||
<tr><td>测试证据</td><td>$(awk -v s="$dim_test_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||||
<tr><td>数据可用性</td><td>$(awk -v s="$dim_data_s" 'BEGIN {printf "%.0f%%", s*100}')</td><td>10%</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>详细评估与改进建议</h2>
|
||||
<table>
|
||||
<tr><th>维度</th><th>评分</th><th>证据</th><th>建议</th></tr>
|
||||
<tr>
|
||||
<td>许可证</td>
|
||||
<td>$( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌")</td>
|
||||
<td>${dim_license_d}</td>
|
||||
<td>$( [[ $dim_license_s != "1.0" ]] && echo "建议添加 MIT/Apache-2.0/GPL-3.0 许可证" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>无密钥/PII</td>
|
||||
<td>$( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️")</td>
|
||||
<td>${dim_nosecret_d}</td>
|
||||
<td>$( [[ $dim_nosecret_s != "1.0" ]] && echo "立即移除泄露的密钥,使用环境变量管理敏感信息" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>README 完整</td>
|
||||
<td>$( [[ $dim_readme_s == "1.0" ]] && echo "✅" || ([[ $dim_readme_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_readme_d}</td>
|
||||
<td>$( [[ $dim_readme_s != "1.0" ]] && echo "补充项目目的、安装、使用、许可和引用章节" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>依赖声明</td>
|
||||
<td>$( [[ $dim_deps_s == "1.0" ]] && echo "✅" || ([[ $dim_deps_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_deps_d}</td>
|
||||
<td>$( [[ $dim_deps_s != "1.0" ]] && echo "添加 package.json/go.mod/requirements.txt 等标准依赖文件" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>构建说明</td>
|
||||
<td>$( [[ $dim_build_s == "1.0" ]] && echo "✅" || ([[ $dim_build_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_build_d}</td>
|
||||
<td>$( [[ $dim_build_s != "1.0" ]] && echo "添加 Makefile/Dockerfile + README 中的构建步骤" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CI 配置</td>
|
||||
<td>$( [[ $dim_ci_s == "1.0" ]] && echo "✅" || ([[ $dim_ci_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_ci_d}</td>
|
||||
<td>$( [[ $dim_ci_s != "1.0" ]] && echo "配置 GitLink CI 或 GitHub Actions 自动构建和测试" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试证据</td>
|
||||
<td>$( [[ $dim_test_s == "1.0" ]] && echo "✅" || ([[ $dim_test_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_test_d}</td>
|
||||
<td>$( [[ $dim_test_s != "1.0" ]] && echo "添加单元测试和集成测试,在 README 中说明如何运行" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>数据可用性</td>
|
||||
<td>$( [[ $dim_data_s == "1.0" ]] && echo "✅" || ([[ $dim_data_s == "0.5" ]] && echo "⚠️" || echo "❌"))</td>
|
||||
<td>${dim_data_d}</td>
|
||||
<td>$( [[ $dim_data_s != "1.0" ]] && echo "说明数据集来源,提供 Zenodo/Figshare 链接或生成脚本" || echo "—")</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — ${today}</div>
|
||||
|
||||
<script>
|
||||
var radarChart = echarts.init(document.getElementById('radarChart'));
|
||||
radarChart.setOption({
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '许可证', max: 100 },
|
||||
{ name: '无密钥', max: 100 },
|
||||
{ name: 'README', max: 100 },
|
||||
{ name: '依赖', max: 100 },
|
||||
{ name: '构建', max: 100 },
|
||||
{ name: 'CI', max: 100 },
|
||||
{ name: '测试', max: 100 },
|
||||
{ name: '数据', max: 100 }
|
||||
],
|
||||
center: ['50%', '55%'],
|
||||
radius: '70%'
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: [
|
||||
$(awk -v s="$dim_license_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_nosecret_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_readme_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_deps_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_build_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_ci_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_test_s" 'BEGIN {printf "%.0f", s*100}'),
|
||||
$(awk -v s="$dim_data_s" 'BEGIN {printf "%.0f", s*100}')
|
||||
],
|
||||
name: '复现性',
|
||||
areaStyle: { color: 'rgba(57,73,171,0.3)' },
|
||||
lineStyle: { color: '#3949ab' }
|
||||
}]
|
||||
}]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
log_ok "HTML 评分卡已生成: $OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
# ═══ Summary ═══
|
||||
echo ""
|
||||
divider
|
||||
log_title "复现性评估摘要"
|
||||
echo " 综合评分: ${total_score}/100 (${grade})"
|
||||
echo " 许可证: $( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_license_d}"
|
||||
echo " 密钥/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️") ${dim_nosecret_d}"
|
||||
echo " README: $( [[ $dim_readme_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_readme_d}"
|
||||
echo " 依赖: $( [[ $dim_deps_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_deps_d}"
|
||||
echo " 构建: $( [[ $dim_build_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_build_d}"
|
||||
echo " CI: $( [[ $dim_ci_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_ci_d}"
|
||||
echo " 测试: $( [[ $dim_test_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_test_d}"
|
||||
echo " 数据: $( [[ $dim_data_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_data_d}"
|
||||
divider
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# GitLink 科研辅助 — 场景 5:进度跟踪与预警 (PowerShell)
|
||||
param(
|
||||
[string]$Owner, [string]$Repo, [string]$Org = "",
|
||||
[int]$Weeks = 4, [string]$Output = "",
|
||||
[switch]$NoWiki, [switch]$DryRun
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Import-Module "$ScriptDir\lib\common.psm1" -Force
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
Check-Auth
|
||||
if ($Org) { $Owner = $Org; $Repo = "__org__" }
|
||||
else { $resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo; $Owner = $resolved.Owner; $Repo = $resolved.Repo }
|
||||
$Today = Get-Date -Format "yyyy-MM-dd"
|
||||
$OutputDir = Join-Path $ScriptDir "..\output"
|
||||
if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null }
|
||||
|
||||
Log-Title "GitLink 科研辅助 — 进度跟踪与预警"
|
||||
|
||||
# 1. Issues
|
||||
Log-Step "1/4 Issue 数据..."
|
||||
$openIssues = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100")
|
||||
$closedIssues = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100")
|
||||
$totalOpen = if ($openIssues.ok) { @($openIssues.data.issues ?? $openIssues.data).Count } else { 0 }
|
||||
$totalClosed = if ($closedIssues.ok) { @($closedIssues.data.issues ?? $closedIssues.data).Count } else { 0 }
|
||||
|
||||
# 2. PRs
|
||||
Log-Step "2/4 PR 数据..."
|
||||
$mergedPrs = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100")
|
||||
$openPrs = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "50")
|
||||
$totalMerged = if ($mergedPrs.ok) { @($mergedPrs.data.issues ?? $mergedPrs.data.pulls ?? $mergedPrs.data).Count } else { 0 }
|
||||
$totalOpenPrs = if ($openPrs.ok) { @($openPrs.data.issues ?? $openPrs.data.pulls ?? $openPrs.data).Count } else { 0 }
|
||||
|
||||
# 3. Releases & CI
|
||||
Log-Step "3/4 Release & CI..."
|
||||
$releases = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "20")
|
||||
$releaseCount = if ($releases.ok) { @($releases.data).Count } else { 0 }
|
||||
$ci = Invoke-GL @("ci", "+builds", "--owner", $Owner, "--repo", $Repo, "--limit", "20")
|
||||
$ciTotal = if ($ci.ok) { @($ci.data).Count } else { 0 }
|
||||
|
||||
# 4. Health score
|
||||
Log-Step "4/4 健康评分..."
|
||||
$iv = [Math]::Min($totalClosed / ($Weeks * 7), 1.0)
|
||||
$pr = if (($totalMerged + $totalOpenPrs) -gt 0) { $totalMerged / ($totalMerged + $totalOpenPrs) } else { 0 }
|
||||
$rc = if ($releaseCount -ge 3) { 1.0 } elseif ($releaseCount -ge 1) { 0.5 } else { 0.2 }
|
||||
$health = [Math]::Round(($iv * 0.30 + $pr * 0.25 + $rc * 0.25 + [Math]::Min(($totalClosed * 0.01), 1.0) * 0.10 + 0.5 * 0.10) * 100, 1)
|
||||
|
||||
# Anomalies
|
||||
$anomalies = @()
|
||||
if ($totalOpen -gt 20) { $anomalies += "[Warning] 开放 Issue 数量($totalOpen)偏高" }
|
||||
if ($totalOpenPrs -gt 5) { $anomalies += "[Warning] $totalOpenPrs 个开放 PR 积压" }
|
||||
if ($releaseCount -eq 0) { $anomalies += "[Info] 无 Release 记录" }
|
||||
if ($totalOpen -gt $totalClosed) { $anomalies += "[Warning] Issue 积压(开放 > 关闭)" }
|
||||
|
||||
$label = if ($health -ge 80) { "健康" } elseif ($health -ge 60) { "正常" } elseif ($health -ge 40) { "需关注" } else { "风险" }
|
||||
|
||||
Divider
|
||||
Write-Host "====== 进度周报摘要 ======" -ForegroundColor White
|
||||
Write-Host " 仓库: $Owner/$Repo"
|
||||
Write-Host " 健康评分: $health/100 ($label)"
|
||||
Write-Host " Issues: $totalOpen 开放 / $totalClosed 关闭"
|
||||
Write-Host " PRs: $totalOpenPrs 开放 / $totalMerged 合并"
|
||||
Write-Host " Releases: $releaseCount | CI: $ciTotal 次"
|
||||
if ($anomalies.Count -gt 0) {
|
||||
Write-Host " 异常信号 ($($anomalies.Count)):" -ForegroundColor Yellow
|
||||
foreach ($a in $anomalies) { Write-Host " $a" }
|
||||
} else { Write-Host " 未检测到异常" -ForegroundColor Green }
|
||||
Divider
|
||||
Log-Ok "分析完成"
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# GitLink 科研辅助 — 场景 5:科研进度智能跟踪与预警
|
||||
# ============================================================
|
||||
# 五维健康评分 + 异常检测(停滞Issue、逾期里程碑、活动下降、
|
||||
# 长期无发布、PR瓶颈),生成周报 HTML
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
trap '' PIPE
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
# ── Configuration ────────────────────────────────────────────────────
|
||||
OUTPUT_DIR="${SCRIPT_DIR}/../../output"
|
||||
OUTPUT_FILE=""
|
||||
PUBLISH_WIKI="true"
|
||||
LOOKBACK_WEEKS=4
|
||||
ORG_MODE=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: 10-research-progress.sh [options]
|
||||
|
||||
Options:
|
||||
--owner <owner> Repository owner (single repo mode)
|
||||
--repo <repo> Repository name (single repo mode)
|
||||
--org <org> Organization name (multi-repo mode)
|
||||
--weeks <N> Lookback weeks (default: 4)
|
||||
--output <file> Output HTML file path
|
||||
--no-wiki Skip publishing to Wiki
|
||||
--dry-run Preview mode
|
||||
|
||||
Examples:
|
||||
10-research-progress.sh --owner zzx-coder --repo gitlink-cli
|
||||
10-research-progress.sh --org zzx-coder --weeks 4
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_common_args "$@"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||||
--no-wiki) PUBLISH_WIKI="false"; shift ;;
|
||||
--org) ORG_MODE="$2"; shift 2 ;;
|
||||
--weeks) LOOKBACK_WEEKS="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Health Score Calculation ─────────────────────────────────────────
|
||||
calc_health() {
|
||||
local total_open="$1" total_closed="$2" total_merged="$3" total_open_prs="$4"
|
||||
local release_count="$5" ci_ok="$6" ci_total="$7"
|
||||
local total_open_prev="$8" total_closed_prev="$9"
|
||||
|
||||
# Issue velocity: closed per day (normalized to 0-1, target 1/day)
|
||||
local iv
|
||||
iv=$(awk -v c="$total_closed" -v w="$LOOKBACK_WEEKS" 'BEGIN { v=c/(w*7); printf "%.4f", (v>1?1:v) }')
|
||||
|
||||
# PR merge rate
|
||||
local pr_mr
|
||||
pr_mr=$(awk -v m="$total_merged" -v o="$total_open_prs" 'BEGIN { t=m+o; printf "%.4f", (t>0?m/t:0) }')
|
||||
|
||||
# Release cadence
|
||||
local rc
|
||||
rc=$(awk -v r="$release_count" 'BEGIN { printf "%.4f", (r>=3?1:(r>=1?0.5:0.2)) }')
|
||||
|
||||
# CI pass rate
|
||||
local ci_score
|
||||
ci_score=$(awk -v ok="$ci_ok" -v t="$ci_total" 'BEGIN { printf "%.4f", (t>0?ok/t:0) }')
|
||||
|
||||
# Activity trend
|
||||
local trend diff
|
||||
diff=$(awk -v cur="$total_closed" -v prev="$total_closed_prev" 'BEGIN { printf "%.4f", (prev>0)?(cur-prev)/prev:0 }')
|
||||
trend=$(awk -v d="$diff" 'BEGIN { t=d+0.5; printf "%.4f", (t<0?0:(t>1?1:t)) }')
|
||||
|
||||
# Weighted sum
|
||||
local health
|
||||
health=$(awk -v iv="$iv" -v pr="$pr_mr" -v rc="$rc" -v ci="$ci_score" -v tr="$trend" \
|
||||
'BEGIN { printf "%.1f", (iv*0.30+pr*0.25+rc*0.25+ci*0.10+tr*0.10)*100 }')
|
||||
echo "$health"
|
||||
}
|
||||
|
||||
# ── Anomaly Detection ────────────────────────────────────────────────
|
||||
detect_anomalies() {
|
||||
local anomalies=""
|
||||
local anomaly_count=0
|
||||
|
||||
# Stalled issues: open > 60 days (simulated via count threshold)
|
||||
if [[ ${1:-0} -gt 20 ]]; then
|
||||
anomalies="${anomalies}{\"type\":\"stalled_issue\",\"severity\":\"Warning\",\"detail\":\"开放 Issue 数量(${1})偏高,可能存在停滞\"},"
|
||||
anomaly_count=$((anomaly_count + 1))
|
||||
fi
|
||||
|
||||
# PR bottleneck
|
||||
if [[ ${3:-0} -gt 5 ]]; then
|
||||
anomalies="${anomalies}{\"type\":\"pr_bottleneck\",\"severity\":\"Warning\",\"detail\":\"${3} 个开放 PR 积压\"},"
|
||||
anomaly_count=$((anomaly_count + 1))
|
||||
fi
|
||||
|
||||
# No recent release
|
||||
if [[ ${4:-0} -eq 0 ]]; then
|
||||
anomalies="${anomalies}{\"type\":\"no_release\",\"severity\":\"Info\",\"detail\":\"无 Release 记录\"},"
|
||||
anomaly_count=$((anomaly_count + 1))
|
||||
fi
|
||||
|
||||
# Activity decline (if prev data available)
|
||||
if [[ -n "${5:-}" ]] && [[ -n "${6:-}" ]]; then
|
||||
local decline
|
||||
decline=$(awk -v cur="${5}" -v prev="${6}" 'BEGIN { if(prev>0 && cur<prev*0.5) print "1"; else print "0" }')
|
||||
if [[ "$decline" == "1" ]]; then
|
||||
anomalies="${anomalies}{\"type\":\"activity_decline\",\"severity\":\"Warning\",\"detail\":\"活动量下降超过 50%\"},"
|
||||
anomaly_count=$((anomaly_count + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# CI failure
|
||||
if [[ -n "${7:-}" ]] && [[ -n "${8:-}" ]] && [[ "${7}" -gt 0 ]] && [[ "${8}" -gt 0 ]]; then
|
||||
local ci_fail_rate
|
||||
ci_fail_rate=$(awk -v ok="${7}" -v t="${8}" 'BEGIN { if(t>0 && ok/t<0.5) print "1"; else print "0" }')
|
||||
if [[ "$ci_fail_rate" == "1" ]]; then
|
||||
anomalies="${anomalies}{\"type\":\"ci_failure\",\"severity\":\"Warning\",\"detail\":\"CI 通过率低于 50%\"},"
|
||||
anomaly_count=$((anomaly_count + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "{\"count\":$anomaly_count,\"items\":[${anomalies%,}]}"
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
log_title "GitLink 科研辅助 — 进度跟踪与预警"
|
||||
|
||||
check_auth
|
||||
local today
|
||||
today=$(date_today)
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
if [[ -n "$ORG_MODE" ]]; then
|
||||
OWNER="$ORG_MODE"
|
||||
REPO="__org__"
|
||||
else
|
||||
require_owner_repo
|
||||
fi
|
||||
|
||||
OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/progress-weekly-${OWNER}-${today}.html}"
|
||||
|
||||
# ═══ Data Collection ═══
|
||||
log_step "1/5 收集 Issue 数据..."
|
||||
local open_json closed_json
|
||||
open_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state open --limit 100)
|
||||
closed_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state closed --limit 100)
|
||||
|
||||
local total_open=0 total_closed=0
|
||||
if [[ "$(json_ok "$open_json")" == "true" ]]; then
|
||||
total_open=$(echo "$open_json" | jq -r '(.data.issues // .data | if type == "array" then length else 0 end)' 2>/dev/null)
|
||||
fi
|
||||
if [[ "$(json_ok "$closed_json")" == "true" ]]; then
|
||||
total_closed=$(echo "$closed_json" | jq -r '(.data.issues // .data | if type == "array" then length else 0 end)' 2>/dev/null)
|
||||
fi
|
||||
|
||||
log_info " Issues: ${total_open} 开放 / ${total_closed} 已关闭"
|
||||
|
||||
# ═══ PR Data ═══
|
||||
log_step "2/5 收集 PR 数据..."
|
||||
local merged_json open_prs_json
|
||||
merged_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state merged --limit 100)
|
||||
open_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state open --limit 50)
|
||||
|
||||
local total_merged=0 total_open_prs=0
|
||||
if [[ "$(json_ok "$merged_json")" == "true" ]]; then
|
||||
total_merged=$(echo "$merged_json" | jq -r '(.data.issues // .data.pulls // .data | if type == "array" then length else 0 end)' 2>/dev/null)
|
||||
fi
|
||||
if [[ "$(json_ok "$open_prs_json")" == "true" ]]; then
|
||||
total_open_prs=$(echo "$open_prs_json" | jq -r '(.data.issues // .data.pulls // .data | if type == "array" then length else 0 end)' 2>/dev/null)
|
||||
fi
|
||||
|
||||
log_info " PRs: ${total_open_prs} 开放 / ${total_merged} 已合并"
|
||||
|
||||
# ═══ Release Data ═══
|
||||
log_step "3/5 收集 Release 数据..."
|
||||
local release_json release_count=0 last_release_date=""
|
||||
release_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 20)
|
||||
if [[ "$(json_ok "$release_json")" == "true" ]]; then
|
||||
release_count=$(echo "$release_json" | jq -r '(.data.releases | length) // 0' 2>/dev/null)
|
||||
last_release_date=$(echo "$release_json" | jq -r '.data.releases[0].created_at // ""' 2>/dev/null)
|
||||
if [[ -n "$last_release_date" ]]; then
|
||||
last_release_date="${last_release_date:0:10}"
|
||||
fi
|
||||
fi
|
||||
log_info " Releases: ${release_count}"
|
||||
|
||||
# ═══ CI Data ═══
|
||||
log_step "4/5 收集 CI 数据..."
|
||||
local ci_json ci_builds=0 ci_ok=0
|
||||
ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 20)
|
||||
if [[ "$(json_ok "$ci_json")" == "true" ]]; then
|
||||
ci_builds=$(echo "$ci_json" | jq -r '(.data | length) // 0' 2>/dev/null)
|
||||
ci_ok=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0")
|
||||
fi
|
||||
log_info " CI: ${ci_ok}/${ci_builds} 通过"
|
||||
|
||||
# ═══ Health Score ═══
|
||||
log_step "5/5 计算健康评分和异常检测..."
|
||||
local health_score
|
||||
# Provide prev period data as half of current (simplified estimation)
|
||||
local prev_closed=$((total_closed / 2))
|
||||
health_score=$(calc_health "$total_open" "$total_closed" "$total_merged" "$total_open_prs" \
|
||||
"$release_count" "$ci_ok" "$ci_builds" "$total_open" "$prev_closed")
|
||||
|
||||
local anomalies_json
|
||||
anomalies_json=$(detect_anomalies "$total_open" "$total_closed" "$total_open_prs" \
|
||||
"$release_count" "$total_closed" "$prev_closed" "$ci_ok" "$ci_builds")
|
||||
|
||||
local anomaly_count
|
||||
anomaly_count=$(echo "$anomalies_json" | jq -r '.count // 0' 2>/dev/null)
|
||||
|
||||
# ═══ Health Grade ═══
|
||||
local health_label health_color
|
||||
if awk "BEGIN { exit ($health_score >= 80) ? 0 : 1 }"; then
|
||||
health_label="健康"; health_color="#2e7d32"
|
||||
elif awk "BEGIN { exit ($health_score >= 60) ? 0 : 1 }"; then
|
||||
health_label="正常"; health_color="#558b2f"
|
||||
elif awk "BEGIN { exit ($health_score >= 40) ? 0 : 1 }"; then
|
||||
health_label="需关注"; health_color="#f57c00"
|
||||
else
|
||||
health_label="风险"; health_color="#c62828"
|
||||
fi
|
||||
|
||||
log_ok "健康评分: ${health_score}/100 (${health_label})"
|
||||
if [[ $anomaly_count -gt 0 ]]; then
|
||||
log_warn "检测到 ${anomaly_count} 个异常"
|
||||
fi
|
||||
|
||||
# ═══ Generate HTML ═══
|
||||
log_step "生成周报 HTML..."
|
||||
|
||||
# Build anomaly table rows
|
||||
local anomaly_rows=""
|
||||
if [[ $anomaly_count -gt 0 ]]; then
|
||||
anomaly_rows=$(echo "$anomalies_json" | jq -r '.items[] | "<tr><td>\(.type)</td><td class=\"sev-\(.severity)\">\(.severity)</td><td>\(.detail)</td></tr>"' 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
cat > "$OUTPUT_FILE" <<HTMLEOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${OWNER}/${REPO} — 进度周报 ${today}</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 40px 30px; }
|
||||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||||
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 24px; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.08); text-align: center; }
|
||||
.card.health { background: ${health_color}; color: #fff; }
|
||||
.card .value { font-size: 32px; font-weight: 700; }
|
||||
.card .label { font-size: 12px; opacity: 0.8; margin-top: 4px; }
|
||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
.chart { width: 100%; height: 350px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||||
th { background: #f5f7fa; color: #555; font-weight: 600; }
|
||||
.sev-Critical { color: #c62828; font-weight: 700; }
|
||||
.sev-Warning { color: #e65100; font-weight: 600; }
|
||||
.sev-Info { color: #1565c0; }
|
||||
.ok { color: #2e7d32; font-weight: 600; }
|
||||
.warn { color: #e65100; font-weight: 600; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
@media (max-width: 768px) { .row { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>${OWNER}/${REPO} — 科研进度周报</h1>
|
||||
<div style="opacity:0.8;font-size:14px;">$LOOKBACK_WEEKS 周回顾 — ${today}</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="cards">
|
||||
<div class="card health">
|
||||
<div class="value">${health_score}</div>
|
||||
<div class="label">健康评分 / 100 — ${health_label}</div>
|
||||
</div>
|
||||
<div class="card"><div class="value">${total_open}</div><div class="label">开放 Issues</div></div>
|
||||
<div class="card"><div class="value">${total_open_prs}</div><div class="label">开放 PRs</div></div>
|
||||
<div class="card"><div class="value">${total_merged}</div><div class="label">已合并 PRs</div></div>
|
||||
<div class="card"><div class="value">${release_count}</div><div class="label">Release 数</div></div>
|
||||
<div class="card"><div class="value">${anomaly_count}</div><div class="label">异常信号</div></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<h2>Issue / PR 概览</h2>
|
||||
<div id="overviewChart" class="chart"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>异常预警</h2>
|
||||
$( if [[ $anomaly_count -eq 0 ]]; then
|
||||
echo "<div style=\"text-align:center;padding:40px;color:#2e7d32;\"><b>未检测到异常,项目运行良好</b></div>"
|
||||
else
|
||||
echo "<table><tr><th>类型</th><th>严重度</th><th>详情</th></tr>${anomaly_rows}</table>"
|
||||
fi )
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>进度指标明细</h2>
|
||||
<table>
|
||||
<tr><th>指标</th><th>数值</th><th>趋势</th><th>建议</th></tr>
|
||||
<tr>
|
||||
<td>Issue 流速</td>
|
||||
<td>${total_open} 开放 / ${total_closed} 关闭</td>
|
||||
<td>$( [[ $total_closed -gt $total_open ]] && echo "<span class=\"ok\">↑ 改善</span>" || echo "<span class=\"warn\">↓ 积压</span>")</td>
|
||||
<td>$( [[ $total_open -gt $total_closed ]] && echo "建议安排 Issue 清理日" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PR 合并率</td>
|
||||
<td>$(awk -v m="$total_merged" -v o="$total_open_prs" 'BEGIN { t=m+o; printf "%.0f%%", (t>0?m/t*100:0) }')</td>
|
||||
<td>$( [[ $total_open_prs -le 5 ]] && echo "<span class=\"ok\">正常</span>" || echo "<span class=\"warn\">积压</span>")</td>
|
||||
<td>$( [[ $total_open_prs -gt 5 ]] && echo "建议增加 Code Review 频率" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>发布节奏</td>
|
||||
<td>${release_count} 个 Release</td>
|
||||
<td>$( [[ $release_count -ge 3 ]] && echo "<span class=\"ok\">活跃</span>" || echo "<span class=\"warn\">不活跃</span>")</td>
|
||||
<td>$( [[ $release_count -eq 0 ]] && echo "建议发布 v0.1.0 初始版本" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CI 稳定性</td>
|
||||
<td>$(awk -v o="$ci_ok" -v t="$ci_builds" 'BEGIN { printf "%.0f%%", (t>0?o/t*100:0) }') (${ci_ok}/${ci_builds})</td>
|
||||
<td>$( awk "BEGIN { if (${ci_builds}>0 && ${ci_ok}/${ci_builds}>=0.8) print \"<span class=\\\"ok\\\">稳定</span>\"; else print \"<span class=\\\"warn\\\">待改进</span>\" }" )</td>
|
||||
<td>$( [[ $ci_builds -eq 0 ]] && echo "建议配置 GitLink CI" || echo "—")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>最近 Release</td>
|
||||
<td>${last_release_date:-无}</td>
|
||||
<td>$( [[ -n "$last_release_date" ]] && echo "<span class=\"ok\">已发布</span>" || echo "<span class=\"warn\">无记录</span>")</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — ${today}</div>
|
||||
|
||||
<script>
|
||||
var overviewChart = echarts.init(document.getElementById('overviewChart'));
|
||||
overviewChart.setOption({
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: ['开放', '已完成'] },
|
||||
xAxis: { type: 'category', data: ['Issues', 'Pull Requests', 'Releases', 'CI Builds'] },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{ name: '开放', type: 'bar', data: [${total_open}, ${total_open_prs}, 0, 0], itemStyle: { color: '#fac858' } },
|
||||
{ name: '已完成', type: 'bar', data: [${total_closed}, ${total_merged}, ${release_count}, ${ci_builds}], itemStyle: { color: '#91cc75' } }
|
||||
]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
log_ok "周报已生成: $OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
# ═══ Summary ═══
|
||||
echo ""
|
||||
divider
|
||||
log_title "进度周报摘要"
|
||||
echo " 仓库: ${OWNER}/${REPO}"
|
||||
echo " 健康评分: ${health_score}/100 (${health_label})"
|
||||
echo " Issues: ${total_open} 开放 / ${total_closed} 已关闭"
|
||||
echo " PRs: ${total_open_prs} 开放 / ${total_merged} 已合并"
|
||||
echo " Releases: ${release_count}"
|
||||
echo " CI: ${ci_ok}/${ci_builds} 通过"
|
||||
echo " 异常数: ${anomaly_count}"
|
||||
if [[ $anomaly_count -gt 0 ]]; then
|
||||
echo "$anomalies_json" | jq -r '.items[] | " [\(.severity)] \(.detail)"' 2>/dev/null
|
||||
fi
|
||||
divider
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# GitLink 科研辅助 — 场景 6:一键生成论文引用格式 (PowerShell)
|
||||
param(
|
||||
[string]$Owner, [string]$Repo, [string]$Format = "all",
|
||||
[string]$Output = "", [switch]$DryRun
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Import-Module "$ScriptDir\lib\common.psm1" -Force
|
||||
Import-Module "$ScriptDir\lib\research-common.psm1" -Force
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
Check-Auth
|
||||
$resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo
|
||||
$Owner = $resolved.Owner; $Repo = $resolved.Repo
|
||||
$Today = Get-DateToday
|
||||
|
||||
Log-Title "GitLink 科研辅助 — 论文引用格式生成"
|
||||
|
||||
# 1. Fetch repo metadata
|
||||
Log-Step "获取仓库元数据..."
|
||||
$repoJson = Invoke-GLCheck @("repo", "+info", "--owner", $Owner, "--repo", $Repo)
|
||||
$repoName = $repoJson.data.name ?? $repoJson.data.full_name ?? "$Owner/$Repo"
|
||||
$repoDesc = $repoJson.data.description ?? ""
|
||||
$updatedAt = $repoJson.data.updated_at ?? ""
|
||||
|
||||
# 2. Get latest release
|
||||
Log-Step "获取最新版本..."
|
||||
$releaseJson = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "1")
|
||||
$version = if ($releaseJson.ok) { $releaseJson.data[0].tag_name ?? "v0.0.0-dev" } else { "v0.0.0-dev" }
|
||||
$releaseDate = if ($releaseJson.ok) { $releaseJson.data[0].created_at ?? $updatedAt } else { $updatedAt }
|
||||
if ($releaseDate.Length -ge 10) { $releaseDate = $releaseDate.Substring(0, 10) }
|
||||
$releaseYear = if ($releaseDate.Length -ge 4) { $releaseDate.Substring(0, 4) } else { (Get-Date).Year }
|
||||
|
||||
# 3. Get members
|
||||
Log-Step "获取贡献者列表..."
|
||||
$membersJson = Invoke-GL @("repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "20")
|
||||
$authorNames = @()
|
||||
if ($membersJson.ok) {
|
||||
$data = if ($membersJson.data.members) { $membersJson.data.members } else { $membersJson.data }
|
||||
if ($data -is [array]) {
|
||||
$authorNames = $data | ForEach-Object { $_.name ?? $_.login ?? "" } | Where-Object { $_ }
|
||||
}
|
||||
}
|
||||
if ($authorNames.Count -eq 0) { $authorNames = @($Owner) }
|
||||
|
||||
# 4. Get repo URL
|
||||
$repoUrl = try { git remote get-url origin 2>$null } catch { "" }
|
||||
if (-not $repoUrl) { $repoUrl = "https://gitlink.org.cn/$Owner/$Repo" }
|
||||
$repoUrl = $repoUrl -replace '\.git$', ''
|
||||
|
||||
# Authors formatting
|
||||
$bibtexAuthors = Format-AuthorsBibtex $authorNames
|
||||
$apaAuthors = Format-AuthorsAPA $authorNames
|
||||
|
||||
# MLA
|
||||
$parts0 = $authorNames[0] -split '\s+'
|
||||
$mlaAuthors = "$($parts0[-1]), $($parts0[0])"
|
||||
if ($authorNames.Count -gt 1) { $mlaAuthors += ", et al." }
|
||||
|
||||
# GB/T 7714
|
||||
$gbAuthors = ($authorNames | Select-Object -First 3) -join ", "
|
||||
if ($authorNames.Count -gt 3) { $gbAuthors += "等" }
|
||||
|
||||
# Short name for BibTeX key
|
||||
$shortName = $Repo -replace '[^a-zA-Z0-9_-]', '_'
|
||||
|
||||
# Generate output
|
||||
$output = ""
|
||||
|
||||
if ($Format -eq "bibtex" -or $Format -eq "all") {
|
||||
$output += "@software{$shortName,`n author = {$bibtexAuthors},`n title = {$repoName},`n version = {$version},`n date = {$releaseDate},`n publisher = {GitLink},`n url = {$repoUrl},`n note = {$repoDesc}`n}`n`n"
|
||||
}
|
||||
|
||||
if ($Format -eq "apa" -or $Format -eq "all") {
|
||||
$output += "$apaAuthors ($releaseYear). $repoName (Version $version) [Computer software].`n GitLink. $repoUrl`n`n"
|
||||
}
|
||||
|
||||
if ($Format -eq "mla" -or $Format -eq "all") {
|
||||
$output += "$mlaAuthors. $repoName. Version $version, GitLink,`n $releaseDate, $repoUrl.`n`n"
|
||||
}
|
||||
|
||||
if ($Format -eq "gbt7714" -or $Format -eq "all") {
|
||||
$output += "[1] $gbAuthors. $repoName[CP/OL]. $version. GitLink,`n $releaseDate[$Today]. $repoUrl.`n`n"
|
||||
}
|
||||
|
||||
if ($Format -eq "cff" -or $Format -eq "all") {
|
||||
$output += "cff-version: 1.2.0`nmessage: `"If you use this software, please cite it as below.`"`nauthors:`n"
|
||||
foreach ($a in $authorNames) {
|
||||
if (-not $a) { continue }
|
||||
$parts = $a -split '\s+'
|
||||
$output += " - family-names: $($parts[-1])`n given-names: $($parts[0])`n"
|
||||
}
|
||||
$output += "title: `"$repoName`"`nversion: $version`ndate-released: $($releaseDate.Substring(0, 10))`nurl: `"$repoUrl`"`n"
|
||||
}
|
||||
|
||||
Divider
|
||||
Write-Host $output
|
||||
Divider
|
||||
|
||||
if ($Output) {
|
||||
if (-not $DryRun) { $output | Out-File -FilePath $Output -Encoding UTF8; Log-Ok "已写入: $Output" }
|
||||
}
|
||||
|
||||
Log-Info "仓库: $Owner/$Repo"
|
||||
Log-Info "版本: $version | 发布日期: $releaseDate | 贡献者: $($authorNames.Count)"
|
||||
Log-Ok "引用格式生成完成 ($Format)"
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# GitLink 科研辅助 — 场景 6:一键生成论文引用格式
|
||||
# ============================================================
|
||||
# 从 GitLink 仓库提取元数据,生成 BibTeX / APA / MLA /
|
||||
# GB/T 7714-2015 / CITATION.cff 格式的学术引用
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
trap '' PIPE
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
# ── Configuration ────────────────────────────────────────────────────
|
||||
OUTPUT_FILE=""
|
||||
CITE_FORMAT="all"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: 11-research-citation.sh --owner <owner> --repo <repo> [options]
|
||||
|
||||
Options:
|
||||
--owner <owner> Repository owner (required)
|
||||
--repo <repo> Repository name (required)
|
||||
--format <format> Citation format: bibtex|apa|mla|gbt7714|cff|all (default: all)
|
||||
--output <file> Output file path (default: stdout)
|
||||
--dry-run Preview mode (no file write)
|
||||
|
||||
Examples:
|
||||
11-research-citation.sh --owner zzx-coder --repo gitlink-cli
|
||||
11-research-citation.sh --owner zzx-coder --repo gitlink-cli --format bibtex
|
||||
11-research-citation.sh --owner zzx-coder --repo gitlink-cli --output citation.bib
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Parse Arguments ──────────────────────────────────────────────────
|
||||
parse_common_args "$@"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--format) CITE_FORMAT="$2"; shift 2 ;;
|
||||
--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
log_title "GitLink 科研辅助 — 论文引用格式生成"
|
||||
|
||||
check_auth
|
||||
require_owner_repo
|
||||
local today
|
||||
today=$(date_today)
|
||||
|
||||
# Step 1: Fetch repo metadata
|
||||
log_step "获取仓库元数据..."
|
||||
local repo_json
|
||||
repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO")
|
||||
local repo_name repo_desc created_at updated_at
|
||||
repo_name=$(json_get "$repo_json" '.data.name // .data.full_name // ""')
|
||||
repo_desc=$(json_get "$repo_json" '.data.description // ""')
|
||||
created_at=$(json_get "$repo_json" '.data.created_at // ""')
|
||||
updated_at=$(json_get "$repo_json" '.data.updated_at // ""')
|
||||
|
||||
# Step 2: Get latest release
|
||||
log_step "获取最新版本..."
|
||||
local release_json version release_date
|
||||
release_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 1)
|
||||
if [[ "$(json_ok "$release_json")" == "true" ]]; then
|
||||
version=$(json_get "$release_json" '.data.releases[0].tag_name // .data.releases[0].name // ""')
|
||||
release_date=$(json_get "$release_json" '.data.releases[0].created_at // ""')
|
||||
fi
|
||||
# Fallback to "dev" if no release
|
||||
version="${version:-v0.0.0-dev}"
|
||||
release_date="${release_date:-$updated_at}"
|
||||
local release_year="${release_date:0:4}"
|
||||
local release_year_only="${release_year:-$(date +%Y)}"
|
||||
|
||||
# Step 3: Get members (authors)
|
||||
log_step "获取贡献者列表..."
|
||||
local members_json authors_login authors_name author_list_bibtex author_list_apa author_list_mla author_list_gb
|
||||
members_json=$(gl_run repo +members --owner "$OWNER" --repo "$REPO" --limit 20)
|
||||
if [[ "$(json_ok "$members_json")" == "true" ]]; then
|
||||
# Extract logins from members
|
||||
authors_login=$(json_get "$members_json" '(.data.members // .data | if type == "array" then [.[].login // .[].user.login // empty] else [] end | join(", "))')
|
||||
authors_name=$(json_get "$members_json" '(.data.members // .data | if type == "array" then [.[].name // .[].full_name // .[].login // empty] else [] end | join(", "))')
|
||||
fi
|
||||
authors_login="${authors_login:-$OWNER}"
|
||||
authors_name="${authors_name:-$OWNER}"
|
||||
|
||||
# Format authors for each citation style
|
||||
# Parse comma-separated names
|
||||
IFS=', ' read -r -a name_array <<< "$authors_name"
|
||||
local author_count=${#name_array[@]}
|
||||
|
||||
# BibTeX: Last1, First1 and Last2, First2
|
||||
author_list_bibtex=""
|
||||
local i=0
|
||||
for name in "${name_array[@]}"; do
|
||||
[[ -z "$name" ]] && continue
|
||||
i=$((i + 1))
|
||||
[[ $i -gt 5 ]] && { author_list_bibtex="${author_list_bibtex} and others"; break; }
|
||||
[[ $i -gt 1 ]] && author_list_bibtex="${author_list_bibtex} and "
|
||||
# Simple: first word = first name, rest = last name
|
||||
local first="${name%% *}" last="${name##* }"
|
||||
[[ "$first" == "$last" ]] && author_list_bibtex="${author_list_bibtex}${last}" || author_list_bibtex="${author_list_bibtex}${last}, ${first}"
|
||||
done
|
||||
|
||||
# APA: Last, F., & Last, F.
|
||||
author_list_apa=""
|
||||
i=0
|
||||
for name in "${name_array[@]}"; do
|
||||
[[ -z "$name" ]] && continue
|
||||
i=$((i + 1))
|
||||
[[ $i -gt 5 ]] && { author_list_apa="${author_list_apa}, et al."; break; }
|
||||
if [[ $i -eq 1 ]]; then
|
||||
:
|
||||
elif [[ $i -eq "$author_count" ]] || [[ $i -eq 5 ]]; then
|
||||
author_list_apa="${author_list_apa}, & "
|
||||
else
|
||||
author_list_apa="${author_list_apa}, "
|
||||
fi
|
||||
local first="${name%% *}" last="${name##* }"
|
||||
[[ "$first" == "$last" ]] && author_list_apa="${author_list_apa}${last}" || author_list_apa="${author_list_apa}${last}, ${first:0:1}."
|
||||
done
|
||||
|
||||
# MLA: Last, First, et al. (2+ authors → et al.)
|
||||
author_list_mla=""
|
||||
local first_name="${name_array[0]%% *}" last_name="${name_array[0]##* }"
|
||||
[[ "$first_name" == "$last_name" ]] && author_list_mla="${last_name}" || author_list_mla="${last_name}, ${first_name}"
|
||||
if [[ $author_count -gt 1 ]]; then
|
||||
author_list_mla="${author_list_mla}, et al."
|
||||
fi
|
||||
|
||||
# GB/T 7714: 作者1, 作者2
|
||||
author_list_gb=""
|
||||
i=0
|
||||
for name in "${name_array[@]}"; do
|
||||
[[ -z "$name" ]] && continue
|
||||
i=$((i + 1))
|
||||
[[ $i -gt 3 ]] && { author_list_gb="${author_list_gb}等"; break; }
|
||||
[[ $i -gt 1 ]] && author_list_gb="${author_list_gb}, "
|
||||
author_list_gb="${author_list_gb}${name}"
|
||||
done
|
||||
|
||||
# Step 4: Get repo URL
|
||||
local repo_url
|
||||
repo_url=$(git remote get-url origin 2>/dev/null || echo "")
|
||||
if [[ -z "$repo_url" ]]; then
|
||||
repo_url="https://gitlink.org.cn/${OWNER}/${REPO}"
|
||||
fi
|
||||
# Normalize .git suffix
|
||||
repo_url="${repo_url%.git}"
|
||||
|
||||
# Step 5: Try to detect DOI
|
||||
log_step "检测 DOI..."
|
||||
local doi=""
|
||||
if echo "$repo_desc" | grep -qoP '10\.\d{4,}/[\w.\-/]+'; then
|
||||
doi=$(echo "$repo_desc" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1)
|
||||
fi
|
||||
|
||||
# Short name for BibTeX key
|
||||
local short_name
|
||||
short_name=$(echo "$REPO" | sed 's/[^a-zA-Z0-9_-]/_/g' | head -c 32)
|
||||
|
||||
# ── Generate Citations ────────────────────────────────────────────
|
||||
local output=""
|
||||
log_step "生成引用格式..."
|
||||
|
||||
# BibTeX
|
||||
if [[ "$CITE_FORMAT" == "bibtex" || "$CITE_FORMAT" == "all" ]]; then
|
||||
output+="@software{${short_name},
|
||||
author = {${author_list_bibtex}},
|
||||
title = {${repo_name}},
|
||||
version = {${version}},
|
||||
date = {${release_date}},
|
||||
publisher = {GitLink},
|
||||
url = {${repo_url}}"
|
||||
if [[ -n "$doi" ]]; then
|
||||
output+=",
|
||||
doi = {${doi}}"
|
||||
fi
|
||||
output+=",
|
||||
note = {${repo_desc}}
|
||||
}
|
||||
"
|
||||
fi
|
||||
|
||||
# APA 7th
|
||||
if [[ "$CITE_FORMAT" == "apa" || "$CITE_FORMAT" == "all" ]]; then
|
||||
output+="
|
||||
${author_list_apa} (${release_year_only}). ${repo_name} (Version ${version}) [Computer software].
|
||||
GitLink. ${repo_url}
|
||||
"
|
||||
fi
|
||||
|
||||
# MLA 9th
|
||||
if [[ "$CITE_FORMAT" == "mla" || "$CITE_FORMAT" == "all" ]]; then
|
||||
output+="
|
||||
${author_list_mla}. ${repo_name}. Version ${version}, GitLink,
|
||||
${release_date}, ${repo_url}.
|
||||
"
|
||||
fi
|
||||
|
||||
# GB/T 7714-2015
|
||||
if [[ "$CITE_FORMAT" == "gbt7714" || "$CITE_FORMAT" == "all" ]]; then
|
||||
output+="
|
||||
[1] ${author_list_gb}. ${repo_name}[CP/OL]. ${version}. GitLink,
|
||||
${release_date}[${today}]. ${repo_url}.
|
||||
"
|
||||
fi
|
||||
|
||||
# CITATION.cff
|
||||
if [[ "$CITE_FORMAT" == "cff" || "$CITE_FORMAT" == "all" ]]; then
|
||||
output+="
|
||||
cff-version: 1.2.0
|
||||
message: \"If you use this software, please cite it as below.\"
|
||||
authors:
|
||||
"
|
||||
for name in "${name_array[@]}"; do
|
||||
[[ -z "$name" ]] && continue
|
||||
local first="${name%% *}" last="${name##* }"
|
||||
output+=" - family-names: ${last}
|
||||
given-names: ${first}
|
||||
"
|
||||
done
|
||||
output+="title: \"${repo_name}\"
|
||||
version: ${version}
|
||||
date-released: ${release_date:0:10}
|
||||
url: \"${repo_url}\"
|
||||
repository-code: \"${repo_url}.git\"
|
||||
"
|
||||
if [[ -n "$doi" ]]; then
|
||||
output+="doi: ${doi}
|
||||
"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Output ────────────────────────────────────────────────────────
|
||||
log_ok "引用格式生成完成"
|
||||
|
||||
if [[ -n "$OUTPUT_FILE" ]]; then
|
||||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
echo "$output" > "$OUTPUT_FILE"
|
||||
log_ok "已写入: $OUTPUT_FILE"
|
||||
else
|
||||
log_warn "[DRY RUN] Would write to: $OUTPUT_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always print to console
|
||||
divider
|
||||
echo "$output"
|
||||
divider
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────
|
||||
log_info "仓库: ${OWNER}/${REPO}"
|
||||
log_info "版本: ${version}"
|
||||
log_info "发布日期: ${release_date}"
|
||||
log_info "贡献者数: ${author_count}"
|
||||
[[ -n "$doi" ]] && log_info "DOI: ${doi}"
|
||||
log_info "格式: ${CITE_FORMAT}"
|
||||
|
||||
# Offer to create CITATION.cff
|
||||
if [[ "${DRY_RUN:-false}" != "true" ]]; then
|
||||
echo ""
|
||||
log_info "提示: 可在仓库中创建 CITATION.cff 文件以便他人引用。"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
# Common utilities for gitlink-cli workflow scripts (PowerShell 5.1+)
|
||||
# Common utilities for gitlink-cli workflow scripts (PowerShell 5.1+)
|
||||
|
||||
# Force UTF-8 when capturing stdout from native commands.
|
||||
# On Windows Chinese locales, PS 5.1 defaults to GBK and corrupts multi-byte
|
||||
# JSON (e.g. 紧急/新增), which makes ConvertFrom-Json fail silently.
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$Script:GL = "gitlink-cli"
|
||||
|
||||
|
|
@ -30,31 +36,41 @@ function Check-Auth {
|
|||
}
|
||||
|
||||
# -- CLI Wrapper --
|
||||
# Returns parsed JSON object on success, $null on failure
|
||||
# Usage: Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo)
|
||||
function Invoke-GL {
|
||||
param([string[]]$Args)
|
||||
$output = & $Script:GL @Args --format json 2>&1
|
||||
return ($output -join "`n")
|
||||
}
|
||||
|
||||
function Invoke-GLCheck {
|
||||
param([string[]]$Args)
|
||||
$output = Invoke-GL $Args
|
||||
param([string[]]$CmdArgs)
|
||||
$allArgs = @($CmdArgs) + @("--format", "json")
|
||||
# Capture stdout only; stderr goes to console
|
||||
$output = & $Script:GL @allArgs
|
||||
$raw = ($output -join "`n")
|
||||
if (-not $raw -or $raw.Trim() -eq "") { return $null }
|
||||
try {
|
||||
$json = $output | ConvertFrom-Json
|
||||
if (-not $json.ok) {
|
||||
$errMsg = if ($json.error.message) { $json.error.message } else { "unknown error" }
|
||||
Log-Err "Command failed: $Script:GL $($Args -join ' ')"
|
||||
Log-Err $errMsg
|
||||
return $null
|
||||
}
|
||||
return $json
|
||||
return ($raw | ConvertFrom-Json)
|
||||
} catch {
|
||||
Log-Err "Command failed (non-JSON): $Script:GL $($Args -join ' ')"
|
||||
Log-Err $output
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
# Like Invoke-GL but logs error on failure
|
||||
function Invoke-GLCheck {
|
||||
param([string[]]$CmdArgs)
|
||||
$json = Invoke-GL $CmdArgs
|
||||
if (-not $json) {
|
||||
$argStr = $CmdArgs -join " "
|
||||
Log-Err "Command failed (no JSON): $Script:GL $argStr"
|
||||
return $null
|
||||
}
|
||||
if (-not $json.ok) {
|
||||
$argStr = $CmdArgs -join " "
|
||||
$errMsg = if ($json.error -and $json.error.message) { $json.error.message } else { "unknown error" }
|
||||
Log-Err "Command failed: $Script:GL $argStr"
|
||||
Log-Err $errMsg
|
||||
return $null
|
||||
}
|
||||
return $json
|
||||
}
|
||||
|
||||
# -- JSON Helpers --
|
||||
function Get-JsonOk {
|
||||
param($Json)
|
||||
|
|
|
|||
|
|
@ -167,3 +167,139 @@ parse_common_args() {
|
|||
divider() {
|
||||
echo -e "${CYAN}────────────────────────────────────────────────${NC}"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# Scientific Research Helpers
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Normalize value against max (returns 0-1, 0 if max is 0)
|
||||
normalize() {
|
||||
awk -v val="$1" -v max="$2" 'BEGIN { printf "%.4f", (max > 0) ? val / max : 0 }'
|
||||
}
|
||||
|
||||
# Clamp value between lo and hi
|
||||
clamp() {
|
||||
awk -v val="$1" -v lo="$2" -v hi="$3" 'BEGIN { printf "%.4f", (val < lo) ? lo : ((val > hi) ? hi : val) }'
|
||||
}
|
||||
|
||||
# Weighted sum: pass pairs of "value weight" as arguments
|
||||
weighted_sum() {
|
||||
awk 'BEGIN { sum=0; for(i=1;i<ARGC;i+=2) sum+=(ARGV[i]*ARGV[i+1]); printf "%.4f", sum }' "$@"
|
||||
}
|
||||
|
||||
# Jaccard similarity between two space-separated strings
|
||||
jaccard() {
|
||||
echo "$1 $2" | awk '{
|
||||
n1 = split($1, a1, " "); n2 = split($2, a2, " ");
|
||||
delete seen; inter = 0;
|
||||
for (i in a1) seen[a1[i]] = 1;
|
||||
for (i in a2) if (seen[a2[i]] == 1) { inter++; seen[a2[i]] = 2; }
|
||||
union = 0;
|
||||
for (i in seen) union++;
|
||||
printf "%.4f", (union > 0) ? inter / union : 0;
|
||||
}'
|
||||
}
|
||||
|
||||
# Detect programming language from file extension
|
||||
detect_lang_from_ext() {
|
||||
local ext="${1##*.}"
|
||||
case "$ext" in
|
||||
go) echo "Go" ;;
|
||||
py|pyx) echo "Python" ;;
|
||||
js|ts|jsx|tsx|mjs|cjs) echo "JavaScript/TypeScript" ;;
|
||||
rs) echo "Rust" ;;
|
||||
java) echo "Java" ;;
|
||||
kt|kts) echo "Kotlin" ;;
|
||||
c|cpp|cxx|h|hpp|hxx) echo "C/C++" ;;
|
||||
r|R) echo "R" ;;
|
||||
jl) echo "Julia" ;;
|
||||
m|mm) echo "MATLAB/Objective-C" ;;
|
||||
swift) echo "Swift" ;;
|
||||
rb) echo "Ruby" ;;
|
||||
php) echo "PHP" ;;
|
||||
scala) echo "Scala" ;;
|
||||
dart) echo "Dart" ;;
|
||||
lua) echo "Lua" ;;
|
||||
ipynb) echo "Jupyter Notebook" ;;
|
||||
sh|bash|zsh) echo "Shell" ;;
|
||||
ps1|psm1|psd1) echo "PowerShell" ;;
|
||||
*) echo "Other" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Cross-platform days between two dates (YYYY-MM-DD format)
|
||||
days_between() {
|
||||
local d1 d2 diff
|
||||
d1=$(date -d "$1" +%s 2>/dev/null || date -jf "%Y-%m-%d" "$1" +%s 2>/dev/null || echo "0")
|
||||
d2=$(date -d "$2" +%s 2>/dev/null || date -jf "%Y-%m-%d" "$2" +%s 2>/dev/null || echo "0")
|
||||
diff=$(( (d2 - d1) / 86400 ))
|
||||
echo "${diff#-}"
|
||||
}
|
||||
|
||||
# Get today, N days ago (cross-platform)
|
||||
date_days_ago() {
|
||||
local n="$1"
|
||||
date -d "$n days ago" +%Y-%m-%d 2>/dev/null || date -v-"$n"d +%Y-%m-%d 2>/dev/null
|
||||
}
|
||||
|
||||
# Extract first N space-separated authors into BibTeX format
|
||||
# Input: "First Last" "First2 Last2" ...
|
||||
format_authors_bibtex() {
|
||||
local names="$1" count=0 result=""
|
||||
for name in $names; do
|
||||
count=$((count + 1))
|
||||
if [[ $count -gt 10 ]]; then
|
||||
result="${result} and others"
|
||||
break
|
||||
fi
|
||||
local last="${name##* }" first="${name%% *}"
|
||||
[[ $count -gt 1 ]] && result="${result} and "
|
||||
result="${result}${last}, ${first}"
|
||||
done
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
# Extract organization name from login (try git remote or repo +info)
|
||||
org_from_owner() {
|
||||
local owner="$1"
|
||||
# check if it's an org or user by listing repos
|
||||
local out
|
||||
out=$(gl_run repo +list --user "$owner" --limit 1)
|
||||
if [[ "$(json_ok "$out")" == "true" ]]; then
|
||||
echo "$owner"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Min and max helpers for awk
|
||||
min_val() { awk -v a="$1" -v b="$2" 'BEGIN { print (a < b) ? a : b }'; }
|
||||
max_val() { awk -v a="$1" -v b="$2" 'BEGIN { print (a > b) ? a : b }'; }
|
||||
|
||||
# ── Knowledge Graph Helpers ──────────────────────────────────────────
|
||||
|
||||
# Generate a unique node ID
|
||||
kg_node_id() { echo "${1}:${2}" | tr '/' '_' | tr ' ' '_'; }
|
||||
|
||||
# URL-encode a string (basic)
|
||||
url_encode() {
|
||||
local str="$1"
|
||||
echo "$str" | jq -sRr @uri 2>/dev/null || echo "$str"
|
||||
}
|
||||
|
||||
# Escape JSON string value
|
||||
json_escape() {
|
||||
echo "$1" | jq -Rsa . 2>/dev/null || echo "\"$1\""
|
||||
}
|
||||
|
||||
# ── Color-coded Severity ──────────────────────────────────────────────
|
||||
|
||||
severity_color() {
|
||||
case "$1" in
|
||||
Critical|critical|CRITICAL) echo -e "${RED}$1${NC}" ;;
|
||||
Warning|warning|WARNING) echo -e "${YELLOW}$1${NC}" ;;
|
||||
Info|info|INFO) echo -e "${CYAN}$1${NC}" ;;
|
||||
OK|ok|CLEAN) echo -e "${GREEN}$1${NC}" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
# GitLink 科研辅助 — PowerShell 公共模块扩展
|
||||
# 在 common.psm1 基础上增加科研计算函数
|
||||
|
||||
# -- Math helpers --
|
||||
function Get-Normalized {
|
||||
param([double]$Value, [double]$Max)
|
||||
if ($Max -le 0) { return 0 }
|
||||
return [Math]::Round($Value / $Max, 4)
|
||||
}
|
||||
function Get-Clamped {
|
||||
param([double]$Value, [double]$Lo, [double]$Hi)
|
||||
return [Math]::Max($Lo, [Math]::Min($Value, $Hi))
|
||||
}
|
||||
function Get-WeightedSum {
|
||||
param([double[]]$Pairs)
|
||||
$sum = 0.0
|
||||
for ($i = 0; $i -lt $Pairs.Count; $i += 2) {
|
||||
$sum += $Pairs[$i] * $Pairs[$i + 1]
|
||||
}
|
||||
return [Math]::Round($sum, 4)
|
||||
}
|
||||
function Get-Jaccard {
|
||||
param([string[]]$ListA, [string[]]$ListB)
|
||||
$setA = @{}; foreach ($a in $ListA) { $setA[$a.Trim()] = 1 }
|
||||
$inter = 0; foreach ($b in $ListB) { if ($setA.ContainsKey($b.Trim())) { $inter++ } }
|
||||
$union = ($setA.Keys + ($ListB | ForEach-Object { $_.Trim() }) | Select-Object -Unique).Count
|
||||
if ($union -le 0) { return 0 }
|
||||
return [Math]::Round($inter / $union, 4)
|
||||
}
|
||||
function Get-DaysBetween {
|
||||
param([string]$Date1, [string]$Date2)
|
||||
try { return ([DateTime]$Date2 - [DateTime]$Date1).Days }
|
||||
catch { return 365 }
|
||||
}
|
||||
function Get-DateToday { return (Get-Date -Format "yyyy-MM-dd") }
|
||||
|
||||
# -- Citation helpers --
|
||||
function Format-AuthorsBibtex {
|
||||
param([string[]]$Authors)
|
||||
$result = ""; $count = 0
|
||||
foreach ($a in $Authors) {
|
||||
if (-not $a) { continue }
|
||||
$count++
|
||||
if ($count -gt 5) { $result += " and others"; break }
|
||||
if ($count -gt 1) { $result += " and " }
|
||||
$parts = $a -split '\s+'
|
||||
$last = $parts[-1]; $first = $parts[0]
|
||||
$result += "$last, $first"
|
||||
}
|
||||
return $result
|
||||
}
|
||||
function Format-AuthorsAPA {
|
||||
param([string[]]$Authors)
|
||||
$result = ""; $count = 0; $total = ($Authors | Where-Object { $_ }).Count
|
||||
foreach ($a in $Authors) {
|
||||
if (-not $a) { continue }
|
||||
$count++
|
||||
if ($count -gt 5) { $result += ", et al."; break }
|
||||
if ($count -eq 1) { }
|
||||
elseif ($count -eq $total -or $count -eq 5) { $result += ", & " }
|
||||
else { $result += ", " }
|
||||
$parts = $a -split '\s+'
|
||||
$last = $parts[-1]; $first = $parts[0][0]
|
||||
$result += "$last, $first."
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Get-Normalized, Get-Clamped, Get-WeightedSum, Get-Jaccard, Get-DaysBetween, Get-DateToday, Format-AuthorsBibtex, Format-AuthorsAPA
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://gitlink.org.cn/schemas/research-knowledge-graph.json",
|
||||
"title": "Research Knowledge Graph",
|
||||
"description": "Schema for the research knowledge graph generated from GitLink repository analysis",
|
||||
"type": "object",
|
||||
"required": ["metadata", "nodes", "edges"],
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"required": ["generated_at", "search_keywords"],
|
||||
"properties": {
|
||||
"generated_at": { "type": "string", "format": "date-time" },
|
||||
"search_keywords": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1
|
||||
},
|
||||
"total_repos_scanned": { "type": "integer", "minimum": 0 },
|
||||
"total_contributors_found": { "type": "integer", "minimum": 0 },
|
||||
"total_edges_inferred": { "type": "integer", "minimum": 0 },
|
||||
"api_version": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "type", "label"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique node identifier, e.g. 'repo:owner/repo-name' or 'topic:NLP'",
|
||||
"pattern": "^[a-z_]+:.+$"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Node type",
|
||||
"enum": ["repo", "contributor", "topic", "paper", "organization", "release"]
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Human-readable display name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Optional description for tooltip"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"description": "Type-specific attributes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edges": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["source", "target", "type"],
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Source node ID"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "Target node ID"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Relationship type",
|
||||
"enum": [
|
||||
"depends_on",
|
||||
"cites",
|
||||
"contributes_to",
|
||||
"forks_from",
|
||||
"has_topic",
|
||||
"collaborates_with",
|
||||
"releases",
|
||||
"references_paper",
|
||||
"implements_method",
|
||||
"related_to"
|
||||
]
|
||||
},
|
||||
"weight": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"default": 0.5,
|
||||
"description": "Edge weight / confidence"
|
||||
},
|
||||
"evidence": {
|
||||
"type": "string",
|
||||
"description": "How this edge was inferred"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>科研知识图谱 — {{REPORT_DATE}}</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
|
||||
.header { background: linear-gradient(135deg, #1a237e 0%, #3949ab 100%); color: #fff; padding: 36px 30px; }
|
||||
.header h1 { font-size: 26px; margin-bottom: 6px; }
|
||||
.header .subtitle { opacity: 0.8; font-size: 14px; }
|
||||
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin-bottom: 24px; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 18px; box-shadow: 0 2px 8px rgba(0,0,0,.08); text-align: center; }
|
||||
.card .value { font-size: 32px; font-weight: 700; color: #1a237e; }
|
||||
.card .label { font-size: 12px; color: #888; margin-top: 4px; }
|
||||
.panel { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,.08); margin-bottom: 24px; }
|
||||
.panel h2 { font-size: 18px; margin-bottom: 16px; color: #1a237e; border-bottom: 2px solid #3949ab; padding-bottom: 8px; }
|
||||
#graphChart { width: 100%; height: 600px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; }
|
||||
th { background: #f5f7fa; color: #555; font-weight: 600; }
|
||||
.tag { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; margin: 2px; }
|
||||
.tag.rising { background: #e8f5e9; color: #2e7d32; }
|
||||
.tag.stable { background: #e3f2fd; color: #1565c0; }
|
||||
.tag.declining { background: #fce4ec; color: #c62828; }
|
||||
.footer { text-align: center; padding: 24px; color: #999; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>科研知识图谱</h1>
|
||||
<div class="subtitle">
|
||||
关键词:{{KEYWORDS}} —
|
||||
仓库:{{TOTAL_REPOS}} 个 —
|
||||
贡献者:{{TOTAL_CONTRIBUTORS}} 人 —
|
||||
{{REPORT_DATE}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="cards">
|
||||
<div class="card"><div class="value">{{TOTAL_REPOS}}</div><div class="label">仓库节点</div></div>
|
||||
<div class="card"><div class="value">{{TOTAL_CONTRIBUTORS}}</div><div class="label">贡献者节点</div></div>
|
||||
<div class="card"><div class="value">{{TOTAL_TOPICS}}</div><div class="label">主题节点</div></div>
|
||||
<div class="card"><div class="value">{{TOTAL_EDGES}}</div><div class="label">关系边</div></div>
|
||||
<div class="card"><div class="value">{{HOTTEST_REPO}}</div><div class="label">最热仓库</div></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>知识图谱 — 力导向布局</h2>
|
||||
<div id="graphChart"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>热度排行榜</h2>
|
||||
<table id="hotnessTable">
|
||||
<thead><tr><th>排名</th><th>仓库</th><th>热度</th><th>语言</th><th>Stars</th><th>趋势</th></tr></thead>
|
||||
<tbody>{{TABLE_ROWS}}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="footer">Generated by GitLink Research Assistant — {{REPORT_DATE}}</div>
|
||||
|
||||
<script>
|
||||
var graph = echarts.init(document.getElementById('graphChart'));
|
||||
graph.setOption({
|
||||
tooltip: {
|
||||
formatter: function(p) {
|
||||
if (p.dataType === 'edge') return p.data.source + ' → ' + p.data.target + '<br/>' + p.data.evidence;
|
||||
var d = p.data;
|
||||
return '<b>' + d.label + '</b><br/>' + (d.desc || '') + '<br/>' +
|
||||
(d.stars ? 'Stars: ' + d.stars : '') + (d.repo_count ? ' 关联仓库: ' + d.repo_count : '');
|
||||
}
|
||||
},
|
||||
legend: [{
|
||||
data: ['仓库', '贡献者', '主题', '论文', '组织'],
|
||||
orient: 'vertical', right: 10, top: 20
|
||||
}],
|
||||
series: [{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
roam: true,
|
||||
draggable: true,
|
||||
force: {
|
||||
repulsion: 200,
|
||||
edgeLength: [80, 300],
|
||||
layoutAnimation: true
|
||||
},
|
||||
categories: [
|
||||
{ name: '仓库', itemStyle: { color: '#5470c6' }, symbol: 'roundRect' },
|
||||
{ name: '贡献者', itemStyle: { color: '#91cc75' }, symbol: 'circle' },
|
||||
{ name: '主题', itemStyle: { color: '#fac858' }, symbol: 'diamond' },
|
||||
{ name: '论文', itemStyle: { color: '#ee6666' }, symbol: 'triangle' },
|
||||
{ name: '组织', itemStyle: { color: '#73c0de' }, symbol: 'pin' }
|
||||
],
|
||||
data: {{GRAPH_NODES}},
|
||||
links: {{GRAPH_EDGES}},
|
||||
label: { show: true, fontSize: 11, formatter: '{b}' },
|
||||
emphasis: { focus: 'adjacency', label: { fontSize: 14 } },
|
||||
lineStyle: { color: '#ccc', curveness: 0.1 }
|
||||
}]
|
||||
});
|
||||
window.addEventListener('resize', function() { graph.resize(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue