feat: add gitlink-web 科研智能分析服务(子任务四)

新增基于 Flask + DeepSeek API + gitlink-cli 的科研智能分析 Web 服务:
- 14 个 Skill(核心 9 + 科研实验室 5:热点追踪/项目洞悉/合规检查/协作匹配/进度跟踪)
- 输入研究主题或仓库 → 自动调 gitlink-cli 拉数据 → DeepSeek 按 Skill 工作流生成结构化报告
- 含 app.py、deploy.py(paramiko 一键部署)、templates、skills prompts、requirements.txt

对应《课程实践任务》子任务四「应用 GitLink 辅助科研」5 个场景。
来源:GitLink 大赛 2026 子赛题四。
This commit is contained in:
wyxfzgg 2026-07-10 18:37:51 +08:00
parent 9749a4c832
commit c88e6f51f9
11 changed files with 2263 additions and 0 deletions

151
gitlink-web/README.md Normal file
View File

@ -0,0 +1,151 @@
# GitLink Skills Web Service科研智能分析服务
> 子任务四「应用 GitLink 辅助科研」的在线服务。基于 **Flask + DeepSeek API + gitlink-cli**,通过 AI Agent 自动分析 GitLink 平台数据,覆盖科研项目分析、热点追踪、合规校验、协作匹配、进度预警等全链路科研辅助场景。
>
> 🌐 在线地址:<http://121.41.212.97/skills/> 归属jiangtx/gitlink-cli 小组
---
## 一、它能做什么
输入一个研究主题或仓库地址,服务会自动调用 `gitlink-cli` 拉取 GitLink 平台数据,再由 DeepSeek 大模型按各 Skill 的工作流生成结构化分析报告。共 **14 个 Skill**,分两类:
### 🎯 核心 Skill开发者场景
| Skill | 入口 | 说明 | 典型输出 |
|---|---|---|---|
| 技术调研 `research-tracker` | `/research` | 输入主题→多关键词搜索→深度评估→调研报告 | 热点概览 / 项目排行 / 重点分析 / 趋势建议 |
| 贡献者分析 `contributor-insight` | `/contributor` | 仓库贡献者活跃度与团队健康度 | 团队概览 / 活跃排行 / 重点画像 |
| Issue 分拣 `issue-triage` | `/issue-triage` | 自动分类 Issue、评估紧急度 | 类型分类 / 紧急度 / 行动建议 |
| CI 健康巡检 `ci-health` | `/ci-health` | CI/CD 状态、构建成功率 | 健康度总览 / 构建趋势 / 故障分析 |
| 仓库健康巡检 `repo-health` | `/repo-health` | 综合活跃度、社区、代码产出 | 基本信息 / 活跃度 / 综合评分 |
| PR 效率分析 `pr-analytics` | `/pr-analytics` | PR 吞吐量、合并率 | 吞吐量 / 合并效率 / 贡献排行 |
| 跨维搜索 `cross-search` | `/cross-search` | 仓库+代码+Issue 三维搜索 | 各维命中 / 代码片段 / 讨论热点 |
| 用户分析 `user-analysis` | `/user-analysis` | 用户画像与项目参与 | 基本信息 / 活跃度 / 贡献列表 |
| 仓库对比 `repo-compare` | `/repo-compare` | 两仓库指标对比 | 基本/社区/活动对比 / 结论 |
### 🔬 科研实验室(科研场景,对应子任务四)
| Skill | 入口 | 科研场景 | 典型输出 |
|---|---|---|---|
| 热点追踪 `lab-hotspot` | `/lab-hotspot` | 多关键词搜索 + 领域知识图谱 | 热点概览 / 排行 / 知识图谱 / 趋势 |
| 项目洞悉 `lab-insight` | `/lab-insight` | 仓库+贡献者+PR/Issue 全息分析 | 项目概况 / 社区活跃 / 团队画像 |
| 合规检查 `lab-compliance` | `/lab-compliance` | License/CI/文档/复现性 | 合规评分 / 文档完整性 / 可复现性 |
| 协作匹配 `lab-match` | `/lab-match` | 新手友好度 + 社区健康度 | 项目概览 / 入门友好度 / 贡献方向 |
| 进度跟踪 `lab-track` | `/lab-track` | 多仓库批量巡检 + 三级告警 | 状态概览 / 指标表 / 预警详情 |
> 科研实验室 5 个 Skill 与《课程实践任务》子任务四场景一一对应:仓库级科研项目洞悉、科研热点追踪与知识图谱、科研项目合规与复现性检查、科研协作智能匹配、科研进度智能跟踪与预警。
---
## 二、架构
```
┌──────────────┐ HTTP ┌──────────────────────────────┐
│ 浏览器/评委 │ ───────► │ Flask (app.py, gunicorn) │
└──────────────┘ │ 路由 / /<slug> /api/run │
└──────────────┬───────────────┘
│ 按 Skill 选 agent_*()
┌──────────────▼───────────────┐
│ DeepSeek API (Chat) │
│ 注入 SKILL.md prompt + 日期 │
└──────────────┬───────────────┘
│ 工具调用 gitlink-cli
┌──────────────▼───────────────┐
│ gitlink-cli (npm 全局) │
│ repo/issue/pr/release/... │
└──────────────┬───────────────┘
┌──────────────▼───────────────┐
│ GitLink 平台 OpenAPI │
└──────────────────────────────┘
```
- `app.py`Flask 服务14 个 `agent_*` / `lab_*` 函数分别对应 14 个 Skill每个严格遵循 `skills/*.txt` 里的工作流。
- `skills/*.txt`:各 Skill 的 prompt 工作流(与仓库 `skills/gitlink-*/SKILL.md` 同源)。
- `templates/``base.html` / `index.html` / `skill.html` 三套页面。
- `reports/`:运行时生成的历史报告(运行时创建)。
---
## 三、本地运行
```bash
# 1. 依赖
pip install -r requirements.txt # flask requests gunicorn
# 2. 安装 gitlink-cli数据源
npm install -g gitlink-cli
gitlink-cli auth login # 登录 GitLink
# 3. 配置大模型 KeyDeepSeek 或兼容 OpenAI 的接口)
export API_KEY="sk-xxxxxxxx"
export API_BASE="https://api.deepseek.com/v1"
export API_MODEL="deepseek-chat"
# 4. 启动
python app.py # 默认 5000 端口;--port=80 指定
# 访问 http://localhost:5000/
```
> `API_KEY` 也可不设环境变量,`app.py` 会回退到内置占位(仅本地测试)。生产环境务必用环境变量,不要把 Key 写进代码。
---
## 四、部署到服务器
### 方式 ADevOps 流水线(推荐,可复现)
仓库根目录 `.devops/gitlink-web.yml` 是建木流水线master 合并后自动 `git clone` → 同步 `gitlink-web/``/opt/gitlink-web` → 装 Python 依赖 → 写 systemd 单元 → 重启 → 健康检查。需在建木密钥组 `gitlink_cli` 中配置:
- `wyx_ssh_pass`:服务器 SSH 密码
- `deepseek_api_key`DeepSeek API Key
### 方式 B手动脚本
```bash
cd gitlink-web
# 编辑 deploy.py 顶部的 PASSWORD 与 API_KEY或改成从环境变量读
python deploy.py
```
`deploy.py` 用 paramiko 走 SSH装系统依赖 → 装 gitlink-cli → 上传文件 → 装 Python 依赖 → 写 systemd 服务 → 启动。服务以 `gitlink-web.service` 常驻,监听 80 端口。
---
## 五、API 端点
| 端点 | 方法 | 说明 |
|---|---|---|
| `/` | GET | 首页Skill 列表) |
| `/<slug>` | GET | 单个 Skill 表单页(如 `/research`、`/lab-hotspot` |
| `/api/run` | POST | 执行 Skillbody`{"skill":"research-tracker","input":"大模型"}`,返回生成报告 |
---
## 六、目录结构
```
gitlink-web/
├── app.py # Flask 服务 + 14 个 Skill 处理函数865 行)
├── deploy.py # paramiko 一键部署脚本
├── requirements.txt # flask / requests / gunicorn
├── skills/ # 各 Skill 的 prompt 工作流
│ ├── research-tracker.txt
│ ├── contributor-insight.txt
│ ├── issue-triage.txt
│ └── ci-health.txt
└── templates/
├── base.html
├── index.html
└── skill.html
```
---
## 七、与 CLI Skills 的关系
本服务的 `skills/*.txt` 与仓库 `skills/gitlink-*/SKILL.md` 同源、可互换:
- **CLI Skill**`skills/gitlink-research-tracker/SKILL.md`):供 Claude Code / Cursor 等 Agent 平台直接调用,离线、可复现。
- **Web Skill**(本服务):封装成 Web 界面 + 后端 DeepSeek评委无需装 Agent 即可在浏览器体验。
两者覆盖相同的科研场景,互为印证。

865
gitlink-web/app.py Normal file
View File

@ -0,0 +1,865 @@
#!/usr/bin/env python3
"""GitLink Skills Web Service — 严格遵循 SKILL.md 工作流"""
import os, re, json, subprocess, time, traceback
from pathlib import Path
from flask import Flask, request, jsonify, render_template
import requests
NOW = time.strftime("%Y-%m-%d") # 当前日期注入所有prompt防止幻觉
app = Flask(__name__)
NOW = time.strftime("%Y-%m-%d") # 当前日期,注入所有 prompt 防止幻觉
API_KEY = os.environ.get("API_KEY") or ""
if not API_KEY:
# 本地测试用
API_KEY = "<YOUR_API_KEY>"
API_BASE = os.environ.get("API_BASE", "https://api.deepseek.com/v1")
API_MODEL = os.environ.get("API_MODEL", "deepseek-chat")
SKILL_DIR = Path(__file__).parent / "skills"
HISTORY_DIR = Path(__file__).parent / "reports"
HISTORY_DIR.mkdir(exist_ok=True)
SKILL_NAMES = {
"research": "research-tracker",
"contributor": "contributor-insight",
"issue-triage": "issue-triage",
"ci-health": "ci-health",
"repo-health": "repo-health",
"pr-analytics": "pr-analytics",
"cross-search": "cross-search",
"user-analysis": "user-analysis",
"repo-compare": "repo-compare",
"lab-hotspot": "lab-hotspot",
"lab-insight": "lab-insight",
"lab-compliance": "lab-compliance",
"lab-match": "lab-match",
"lab-track": "lab-track",
}
SKILL_INFO = {
"research-tracker": {"title":"技术调研","label":"研究主题","placeholder":"大模型、AI Agent、微服务","intro":"输入一个研究主题,自动拆解为多个关键词在 GitLink 上搜索相关项目,深度评估后生成调研报告。","output_desc":"热点概览(项目数/语言/活跃度)\n项目排行榜(含评分/星数/Fork/链接)\n重点分析(核心项目详情)\n趋势洞察与建议"},
"contributor-insight":{"title":"贡献者分析","label":"仓库(owner/repo)","placeholder":"ci4s/ci4sManagement-cloud","intro":"输入仓库地址,分析贡献者的活跃度、贡献趋势和团队健康度。","output_desc":"团队概览(贡献者总数/级别分布)\n活跃度排行榜PR数/Issue数/趋势)\n重点贡献者分析(画像/PR明细\n团队健康度评估与建议"},
"issue-triage":{"title":"Issue分拣","label":"仓库(owner/repo)","placeholder":"Gitlink/gitlink-cli","intro":"输入仓库地址,自动扫描 Issue 并分类Bug/Feature/Question评估紧急度和复杂度。","output_desc":"Issue总览总数/开放/已关闭)\n类型分类Bug/Feature/Docs等\n紧急度评估Urgent/High/Normal/Low\n行动建议FixNow/Investigate/Discuss\n维护建议"},
"ci-health":{"title":"CI健康巡检","label":"仓库(owner/repo)","placeholder":"jiangtx/gitlink-cli","intro":"检查仓库 CI/CD 状态open_devops、构建历史、成功率。","output_desc":"健康度总览CI激活/成功率/稳定性评分)\n构建趋势近7天/14天/30天\n故障分析与改进建议"},
"repo-health":{"title":"仓库健康巡检","label":"仓库(owner/repo)","placeholder":"Gitlink/gitlink-cli","intro":"综合评估仓库的活跃度、社区规模、代码产出和风险。","output_desc":"基本信息(语言/规模/描述)\n活跃度分析PR/Issue统计\nPR/Issue健康度\n综合评分与改进建议"},
"pr-analytics":{"title":"PR效率分析","label":"仓库(owner/repo)","placeholder":"Gitlink/gitlink-cli","intro":"统计 PR 吞吐量、合并率、贡献者活跃度。","output_desc":"PR吞吐量总数/合并/关闭)\n合并效率分析\n贡献者排行榜\n改进建议"},
"cross-search":{"title":"跨维搜索","label":"搜索主题","placeholder":"AI Agent、数据分析、容器","intro":"同时搜索 GitLink 的仓库、代码和 Issue 三个维度。","output_desc":"各维度命中概况\n仓库搜索结果\n代码片段摘要\nIssue讨论热点"},
"user-analysis":{"title":"用户分析","label":"用户名","placeholder":"jiangtx、lindiwen23","intro":"查看用户基本信息、活跃度、项目参与情况。","output_desc":"基本信息(注册时间/身份/项目数)\n活跃度分析\n项目贡献列表\n综合用户画像"},
"repo-compare":{"title":"仓库对比","label":"AvsB","placeholder":"Gitlink/gitlink-cli vs ci4s/ci4sManagement-cloud","intro":"对比两个仓库的指标差异。","output_desc":"基本信息对比(语言/规模/分支)\n社区活跃度对比\n开发活动对比PR/Issue/Release\n综合结论"},
"lab-hotspot":{"title":"热点追踪","label":"研究主题","placeholder":"大模型、AI Agent、微服务","intro":"多关键词搜索GitLink项目深度评估+领域知识图谱。","output_desc":"热点概览(项目数/语言/活跃比例)\n项目排行榜(评分/星数/Fork/链接)\n领域知识图谱Mermaid流程图\n趋势洞察与建议"},
"lab-insight":{"title":"项目洞悉","label":"仓库(owner/repo)","placeholder":"ci4s/ci4sManagement-cloud","intro":"综合仓库信息+贡献者+PR/Issue生成全息分析。","output_desc":"项目概况(描述/规模/语言)\n社区活跃度(贡献者/PR/Issue\n团队画像\n综合健康度评估"},
"lab-compliance":{"title":"合规检查","label":"仓库(owner/repo)","placeholder":"Gitlink/gitlink-cli","intro":"检查License/CI/文档完整性,输出合规评分。","output_desc":"License合规性\n文档完整性\nCI/CD完善度\n可复现性检查\n综合评分"},
"lab-match":{"title":"协作匹配","label":"仓库(owner/repo)","placeholder":"ci4s/ci4sManagement-cloud","intro":"分析Issue和社区健康度评估新手友好度。","output_desc":"项目概览(技术栈/社区规模)\n入门友好度分析\n推荐贡献方向\n社区活跃度评估"},
"lab-track":{"title":"进度跟踪","label":"仓库列表(逗号分隔)","placeholder":"repo1,repo2,repo3","intro":"批量巡检多仓库,输出健康/警告/危险状态。","output_desc":"各仓库状态概览(健康/警告/危险)\n详细指标表(贡献者/CI/活跃度)\n预警详情\n整体健康度评估"},
}
# ── 工具函数 ──
def run(cmd, timeout=30):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
out = r.stdout.strip()
if not out:
return r.stderr.strip()[:1000]
# JSON search results can be large, don't truncate too aggressively
return out[:50000]
except subprocess.TimeoutExpired:
return "[超时]"
except Exception as e:
return f"[错误] {e}"
def llm(messages, max_tokens=8192):
if not API_KEY:
return "【API_KEY 未设置】"
try:
resp = requests.post(
f"{API_BASE}/chat/completions",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={"model": API_MODEL, "messages": messages, "max_tokens": max_tokens, "temperature": 0.7},
timeout=180
)
data = resp.json()
if "choices" in data and data["choices"]:
return data["choices"][0]["message"]["content"]
return f"[API异常] {json.dumps(data, ensure_ascii=False)[:300]}"
except Exception as e:
return f"[请求失败] {e}"
def load_prompt(name):
p = SKILL_DIR / f"{name}.txt"
return p.read_text(encoding="utf-8") if p.exists() else ""
# ═══════════════════════════════════════════════════════════════════
# research-tracker严格遵循 SKILL.md 5步工作流
# Round 1: LLM 拆关键词 → 服务器执行搜索+深度评估 → Round 2: LLM 写报告
# ═══════════════════════════════════════════════════════════════════
def agent_research(user_input):
steps = [] # 记录每一步供对比
# ── Step 1: LLM 拆解关键词 ──
kw_prompt = f"""你是一个科研助手。请为研究主题「{user_input}」拆解 3~5 个搜索关键词。
要求覆盖中英文缩写全称技术术语和行业叫法
直接返回关键词列表每行一个不要多余文字"""
kw_response = llm([{"role": "user", "content": kw_prompt}])
keywords = [l.strip("-* \t") for l in kw_response.strip().split("\n") if l.strip()]
steps.append(("Step1-关键词拆解", {"prompt": kw_prompt, "llm_output": kw_response, "keywords": keywords}))
# ── Step 2: 多关键词搜索 ──
all_projects = {}
raw_searches = []
for kw in keywords[:5]:
out = run(f'gitlink-cli search +repos -k "{kw}" --format json')
raw_searches.append(f"--- {kw} ---\n{out}")
try:
for p in json.loads(out).get("data", {}).get("projects", []):
key = f"{p['author']['login']}/{p['identifier']}"
if key not in all_projects:
all_projects[key] = {"search_data": p, "keywords": [kw]}
else:
all_projects[key]["keywords"].append(kw)
except:
pass
steps.append(("Step2-多关键词搜索", {"keyword_count": len(keywords), "raw_hits": sum(1 for k in keywords), "unique": len(all_projects)}))
# ── Step 3: 去重 + 深度评估 ──
sorted_projects = sorted(all_projects.values(), key=lambda x: x["search_data"].get("praises_count", 0), reverse=True)[:8]
deep_data = []
seen_mirror = 0
for p in sorted_projects:
sd = p["search_data"]
owner, repo_name = sd["author"]["login"], sd["identifier"]
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo_name} --format json')
is_mirror = False
try:
info_data = json.loads(info)
is_mirror = info_data.get("data", {}).get("mirror", False)
if is_mirror:
seen_mirror += 1
except:
pass
url = f"https://www.gitlink.org.cn/{owner}/{repo_name}"
deep_data.append(f"# [{owner}/{repo_name}]({url})\nURL: {url}\n关键词: {', '.join(p['keywords'])}\n镜像: {'' if is_mirror else ''}\n{info}")
steps.append(("Step3-深度评估", {"deep_count": len(deep_data), "mirror_count": seen_mirror}))
# ── Step 4+5: LLM 写报告 ──
system = load_prompt("research-tracker")
real_data = "=== 搜索结果 ===\n" + "\n\n".join(raw_searches) + "\n\n=== 深度评估 ===\n" + "\n\n".join(deep_data)
report_prompt = f"""研究主题: {user_input}
搜索关键词: {', '.join(keywords)}
以下是从 GitLink 平台实时获取的真实数据
请基于这些数据严格按照 SKILL.md 的评分标准和模板生成调研报告
包含技术格局概览项目成熟度排行榜含评分重点分析趋势洞察调研建议
**超链接要求**报告中所有项目名必须使用 Markdown 超链接格式
- 格式[owner/repo](https://www.gitlink.org.cn/owner/repo)
- 排行榜重点分析推荐表中所有仓库名都必须是可点击的超链接
- 例如[Gitlink/microservices](https://www.gitlink.org.cn/Gitlink/microservices)
- 镜像项目也加链接[owner/repo](https://...)
{real_data}"""
report = llm([
{"role": "system", "content": system},
{"role": "user", "content": report_prompt}
])
steps.append(("Step4+5-报告生成", {"llm_input_chars": len(report_prompt), "report_chars": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# contributor-insight严格遵循 SKILL.md 5步工作流
# 服务器执行命令 → LLM 分析写报告
# ═══════════════════════════════════════════════════════════════════
def agent_contributor(user_input):
steps = []
parts = user_input.replace(" ", "").split("/")
if len(parts) < 2:
return "请提供 owner/repo 格式", []
owner, repo_name = parts[0], parts[1]
# Step 1: repo +info
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo_name} --format json')
steps.append(("Step1-项目概览", {"raw_len": len(info)}))
try:
contrib_count = json.loads(info).get("data", {}).get("contributor_users_count", 0)
except:
contrib_count = "?"
# Step 2: pr +list + issue +list
pr_data = run(f'gitlink-cli pr +list --owner {owner} --repo {repo_name} --format json')
issue_data = run(f'gitlink-cli issue +list --owner {owner} --repo {repo_name} --format json')
steps.append(("Step2-数据采集", {"pr_len": len(pr_data), "issue_len": len(issue_data)}))
# Step 3: user +info 提取画像
authors = set()
try:
for i in json.loads(pr_data).get("data", {}).get("issues", []):
if i.get("author_login"):
authors.add(i["author_login"])
except:
pass
user_infos = []
for a in list(authors)[:5]:
u = run(f'gitlink-cli user +info --login {a} --format json')
user_infos.append(f"# {a}\n{u}")
steps.append(("Step3-用户画像", {"author_count": len(authors)}))
# Step 4+5: LLM 分析写报告
system = load_prompt("contributor-insight")
data = f"# repo +info\n{info}\n\n# pr +list\n{pr_data}\n\n# issue +list\n{issue_data}\n\n" + "\n".join(user_infos)
prompt = f"""仓库: [{owner}/{repo_name}](https://www.gitlink.org.cn/{owner}/{repo_name})
实际贡献者(从PR提取): {', '.join(authors) if authors else ''}
repo +info contributor_users_count: {contrib_count}
以下是 gitlink-cli 获取的真实数据
请基于此生成贡献者洞察报告包含团队概览活跃度排行榜重点分析健康度评估建议
仓库名用超链接: [owner/repo](https://www.gitlink.org.cn/owner/repo)
如果数据缺失请如实标注不要编造
{data}"""
report = llm([
{"role": "system", "content": system},
{"role": "user", "content": prompt}
])
steps.append(("Step4+5-报告", {"llm_input": len(prompt), "report_len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# issue-triage严格遵循 SKILL.md 5步工作流
# ═══════════════════════════════════════════════════════════════════
def agent_issue(user_input):
steps = []
parts = user_input.replace(" ", "").split("/")
if len(parts) < 2:
return "请提供 owner/repo 格式", []
owner, repo_name = parts[0], parts[1]
# Step 1: repo +info
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo_name} --format json')
steps.append(("Step1-项目概览", {}))
# Step 2: issue +list
issues_raw = run(f'gitlink-cli issue +list --owner {owner} --repo {repo_name} --state open --format json')
steps.append(("Step2-Issue列表", {"raw_len": len(issues_raw)}))
# 客户端按 status_id 过滤
open_issues = []
try:
for i in json.loads(issues_raw).get("data", {}).get("issues", []):
sid = i.get("status_id", -1)
if sid in (1, 2, 0):
open_issues.append(i)
except:
pass
steps.append(("Step2.5-状态过滤", {"before": "?", "after": len(open_issues)}))
# Step 3: issue +view 逐条分析
views = []
for issue in open_issues[:10]:
num = issue.get("project_issues_index") or issue.get("number", "")
if num:
v = run(f'gitlink-cli issue +view --owner {owner} --repo {repo_name} --number {num} --format json')
views.append(f"# Issue #{num}: {issue.get('subject','')}\n{v}")
steps.append(("Step3-逐条分析", {"count": len(views)}))
# Step 4+5: LLM
system = load_prompt("issue-triage")
data = f"# repo +info\n{info}\n\n# issue +list (所有)\n{issues_raw}\n\n过滤后开放({len(open_issues)}条):\n" + "\n\n".join(views)
prompt = f"""仓库: [{owner}/{repo_name}](https://www.gitlink.org.cn/{owner}/{repo_name})
过滤后开放Issue: {len(open_issues)}
请按SKILL.md的4维分类规则生成分拣报告
注意: --state open 过滤不准确, 已按 status_id(1=新增,2=处理中) 过滤, status_id=0已标注异常
仓库名用 gitlink.org.cn 超链接不要用 github.com
如果数据中有字段缺失请如实说
{data}"""
report = llm([
{"role": "system", "content": system},
{"role": "user", "content": prompt}
])
steps.append(("Step4+5-报告", {"report_len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# ci-health严格遵循 SKILL.md 5步工作流
# ═══════════════════════════════════════════════════════════════════
def agent_ci(user_input):
steps = []
parts = user_input.replace(" ", "").split("/")
if len(parts) < 2:
return "请提供 owner/repo 格式", []
owner, repo_name = parts[0], parts[1]
# Step 1: repo +info → open_devops
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo_name} --format json')
open_devops = False
try:
d = json.loads(info).get("data", {})
open_devops = d.get("open_devops", False)
except:
pass
steps.append(("Step1-CI授权检查", {"open_devops": open_devops}))
if not open_devops:
return f"该仓库 CI/CD 未激活 (open_devops=false)。\n\n## repo 基本信息\n{info}\n\n建议在 GitLink Web 界面开启 DevOps 后重新巡检。", steps
# Step 2: ci +builds
builds = run(f'gitlink-cli ci +builds --owner {owner} --repo {repo_name} --format json')
steps.append(("Step2-构建历史", {"builds_len": len(builds)}))
# Step 3: ci +logs (失败构建)
failed_ids = []
try:
for b in json.loads(builds).get("data", {}).get("builds", [])[:5]:
if b.get("status") == "failed":
failed_ids.append(b.get("id", ""))
except:
pass
logs_data = []
for bid in failed_ids[:3]:
l = run(f'gitlink-cli ci +logs --owner {owner} --repo {repo_name} --build {bid} --format json')
logs_data.append(f"# Build {bid}\n{l}")
steps.append(("Step3-失败日志", {"failed_count": len(failed_ids)}))
# Step 4+5: LLM
system = load_prompt("ci-health")
data = f"# repo +info\n{info}\n\n# ci +builds\n{builds}\n\n" + ("\n".join(logs_data) if logs_data else "# 无失败构建")
prompt = f"""仓库: {owner}/{repo_name}
CI激活: {'' if open_devops else ''}
请生成 CI 健康巡检报告包含健康度总览构建趋势故障分析改进建议
open_devops=false 表示CI未激活此时无构建数据
{data}"""
report = llm([
{"role": "system", "content": system},
{"role": "user", "content": prompt}
])
steps.append(("Step4+5-报告", {"report_len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# repo-health仓库健康巡检 — 综合评估仓库活动、代码规模、社区参与
# ═══════════════════════════════════════════════════════════════════
def agent_repo_health(user_input):
steps = []
parts = user_input.replace(" ", "").split("/")
if len(parts) < 2:
return "请提供 owner/repo 格式", []
owner, repo_name = parts[0], parts[1]
# repo +info
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo_name} --format json')
steps.append(("Step1-基本信息", {"len": len(info)}))
# issue +list + pr +list
issues = run(f'gitlink-cli issue +list --owner {owner} --repo {repo_name} --format json')
prs = run(f'gitlink-cli pr +list --owner {owner} --repo {repo_name} --format json')
steps.append(("Step2-活动数据", {"issues_len": len(issues), "prs_len": len(prs)}))
data = f"# repo +info\n{info}\n\n# issue +list\n{issues}\n\n# pr +list\n{prs}"
report = llm([
{"role": "system", "content": "你是一个仓库健康度评估专家。基于 gitlink-cli 获取的真实数据,评估仓库的综合健康度。"},
{"role": "user", "content": f"""仓库: {owner}/{repo_name}
请基于以下真实数据生成仓库健康巡检报告包含
1. 仓库基本信息语言规模创建时间
2. 代码活跃度Issue 数量PR 数量贡献者数
3. PR/Issue 健康度合并比例开放比例
4. 综合健康评分满分 20和风险提示
{data}"""}
])
steps.append(("Step3-报告", {"len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# pr-analyticsPR 效率分析 — 分析合并时间、Review 模式、贡献节奏
# ═══════════════════════════════════════════════════════════════════
def agent_pr_analytics(user_input):
steps = []
parts = user_input.replace(" ", "").split("/")
if len(parts) < 2:
return "请提供 owner/repo 格式", []
owner, repo_name = parts[0], parts[1]
# pr +list 获取全量
all_prs = run(f'gitlink-cli pr +list --owner {owner} --repo {repo_name} --format json')
steps.append(("Step1-PR列表", {"len": len(all_prs)}))
# 提取统计
total, merged, authors = 0, 0, set()
try:
for i in json.loads(all_prs).get("data", {}).get("issues", []):
total += 1
if i.get("pull_request_status") == 1:
merged += 1
if i.get("author_login"):
authors.add(i["author_login"])
except:
pass
steps.append(("Step2-统计", {"total": total, "merged": merged, "authors": len(authors)}))
data = f"# pr +list\n{all_prs}"
report = llm([
{"role": "system", "content": "你是一个开源项目 PR 效率分析师。基于真实数据生成 PR 效率分析报告。"},
{"role": "user", "content": f"""仓库: {owner}/{repo_name}
PR: {total}, 已合并: {merged}, 贡献者: {len(authors)}
请基于以下真实数据生成 PR 效率分析报告包含
1. PR 吞吐量总数合并数关闭数
2. 贡献者活跃度人均 PR Top 贡献者
3. 合并效率合并比例
4. 改进建议
{data}"""}
])
steps.append(("Step3-报告", {"len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# cross-search跨维搜索 — 同时搜仓库、代码、Issue 并汇总
# ═══════════════════════════════════════════════════════════════════
def agent_cross_search(user_input):
steps = []
kw = user_input.strip()
# 三维搜索
repos = run(f'gitlink-cli search +repos -k "{kw}" --format json')
code = run(f'gitlink-cli search +code -k "{kw}" --format json')
issues = run(f'gitlink-cli search +issues -k "{kw}" --format json')
steps.append(("Step1-三维搜索", {"repos_len": len(repos), "code_len": len(code), "issues_len": len(issues)}))
# 统计
repo_count, code_count, issue_count = 0, 0, 0
try: repo_count = len(json.loads(repos).get("data",{}).get("projects",[]))
except: pass
try: code_count = len(json.loads(code).get("data",{}).get("results",[]))
except: pass
try: issue_count = len(json.loads(issues).get("data",{}).get("issues",[]))
except: pass
data = f"# 仓库搜索\n{repos}\n\n# 代码搜索\n{code}\n\n# Issue搜索\n{issues}"
report = llm([
{"role": "system", "content": "你是一个搜索分析师。汇总 GitLink 多维度搜索结果,生成综合分析报告。所有仓库名用 Markdown 超链接 [owner/repo](https://www.gitlink.org.cn/owner/repo) 格式。"},
{"role": "user", "content": f"""搜索关键词: {kw}
仓库命中: {repo_count} 代码命中: {code_count} Issue命中: {issue_count}
请基于以下搜索数据生成综合分析报告包含
1. 各维度命中概况
2. 仓库搜索结果分析
3. 代码搜索结果提炼热门代码片段
4. Issue 讨论热点
5. 综合洞察
{data}"""}
])
steps.append(("Step2-报告", {"len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# user-analysis用户分析 — 搜索用户 + 查看用户信息
# ═══════════════════════════════════════════════════════════════════
def agent_user_analysis(user_input):
steps = []
login = user_input.strip()
# 搜索用户
search = run(f'gitlink-cli search +users -k "{login}" --format json')
steps.append(("Step1-用户搜索", {"len": len(search)}))
# 用户信息
info = run(f'gitlink-cli user +info --login {login} --format json')
steps.append(("Step2-用户信息", {"len": len(info)}))
data = f"# search +users\n{search}\n\n# user +info\n{info}"
report = llm([
{"role": "system", "content": "你是一个用户分析专家。基于 GitLink 用户数据生成用户分析报告。"},
{"role": "user", "content": f"""用户名: {login}
请基于以下真实数据生成用户分析报告
1. 用户基本信息注册时间身份项目数
2. 用户活跃度参与项目关注数
3. 搜索匹配情况
4. 综合画像
{data}"""}
])
steps.append(("Step3-报告", {"len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# repo-compare仓库对比 — 对比两个仓库的核心指标
# ═══════════════════════════════════════════════════════════════════
def agent_repo_compare(user_input):
steps = []
# 解析 "A vs B" 格式
parts = [p.strip() for p in user_input.replace("vs", " vs ").split("vs") if p.strip()]
if len(parts) < 2:
return "请用 A vs B 格式输入两个仓库例如Gitlink/gitlink-cli vs ci4s/ci4sManagement-cloud", []
repo_a, repo_b = parts[0], parts[1]
o1, r1 = repo_a.replace(" ", "").split("/")[:2]
o2, r2 = repo_b.replace(" ", "").split("/")[:2]
info_a = run(f'gitlink-cli repo +info --owner {o1} --repo {r1} --format json')
info_b = run(f'gitlink-cli repo +info --owner {o2} --repo {r2} --format json')
steps.append(("Step1-获取数据", {"repo_a_len": len(info_a), "repo_b_len": len(info_b)}))
data = f"# 仓库A: [{repo_a}](https://www.gitlink.org.cn/{repo_a})\n{info_a}\n\n# 仓库B: [{repo_b}](https://www.gitlink.org.cn/{repo_b})\n{info_b}"
report = llm([
{"role": "system", "content": "你是一个仓库对比分析师。对比两个 GitLink 仓库的指标异同。报告中所有仓库名必须用 Markdown 超链接 [owner/repo](https://www.gitlink.org.cn/owner/repo) 格式。"},
{"role": "user", "content": f"""仓库A: {repo_a}
仓库B: {repo_b}
请基于真实数据生成仓库对比报告
1. 基本信息对比语言规模分支
2. 社区活跃度对比贡献者watchfork
3. 开发活动对比PRIssueRelease
4. 综合结论与推荐
{data}"""}
])
steps.append(("Step2-报告", {"len": len(report)}))
return report, steps
# ═══════════════════════════════════════════════════════════════════
# 科研实验室5 个科研场景工作流
# ═══════════════════════════════════════════════════════════════════
def lab_hotspot(user_input):
"""热点追踪+知识图谱:搜项目 → 深度评估 → 提取Fork关系链"""
steps = []
# 拆关键词
kw_resp = llm([{"role": "user", "content": f"为研究主题「{user_input}」拆 3~5 个搜索关键词,每行一个"}])
kws = [l.strip("-* \t") for l in kw_resp.strip().split("\n") if l.strip()][:5]
steps.append(("Step1-关键词拆解", {"kws": kws}))
# 搜索
all_p = {}
for kw in kws:
out = run(f'gitlink-cli search +repos -k "{kw}" --format json')
try:
for p in json.loads(out).get("data",{}).get("projects",[]):
k = f"{p['author']['login']}/{p['identifier']}"
all_p.setdefault(k, {"data": p, "kws": []})["kws"].append(kw)
except: pass
steps.append(("Step2-搜索", {"unique": len(all_p)}))
# 深度评估 + 提取 fork 关系
sorted_p = sorted(all_p.values(), key=lambda x: x["data"].get("praises_count",0), reverse=True)[:8]
deep, relations = [], []
for p in sorted_p:
sd = p["data"]; owner, repo = sd["author"]["login"], sd["identifier"]
info = run(f'gitlink-cli repo +info --owner {owner} --repo {repo} --format json')
try:
d = json.loads(info).get("data",{})
mirror = d.get("mirror",False)
fork_from = d.get("forked_from_project_id")
url = f"https://www.gitlink.org.cn/{owner}/{repo}"
deep.append(f"[{owner}/{repo}]({url})\n关键词: {', '.join(p['kws'])}\n镜像: {mirror}\nFork来源: {fork_from}\n{info}")
# 知识图谱关系
if fork_from:
relations.append(f"{owner}/{repo} → Fork了 → 项目ID {fork_from}")
if d.get("fork_info"):
parent = d["fork_info"].get("fork_form_name","?")
relations.append(f"{owner}/{repo} → Fork自 → {d['fork_info'].get('fork_project_user_login','?')}/{parent}")
except: pass
steps.append(("Step3-深度评估+Fork图谱", {"deep": len(deep), "relations": len(relations)}))
data = "=== 搜索结果 ===\n" + "\n".join([f"--- {kw} ---\n{run(f'gitlink-cli search +repos -k \"{kw}\" --format json')}" for kw in kws]) + "\n\n=== Fork关系 ===\n" + "\n".join(relations) + "\n\n=== 深度评估 ===\n" + "\n".join(deep)
report = llm([
{"role": "system", "content": "你是一个科研热点分析师。基于 GitLink 真实数据生成热点追踪+知识图谱报告。所有仓库链接必须用 gitlink.org.cn 域名,不要用 github.com。"},
{"role": "user", "content": f"""当前日期: {NOW}
研究主题: {user_input}
关键词: {', '.join(kws)}
请生成报告日期写{NOW}
1. 热点概览项目数语言活跃项目比例
2. 项目排行榜含成熟度评分仓库名用 gitlink.org.cn 超链接不要用 github.com
3. **领域知识图谱** Mermaid 流程图画出该研究领域的知识结构图包含核心概念子方向关键技术及其关系不是项目Fork关系而是学术概念之间的关系
4. 趋势洞察与建议
Mermaid 知识图谱示例格式使用纯文本不要用 HTML 标签或 br**每个连接单独一行不要用 & 号连接多个节点**
```mermaid
flowchart LR
A[核心概念] --> B[子方向1]
A --> C[子方向2]
B --> D[技术方法1]
C --> E[技术方法2]
```
{data}"""}
])
# 清理 AI 在 Mermaid 代码块中混入的 HTML 标签和非法语法
report_clean = re.sub(r'<[^>]+>', '', report)
report_clean = re.sub(r'(\w+)\s*&', '', report_clean) # 去掉 X & Y --> Z 中的 &
steps.append(("Step4-报告", {"len": len(report_clean)}))
return report_clean, steps
def lab_insight(user_input):
"""项目洞悉:一个仓库的综合全息分析"""
steps = []
parts = user_input.replace(" ","").split("/")
if len(parts) < 2: return "请提供 owner/repo 格式", []
o, r = parts[0], parts[1]
url_s = f"https://www.gitlink.org.cn/{o}/{r}"
info = run(f'gitlink-cli repo +info --owner {o} --repo {r} --format json')
prs = run(f'gitlink-cli pr +list --owner {o} --repo {r} --format json')
issues = run(f'gitlink-cli issue +list --owner {o} --repo {r} --format json')
steps.append(("Step1-数据采集", {}))
# 提取贡献者
authors = set()
try:
for i in json.loads(prs).get("data",{}).get("issues",[]):
if i.get("author_login"): authors.add(i["author_login"])
except: pass
user_d = []
for a in list(authors)[:5]:
u = run(f'gitlink-cli user +info --login {a} --format json')
user_d.append(f"# {a}\n{u}")
data = f"# [{o}/{r}]({url_s})\n{info}\n\n# PR\n{prs}\n\n# Issues\n{issues}\n\n" + "\n".join(user_d)
report = llm([
{"role": "system", "content": f"你是一个项目洞悉分析师。当前日期{NOW}。综合仓库数据生成全息分析报告。"},
{"role": "user", "content": f"""仓库: [{o}/{r}]({url_s})
贡献者: {', '.join(authors) if authors else ''}
请综合以下数据生成项目洞悉报告包含
1. 项目概况语言规模描述
2. 社区活跃度贡献者PRIssue
3. 团队画像核心贡献者特点
4. 综合健康度评估与建议
所有仓库名用超链接 [owner/repo](https://www.gitlink.org.cn/owner/repo)
{data}"""}
])
steps.append(("Step2-报告", {"len": len(report)}))
return report, steps
def lab_compliance(user_input):
"""合规复现检查License、README、CI、Release"""
steps = []
parts = user_input.replace(" ","").split("/")
if len(parts) < 2: return "请提供 owner/repo 格式", []
o, r = parts[0], parts[1]
url_s = f"https://www.gitlink.org.cn/{o}/{r}"
info = run(f'gitlink-cli repo +info --owner {o} --repo {r} --format json')
builds = run(f'gitlink-cli ci +builds --owner {o} --repo {r} --format json')
steps.append(("Step1-数据采集", {}))
# 提取关键字段
has_license, has_readme, has_ci, size, desc, release = "?", "?", "?", "?", "?", 0
try:
d = json.loads(info).get("data",{})
has_license = "✅ 有" if d.get("license_id") else "❌ 无"
has_readme = "✅ 有" if d.get("description") else "⚠️ 可能无"
has_ci = "✅ 已激活" if d.get("open_devops") else "❌ 未激活"
size = d.get("size","?")
desc = (d.get("description") or "无描述")[:100]
release = d.get("version_releases_count",0)
except: pass
data = f"# [{o}/{r}]({url_s})\nLicense: {has_license}\nCI: {has_ci}\nSize: {size}\nRelease: {release}\nDesc: {desc}\n{info}\n\n# CI Builds\n{builds}"
report = llm([
{"role": "system", "content": f"你是一个开源合规分析师。当前日期{NOW}。检查仓库的合规性和可复现性。"},
{"role": "user", "content": f"""仓库: [{o}/{r}]({url_s})
检查结果
- License: {has_license}
- CI激活: {has_ci}
- Release: {release}
- 描述: {desc}
请在此基础上生成合规检查报告
1. License 合规性是否有许可证是否开源友好
2. 文档完整性README描述
3. CI/CD 完善度是否激活构建历史
4. 可复现性Release依赖管理
5. 综合评分满分 20和改进建议
{data}"""}
])
steps.append(("Step2-报告", {"len": len(report)}))
return report, steps
def lab_match(user_input):
"""协作匹配:找入门 Issue + 评估社区友好度"""
steps = []
parts = user_input.replace(" ","").split("/")
if len(parts) < 2: return "请提供 owner/repo 格式", []
o, r = parts[0], parts[1]
url_s = f"https://www.gitlink.org.cn/{o}/{r}"
info = run(f'gitlink-cli repo +info --owner {o} --repo {r} --format json')
prs = run(f'gitlink-cli pr +list --owner {o} --repo {r} --format json')
issues = run(f'gitlink-cli issue +list --owner {o} --repo {r} --state open --format json')
steps.append(("Step1-数据采集", {}))
data = f"# [{o}/{r}]({url_s})\n{info}\n\n# PR\n{prs}\n\n# Open Issues\n{issues}"
report = llm([
{"role": "system", "content": f"你是一个开源协作匹配专家。当前日期{NOW}。评估项目对新贡献者的友好度。"},
{"role": "user", "content": f"""仓库: [{o}/{r}]({url_s})
请生成协作匹配报告
1. 项目概览技术栈社区规模
2. 入门友好度分析
- 是否有 good-first-issue 标签
- Issue 描述是否清晰
- PR Review 是否及时
3. 推荐适合贡献的方向具体 Issue 或模块
4. 社区健康度贡献者多样性响应速度
仓库名用 gitlink.org.cn 超链接不要用 github.com
{data}"""}
])
steps.append(("Step2-报告", {"len": len(report)}))
return report, steps
def lab_track(user_input):
"""进度跟踪与预警:监控多个仓库的活动状态"""
steps = []
repos = [r.strip().replace(" ","") for r in user_input.split(",") if r.strip()]
if not repos: return "请提供仓库列表,用逗号分隔", []
items = []
warns = {"stale": [], "no_ci": [], "inactive": []}
for repo in repos:
if "/" not in repo: continue
o, rn = repo.split("/")[:2]
info = run(f'gitlink-cli repo +info --owner {o} --repo {rn} --format json')
try:
d = json.loads(info).get("data",{})
contrib = d.get("contributor_users_count",0)
ci = d.get("open_devops",False)
updated = d.get("full_name","?")
items.append({"repo": repo, "contrib": contrib, "ci": ci, "data": d})
if not ci: warns["no_ci"].append(repo)
if contrib == 0: warns["inactive"].append(repo)
except: pass
steps.append(("Step1-巡检", {"count": len(items), "warns": {k: len(v) for k,v in warns.items()}}))
def repo_info_line(repo):
o, rn = repo.split("/")[:2]
return f"# {repo}\n{run(f'gitlink-cli repo +info --owner {o} --repo {rn} --format json')}"
data_lines = [repo_info_line(r) for r in repos if "/" in r]
data = "\n\n".join(data_lines)
warn_info = "\n".join([f"- ⚠️ {r}: {'CI未激活' if r in warns['no_ci'] else ''} {'无活跃贡献者' if r in warns['inactive'] else ''}" for r in repos if r in warns['no_ci'] or r in warns['inactive']]) or "无预警"
report = llm([
{"role": "system", "content": f"你是一个开源项目进度跟踪分析师。当前日期{NOW}。生成多仓库进度报告和预警。"},
{"role": "user", "content": f"""监控仓库: {', '.join(repos)}
预警信息: {warn_info}
请生成进度跟踪报告
1. 各仓库状态概览健康/警告/危险
2. 详细状态表贡献者CI活跃度
3. 预警详情哪些仓库需要关注
4. 整体健康度评估
{data}"""}
])
steps.append(("Step3-报告", {"len": len(report)}))
return report, steps
LAB_AGENTS = {
"lab-hotspot": lab_hotspot,
"lab-insight": lab_insight,
"lab-compliance": lab_compliance,
"lab-match": lab_match,
"lab-track": lab_track,
}
AGENTS = {
"research-tracker": agent_research,
"contributor-insight": agent_contributor,
"issue-triage": agent_issue,
"ci-health": agent_ci,
"repo-health": agent_repo_health,
"pr-analytics": agent_pr_analytics,
"cross-search": agent_cross_search,
"user-analysis": agent_user_analysis,
"repo-compare": agent_repo_compare,
**LAB_AGENTS,
}
# ── Routes ──
@app.route("/")
def index():
return render_template("index.html", skills=SKILL_INFO, url_map={v: k for k, v in SKILL_NAMES.items()})
@app.route("/<slug>")
def page(slug):
name = SKILL_NAMES.get(slug, slug)
info = SKILL_INFO.get(name)
if not info:
return "Skill not found", 404
return render_template("skill.html", skill_name=name, info=info)
@app.route("/api/run", methods=["POST"])
def api_run():
name = request.form.get("skill", "")
user_input = request.form.get("input", "").strip()
if not name or not user_input:
return jsonify({"error": "缺少参数"}), 400
agent = AGENTS.get(name)
if not agent:
return jsonify({"error": f"未知 skill: {name}"}), 400
try:
report, steps = agent(user_input)
except Exception as e:
return jsonify({"error": str(e), "traceback": traceback.format_exc()}), 500
timestamp = time.strftime("%Y%m%d_%H%M%S")
(HISTORY_DIR / f"{name}_{timestamp}.md").write_text(
f"# {SKILL_INFO[name]['title']} 报告\n\n## 输入\n{user_input}\n\n## 结果\n\n{report}\n\n---\n*生成: {time.strftime('%Y-%m-%d %H:%M:%S')}*",
encoding="utf-8"
)
return jsonify({"report": report, "steps": steps})
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser()
p.add_argument("--port", type=int, default=int(os.environ.get("PORT", 5000)))
p.add_argument("--host", default="0.0.0.0")
p.add_argument("--debug", action="store_true", default=False)
args = p.parse_args()
print(f"GitLink Skills Web Service: http://{args.host}:{args.port}")
app.run(host=args.host, port=args.port, debug=args.debug)

104
gitlink-web/deploy.py Normal file
View File

@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Deploy GitLink Skills Web Service to Alibaba Cloud."""
import os
import sys
import subprocess
HOST = "121.41.212.97"
PORT = 22
USER = "root"
PASSWORD = "<YOUR_PASSWORD>"
API_KEY = "<YOUR_API_KEY>"
LOCAL_DIR = os.path.dirname(os.path.abspath(__file__))
REMOTE_DIR = "/opt/gitlink-web"
try:
import paramiko
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "paramiko", "-q"])
import paramiko
def run_ssh(ssh, cmd):
_, stdout, stderr = ssh.exec_command(cmd)
exit_code = stdout.channel.recv_exit_status()
out = stdout.read().decode().strip()
err = stderr.read().decode().strip()
return out, err, exit_code
def main():
print(f"[1/8] Connecting to {HOST}...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, PASSWORD, timeout=10)
print(" OK - Connected")
print("[2/8] Installing system dependencies...")
run_ssh(ssh, "apt-get update -qq && apt-get install -y -qq python3-pip npm nodejs 2>&1 | tail -3")
print("[3/8] Installing gitlink-cli...")
run_ssh(ssh, "npm install -g gitlink-cli 2>&1 | tail -3")
v, _, _ = run_ssh(ssh, "gitlink-cli version 2>&1 || echo 'not found'")
print(f" gitlink-cli: {v}")
print(f"[4/8] Creating {REMOTE_DIR}...")
run_ssh(ssh, f"mkdir -p {REMOTE_DIR}/templates {REMOTE_DIR}/skills {REMOTE_DIR}/reports")
print("[5/8] Uploading files...")
sftp = ssh.open_sftp()
for root, dirs, files in os.walk(LOCAL_DIR):
for fname in files:
if fname.endswith(('.py', '.txt', '.html')) or fname in ('requirements.txt', 'README.md'):
local_path = os.path.join(root, fname)
rel_path = os.path.relpath(local_path, LOCAL_DIR)
remote_path = f"{REMOTE_DIR}/{rel_path}"
try:
sftp.put(local_path, remote_path)
print(f" -> {rel_path}")
except Exception as e:
print(f" FAIL {rel_path}: {e}")
sftp.close()
print("[6/8] Installing Python dependencies...")
run_ssh(ssh, f"cd {REMOTE_DIR} && pip3 install -r requirements.txt -q 2>&1 | tail -3")
print("[7/8] Creating systemd service...")
service = '\n'.join([
'[Unit]',
'Description=GitLink Skills Web Service',
'After=network.target',
'',
'[Service]',
'Type=simple',
'User=root',
f'WorkingDirectory={REMOTE_DIR}',
f'Environment="API_KEY={API_KEY}"',
'Environment="API_MODEL=deepseek-chat"',
f'ExecStart=/usr/bin/python3 {REMOTE_DIR}/app.py --port=80',
'Restart=always',
'RestartSec=5',
'',
'[Install]',
'WantedBy=multi-user.target',
])
escaped_service = service.replace('"', '\\"').replace("'", "\\'")
run_ssh(ssh, f"cat > /etc/systemd/system/gitlink-web.service << 'EOF'\n{service}\nEOF")
print("[8/8] Starting service...")
run_ssh(ssh, "systemctl daemon-reload")
run_ssh(ssh, "systemctl enable gitlink-web")
_, err, code = run_ssh(ssh, "systemctl start gitlink-web")
if err and "Unit" not in err:
print(f" WARN: {err[:200]}")
out, _, _ = run_ssh(ssh, "systemctl status gitlink-web --no-pager -l | head -15")
print(f"\n {out.replace(chr(10), chr(10)+' ')}")
print(f"\nService URLs:")
print(f" http://{HOST}/")
print(f" http://{HOST}/research")
print(f" http://{HOST}/contributor")
ssh.close()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,3 @@
flask>=3.0
requests>=2.31
gunicorn>=21.2

View File

@ -0,0 +1,179 @@
# gitlink-ci-healthCI 健康巡检)
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 本 Skill 为只读操作。CI 激活/关闭需通过 GitLink Web 界面操作CLI 不提供对应命令。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`GitHub CLI操作 GitLink 资源。**
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
---
## 功能概述
面向维护者的 CI/CD 健康度巡检工具:
1. **授权检查** — 确认仓库 CI 是否已激活
2. **构建历史** — 获取近期构建列表
3. **成功率统计** — 计算构建成功率和平均耗时
4. **故障分析** — 识别频繁失败的构建及其原因
5. **健康报告** — 生成 CI 健康度评分和改进建议
---
## 工作流CI 健康巡检
### Step 1检查 CI 授权状态
**方法 1推荐**:通过 `repo +info` 查看 `open_devops` 字段:
```bash
gitlink-cli repo +info --owner <owner> --repo <repo> --format json
```
- `"open_devops": true` → CI 已激活
- `"open_devops": false` → CI 未激活
**方法 2**:直接调用 `ci +builds`CI 未激活时返回:
```json
{"status": -1, "message": "接口数据异常"}
```
> ⚠️ `ci +authorize` 命令在当前 CLI 版本v0.1.18)中**不存在**。可用 CI 命令仅:`+builds`、`+logs`、`+restart`、`+stop`。
若 CI 未激活,报告中说明"CI 未启用",建议通过 GitLink Web 界面(仓库设置 → DevOps开启随后不再继续后续步骤。
### Step 2获取构建历史
```bash
gitlink-cli ci +builds --owner <owner> --repo <repo> --format json
```
提取每次构建的:
- `status` — 构建状态success/failed/running/pending
- `created_at` / `finished_at` — 时间信息
- `duration` — 耗时(如有)
- `branch` — 触发分支
如构建数量 >30取最近 30 次分析。
### Step 3构建日志失败构建
对状态为 failed 的构建获取日志:
```bash
gitlink-cli ci +logs --owner <owner> --repo <repo> --build <build_id> --format json
```
> ⚠️ **控制调用量**:仅对最近 5 次失败构建获取日志,避免过多 API 调用。日志可能过大,提取关键错误行(最后 20 行)。
### Step 4统计分析
#### 4.1 成功率计算
| 指标 | 计算方式 |
|------|----------|
| 整体成功率 | 成功构建数 / 总构建数 × 100% |
| 近 10 次成功率 | 最近 10 次中成功占比 |
| 平均修复时间 | 从失败到下次成功的平均间隔 |
#### 4.2 健康度评分(满分 20
| 维度 | 权重 | 评分标准 |
|------|------|----------|
| CI 激活 | 4 | 已激活=4未激活=0 |
| 构建成功率 | 5 | ≥90%=5≥80%=4≥70%=3≥50%=2<50%=1 |
| 近期稳定性 | 5 | 近10次全部成功=58-9次=46-7次=34-5次=2<4次=1 |
| 构建频率 | 3 | 每天有构建=32-3天=2每周=1更少=0 |
| 修复速度 | 3 | 失败后1次内修复=32-3次=2>3次=1 |
### Step 5生成 CI 健康报告
---
## 输出模板
```markdown
# 🔧 CI 健康巡检报告:{{仓库名}}
> 巡检时间:{{当前时间}}
> 仓库:{{full_name}}
> CI 状态:{{ci_status_display}}
---
## 一、健康度总览
| 指标 | 数值 | 评分 |
|------|------|------|
| CI 激活状态 | {{activated_status}} | {{activate_score}}/4 |
| 整体成功率 | {{success_rate}}%{{success_count}}/{{total_count}} | {{success_score}}/5 |
| 近期稳定性 | 近 10 次 {{recent_success}} 次成功 | {{stability_score}}/5 |
| 构建频率 | {{build_frequency_desc}} | {{frequency_score}}/3 |
| 修复速度 | {{repair_speed_desc}} | {{repair_score}}/3 |
| **总分** | | **{{total_score}}/20** |
## 二、构建趋势
```
最近 20 次构建:
✅✅❌✅✅✅❌✅✅✅✅✅❌✅✅✅✅✅✅
(✅=成功 ❌=失败)
```
| 时间段 | 总构建 | 成功 | 失败 | 成功率 |
|--------|--------|------|------|--------|
| 最近 7 天 | {{w1_total}} | {{w1_success}} | {{w1_fail}} | {{w1_rate}}% |
| 7-14 天 | {{w2_total}} | {{w2_success}} | {{w2_fail}} | {{w2_rate}}% |
| 14-30 天 | {{w3_total}} | {{w3_success}} | {{w3_fail}} | {{w3_rate}}% |
## 三、故障分析
> 如无失败构建,输出:**🎉 分析期内无失败构建CI 运行健康。**
| 构建 ID | 分支 | 失败时间 | 错误摘要 |
|---------|------|----------|----------|
| {{id}} | {{branch}} | {{time}} | {{error_summary}} |
### 故障模式分类
| 故障类型 | 次数 | 占比 |
|----------|------|------|
| 编译错误 | {{compile_count}} | {{compile_pct}}% |
| 测试失败 | {{test_fail_count}} | {{test_fail_pct}}% |
| 超时 | {{timeout_count}} | {{timeout_pct}}% |
| 环境问题 | {{env_count}} | {{env_pct}}% |
| 其他 | {{other_count}} | {{other_pct}}% |
## 四、改进建议
<!-- 根据分析结果,从以下列表中选择匹配的建议输出 -->
- **立即激活 CI**(当 CI 未激活时):前往 GitLink Web 界面 → 仓库设置 → DevOps 开启 CI/CD 服务CLI 暂不支持 `ci +activate`
- **提升成功率**(当 success_rate < 80% 时):优先修复高频失败原因
- **增加构建频率**(当构建频率评分 < 2 时):建议每次 push 触发 CI
- **缩短修复时间**(当修复速度评分 < 2 时):建立 CI 失败告警
```
---
## 异常场景处理
| 场景 | 处理方式 |
|------|----------|
| CI 未激活 | 报告 CI 状态为"未激活",建议通过 Web 界面开启,不再继续后续步骤 |
| 无构建记录 | 标注"仓库暂无 CI 构建记录" |
| `ci +logs` 返回空 | 标注"日志不可用" |
| 构建总数 < 5 | 样本量不足,标注"数据有限,统计不具代表性" |
---
## 注意事项
- ✅ **所有命令使用 `--format json`**,确保可解析
- ✅ **CI 激活/关闭需通过 GitLink Web 界面**CLI 不提供 `+activate`/`+deactivate` 命令
- ✅ **Owner/repo 优先从 `git remote` 自动解析**
- ⚠️ **`ci +logs` 输出可能很大**,仅提取关键错误行
- ⚠️ **构建历史无分页参数**,实际返回条数取决于 API
- ⚠️ **CI 数据仅反映 GitLink 平台活动**,不包括第三方 CI 服务
- ⚠️ **`repo +info` 的 `open_devops` 字段**是判断 CI 是否激活的最可靠方式

View File

@ -0,0 +1,247 @@
# gitlink-contributor-insight贡献者活跃度分析
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 本 Skill 为只读操作,不会修改任何仓库。无需用户额外确认即可执行。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`GitHub CLI操作 GitLink 资源。**
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
---
## ⚠️ 命令可用性声明
gitlink-cli 的命令集在持续演进中。以下命令**当前版本可能不可用**,执行前先验证:
| 命令 | 状态 | 替代方案 |
|------|------|----------|
| `repo +contributors` | ❌ 不可用 | 从 `pr +list` 提取 `author_login` + `repo +info` 获取 `contributor_users_count` |
| `user +heatmap` | ❌ 不可用 | 从 PR 时间戳手动推算活跃天数 |
| `user +stats` | ❌ 不可用 | 从 `pr +list` 统计 PR 数Issue 数通过 `issue +list` 获取 |
| `user +trends` | ❌ 不可用 | 从 PR 时间分布手动判断趋势(上升/平稳/下降) |
| `repo +info` | ✅ 可用 | — |
| `pr +list` | ✅ 可用 | — |
| `user +info` | ✅ 可用 | — |
| `issue +list` | ✅ 可用 | — |
> **核心原则**:优先使用可用的 Shortcut 命令。当所需命令不可用时,从 `pr +list` 和 `user +info` 中提取等效数据,并在报告中标注"数据来源PR 列表(命令 X 不可用)"。
---
## 功能概述
面向开源社区管理者和维护者的贡献者分析工具:
1. **项目概览** — 获取仓库贡献者规模和基本信息
2. **贡献数据分析** — 通过 `pr +list` 提取每位贡献者的 PR 数量和时间分布
3. **用户画像** — 通过 `user +info` 了解贡献者背景
4. **趋势判断** — 从 PR 时间序列推断贡献趋势
5. **洞察报告** — 生成贡献者活跃度排名和团队健康度评估
---
## 工作流:贡献者分析全流程
### Step 1获取仓库基本信息
```bash
gitlink-cli repo +info --owner <owner> --repo <repo> --format json
```
提取:`contributor_users_count`、`full_name`、`description`、`default_branch`、`fork_info`(如为 Fork 项目)。
### Step 2获取贡献者列表通过 PR 数据)
由于 `repo +contributors` 不可用,改用两步获取贡献者:
```bash
# 2a. 获取所有 PR含已合并和已关闭
gitlink-cli pr +list --owner <owner> --repo <repo> --format json
# 2b. 如果 Issue 数据也需要
gitlink-cli issue +list --owner <owner> --repo <repo> --format json
```
从 `pr +list` 返回数据中:
- 提取所有唯一的 `author_login` 作为实际代码贡献者
- 统计每位作者的 PR 数(`pull_request_status`: 0=open, 1=merged, 2=closed
- 记录每个 PR 的 `pr_full_time` 用于时间分析
- 记录每个 PR 的 `journals_count`(评论/审核活动数)
从 `issue +list` 返回数据中:
- 提取所有唯一的 `author_login` 作为 Issue 参与者
- 统计每位作者的 Issue 数
> 如果 PR 数量较多(>50按 `author_login` 聚合后取 PR 数前 10 的贡献者分析,报告中注明"基于 Top 10 分析"。
### Step 3逐位贡献者深度分析
对每位贡献者执行:
```bash
# 用户基本信息
gitlink-cli user +info --login <username> --format json
```
从 `user +info` 提取:`login`、`name`、`created_time`(注册时间)、`user_projects_count`、`user_org_count`、`user_identity`。
**如果 `user +heatmap/+stats/+trends` 可用**(未来版本),补充执行。当前版本用以下替代方案:
| 维度 | 替代数据源 | 分析要点 |
|------|----------|----------|
| 贡献频率 | PR 时间戳列表 | 统计活跃天数、相邻 PR 间隔、判断"持续贡献者"还是"间歇参与者" |
| 贡献产出 | `pr +list` 聚合 | PR 数、Issue 数,区分"代码贡献者"和"问题反馈者" |
| 活跃趋势 | PR 按日/周聚合 | 贡献量上升/稳定/下降,识别"上升期贡献者"和"逐渐淡出者" |
> ⚠️ **控制 API 调用**:贡献者 >15 人时,仅分析 PR 数最高的前 10 位。每人 1 次 `user +info` 调用(共 ≤10 次PR 数据已在 Step 2 全量获取。
### Step 4贡献者分级与分类
#### 4.1 活跃度分级
| 级别 | 判定标准 |
|------|----------|
| 🔥 **核心贡献者** | 最近 30 天有贡献 + 总贡献 PR ≥ 5或总贡献 PR > 10 |
| 🌟 **活跃贡献者** | 最近 60 天有贡献 + 总贡献 ≥ 3 |
| 🌱 **新兴贡献者** | 最近 90 天首次出现 + 贡献频率上升 |
| 💤 **休眠贡献者** | 最近 90 天无贡献 + 历史有贡献 |
> **年轻项目特殊处理**:项目历史 < 30 天时,放宽标准——所有活跃贡献者均可标记为核心贡献者,报告中注明"项目处于早期阶段,分级标准已放宽"。
#### 4.2 贡献类型分类
| 类型 | 判定 |
|------|------|
| **代码贡献者** | PR 数量 > Issue 数量 |
| **问题反馈者** | Issue 数量 > PR 数量 |
| **全能贡献者** | PR 和 Issue 数量均衡(差异 ≤ 1 |
### Step 5生成贡献者洞察报告
按下方输出模板生成报告,并根据数据可用性灵活调整章节。
---
## 输出模板
```markdown
# 👥 贡献者洞察报告:{{仓库名}}
> 分析时间:{{当前时间}}
> 仓库:{{full_name}}
> 总贡献者:{{contributor_users_count}} 人,本次分析:{{analyzed_count}} 人
---
## 一、团队概览
| 指标 | 数值 |
|------|------|
| 总贡献者 | {{contributor_users_count}} |
| 核心贡献者 | {{core_count}} |
| 活跃贡献者 | {{active_count}} |
| 新兴贡献者 | {{new_count}} |
| 休眠贡献者 | {{dormant_count}} |
| 近 30 天活跃率 | {{active_30d_rate}}% |
---
## 二、贡献者活跃度排行榜
| 排名 | 贡献者 | 级别 | 类型 | 活跃天数 | 总PR | 总Issue | 趋势 |
|------|--------|------|------|---------|------|---------|------|
| 1 | {{login}} | 🔥 | 代码 | {{d}} 天 | {{pr_count}} | {{issue_count}} | ↑ |
| ... | ... | ... | ... | ... | ... | ... | ... |
---
## 三、重点贡献者分析
> 仅展示核心/活跃贡献者。
### 🔥 {{login}}(核心贡献者)
| 维度 | 数据 | 说明 |
|------|------|------|
| 活跃天数 | {{d}} 天 | {{评价}} |
| 总 PR 数 | {{pr_count}} | |
| 总 Issue 数 | {{issue_count}} | |
| 贡献趋势 | {{trend_direction}} | {{trend_comment}} |
**PR 贡献明细**:(可选,数据充足时展示)
| PR# | 标题 | 日期 | 类型 |
|-----|------|------|------|
| ... | ... | ... | feat/fix/refactor |
---
## 四、团队健康度评估
### 健康度指标
| 指标 | 状态 | 说明 |
|------|------|------|
| 核心贡献者占比 | {{core_ratio}}% | {{core_comment}} |
| 新老比例 | {{new_old_ratio}} | {{new_old_comment}} |
| 贡献频率稳定性 | {{stability}} | {{stability_comment}} |
| 知识分散度 | {{bus_factor}} | {{bus_factor_comment}} |
### 风险提示
- ⚠️ **核心贡献者不足**(当 core_count < 3 时):仅 {{core_count}} 位核心贡献者存在单点依赖风险Bus Factor = {{core_count}})。
- ⚠️ **贡献者流失**(当 dormant_rate > 50% 时):超过一半的贡献者已不活跃,需要关注社区留存。
- ⚠️ **缺少新鲜血液**(当 new_count == 0 时):近期无新兴贡献者,建议通过 Good First Issue 等方式吸引新人。
- **项目处于早期阶段**(当项目历史 < 30 天时):贡献者分级标准已放宽,以上风险置信度有限。
- ✅ **团队健康**(当以上情况均不满足时):贡献者结构合理,团队运转良好。
---
## 五、社区建设建议
1. **激励核心贡献者**{{核心贡献者维护建议}}
2. **激活休眠贡献者**{{休眠贡献者召回建议}}
3. **吸引新贡献者**{{新贡献者吸引建议}}
4. **平衡贡献类型**{{贡献类型平衡建议}}
---
## 📋 数据来源与局限性
| 数据维度 | 来源 | 可靠性 |
|----------|------|--------|
| 贡献者数量 | `repo +info` | ✅ 可靠 |
| PR 贡献数据 | `pr +list` 全量 | ✅ 可靠 |
| Issue 数据 | `issue +list` | ✅ 可靠 |
| 用户信息 | `user +info` | ✅ 可靠 |
| 贡献热力图 | 不可用(命令未实现) | ❌ 缺失 |
| 统计信息 | 不可用(命令未实现) | ❌ 缺失 |
| 趋势数据 | 不可用(命令未实现) | ❌ 缺失 |
> **局限性**:本报告仅反映 GitLink 平台活动不包括其他平台GitHub、GitLab 等)的数据。
```
---
## 异常场景处理
| 场景 | 处理方式 |
|------|----------|
| `repo +contributors` 不可用(当前版本常态) | 从 `pr +list` 的 `author_login` 提取贡献者列表 |
| `user +heatmap` / `+stats` / `+trends` 不可用 | 从 PR 时间戳推算活跃天数PR 聚合得产出量,时间分布得趋势 |
| `pr +list` 返回空 | 标注"仓库暂无 PR 数据",仅展示 `repo +info` 基本信息 |
| `user +info` 返回空 | 标注"用户信息不可用",仅展示 PR 统计 |
| 贡献者 > 15 人 | 仅分析 PR 数最高的前 10 位,报告中注明"基于 Top 10 分析" |
| 项目历史 < 30 天 | 放宽分级标准,报告中注明"项目处于早期阶段" |
| `issue +list` 返回空 | Issue 数列为 0贡献类型统一标注"代码贡献者" |
---
## 注意事项
- ✅ **所有命令使用 `--format json`**,确保可解析
- ✅ **本 Skill 为纯只读分析**,不会修改任何仓库
- ✅ **Owner/repo 优先从 `git remote` 自动解析**,无 git 上下文时询问用户
- ⚠️ **核心数据来源为 `pr +list`**:当前版本 gitlink-cli 中 `user +heatmap/+stats/+trends` 不可用,分析主要依赖 PR 列表数据
- ⚠️ **`repo +contributors` 不可用**:贡献者列表从 PR 作者提取,可能与实际 `contributor_users_count` 有差异(后者包含未提 PR 的参与者)
- ⚠️ **数据仅反映 GitLink 平台活动**:不包括 GitHub 或其他平台的数据
- **参照样例**[`EXAMPLES.md`](EXAMPLES.md) 包含手动执行和 Agent 调用两种场景的完整样例,[`examples/jiangtx-gitlink-cli.md`](examples/jiangtx-gitlink-cli.md) 包含原始命令输出数据

View File

@ -0,0 +1,216 @@
# gitlink-issue-triageIssue 智能分拣)
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 本 Skill 为只读操作,不会修改任何 Issue。无需用户额外确认即可执行。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`GitHub CLI操作 GitLink 资源。**
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
---
## 功能概述
对仓库的开放 Issue 进行全量扫描和智能分类,输出结构化的分拣报告:
1. **类型分类** — 判断每个 Issue 是 Bug、功能请求、文档问题还是使用咨询
2. **紧急度评估** — 根据关键词和优先级字段标注紧急程度
3. **复杂度预估** — 根据描述详尽程度评估修复难度
4. **行动建议** — 给出具体处理建议(立即修复/需讨论/可关闭/适合作入门任务)
---
## 工作流Issue 全量分拣
### Step 1获取项目概览
```bash
gitlink-cli repo +info --owner <owner> --repo <repo> --format json
```
提取 `issues_count` 了解 Issue 池总量,`default_branch` 确认主分支。
### Step 2获取全部开放 Issue
```bash
gitlink-cli issue +list --owner <owner> --repo <repo> --state open --format json
```
> ⚠️ **已知问题**`--state open` 过滤不准确,返回列表可能包含已关闭的 Issue。需在客户端按 `status_id` 二次过滤:保留 `status_id` = 1新增或 2正在解决排除 3已解决、5关闭。`status_id` = 0 纳入分析但标注"状态异常"。
如果返回数量 >20追加分页参数获取全部
```bash
gitlink-cli issue +list --owner <owner> --repo <repo> --state open --format json --page 2
```
### Step 3逐条深入分析
对过滤后的每条 Issue获取详情
```bash
gitlink-cli issue +view --owner <owner> --repo <repo> --number <project_issues_index> --format json
```
分析以下维度:
| 维度 | 关注字段 | 分析要点 |
|------|----------|----------|
| 类型 | `subject`, `description` | 标题和描述中的关键词 |
| 紧急度 | `priority`, `subject` | 优先级字段 + 标题紧急信号 |
| 复杂度 | `description` 长度 | 描述的详细程度、是否有复现步骤 |
| 活跃度 | `comment_journals_count`, `updated_at` | 讨论热度和最后活跃时间 |
| 分配状态 | `assigners` | 是否已有人负责 |
### Step 4分类规则
#### 4.1 类型分类type
| 类型 | 匹配规则 |
|------|----------|
| **bug** | 标题/描述含 `bug`、`错误`、`失败`、`崩溃`、`异常`、`修复`、`fix`、`修复`、`报错`、`不工作`、`问题`(上下文为故障时) |
| **feature** | 标题/描述含 `feature`、`新增`、`添加`、`希望`、`建议`、`需要`、`支持`、`实现`,且非故障描述 |
| **docs** | 标题/描述含 `文档`、`doc`、`README`、`说明`、`教程`、`注释` |
| **question** | 标题/描述含 `如何`、`怎么`、`是否`、`能不能`、`请问`、`为什么`,且以问号结尾或明显为咨询语气 |
| **refactor** | 标题/描述含 `重构`、`refactor`、`优化结构`、`代码清理`、`技术债` |
| **ci** | 标题/描述含 `CI`、`CD`、`构建`、`部署`、`pipeline`、`自动化`、`测试环境` |
| **meta** | 维护者创建的元讨论帖、反馈收集帖、公告,无具体技术任务指向 |
| **other** | 不匹配以上任何类型时的兜底分类 |
#### 4.2 紧急度评估urgency
| 级别 | 判定条件 |
|------|----------|
| **urgent** | 标题含 `紧急`、`urgent`、`hotfix`、`生产`、`线上`、`崩溃`;或 `priority.name` = "紧急" |
| **high** | `priority.name` = "高";或标题含 `严重`、`阻塞`、`关键` |
| **normal** | 默认级别;`priority.name` = "正常" 或无优先级 |
| **low** | `priority.name` = "低";或标题含 `优化`、`nice to have`、`小建议` |
#### 4.3 复杂度预估complexity
| 级别 | 判定条件 |
|------|----------|
| **easy** | 描述简洁明确,有清晰复现步骤或单一功能点;`description` < 300 字且范围明确 |
| **medium** | 涉及多个文件/模块,需要一定背景了解;`description` 300~800 字,或虽有描述但需推断 |
| **hard** | 涉及架构变更、新子系统、跨模块重构;`description` > 800 字或非常模糊 |
> **特殊情况**`description` 仅含图片附件链接而无可读文字 → 视为"描述缺失",复杂度标记为 hard因无法评估建议标记为 discuss。
#### 4.4 行动建议action
| 建议 | 判定条件 |
|------|----------|
| **fix-now** | bug + urgent/high |
| **investigate** | bug + normal/low需先确认复现 |
| **implement** | feature + 描述清晰 + 范围明确 |
| **discuss** | 描述模糊、需求不清、或 question 类型 |
| **close-candidate** | 超过 90 天无更新、无评论、无分配 |
| **good-first-issue** | complexity=easy + 无人分配 + 范围明确 |
### Step 5生成分拣报告
将所有分析结果组织输出。
---
## 输出模板
```markdown
# 📊 {{仓库名}} Issue 分拣报告
> 分析时间:{{当前时间}}
> Issue 总数:{{total}},开放:{{open_count}},本次分析:{{analyzed_count}} 条
---
## 总览
| 指标 | 数量 |
|------|------|
| Bug | {{bug_count}} |
| 功能请求 | {{feature_count}} |
| 文档 | {{docs_count}} |
| 咨询 | {{question_count}} |
| 元讨论 | {{meta_count}} |
| 其他 | {{other_count}} |
| **需立即处理** | {{urgent_count}} |
| **适合入门** | {{good_first_issue_count}} |
---
## 🔴 需立即处理
> 如本段为空,输出:*当前无紧急 Issue状态健康。*
| # | 标题 | 类型 | 紧急度 | 复杂度 | 建议 | 备注 |
|---|------|------|--------|--------|------|------|
| {{number}} | {{subject}} | bug | urgent | medium | fix-now | |
| ... | ... | ... | ... | ... | ... | ... |
## 🟡 建议近期处理
> 如本段为空,输出:*当前无高优先级 Issue。*
| # | 标题 | 类型 | 紧急度 | 复杂度 | 建议 | 备注 |
|---|------|------|--------|--------|------|------|
| ... | ... | bug/feature | high/normal | easy/medium | investigate/implement | |
## 🟢 可延迟 / 需讨论
> 如本段为空,输出:*所有 Issue 均已明确,无需额外讨论。*
| # | 标题 | 类型 | 紧急度 | 复杂度 | 建议 | 备注 |
|---|------|------|--------|--------|------|------|
| ... | ... | question/feature | normal/low | medium/hard | discuss | |
## ⭐ 适合入门Good First Issue
> 如本段为空,输出:*暂无完全符合条件的入门 Issue。建议在后续工作中拆分出简单子任务。*
| # | 标题 | 类型 | 复杂度 | 推荐理由 |
|---|------|------|--------|----------|
| {{number}} | {{subject}} | bug/docs | easy | 范围明确,单文件修改 |
| ... | ... | ... | ... | ... |
## ⚠️ 候选关闭90+ 天无活动)
> 如本段为空,输出:*无长期不活跃的 Issue。*
| # | 标题 | 最后更新 | 建议 |
|---|------|----------|------|
| {{number}} | {{subject}} | {{updated_at}} | 评论询问是否仍需要,如无回应可关闭 |
---
## 📋 维护建议
1. **立即行动**{{urgent_count}} 个紧急 Issue 需要优先处理
2. **本周目标**:建议处理 {{suggested_this_week}} 个 Issuesuggested_this_week = 建议近期处理段中的 Issue 数量,即 bug+normal/high + feature+清晰描述 的总数)
3. **社区引导**{{good_first_issue_count}} 个 Issue 适合标记为 good first issue吸引新贡献者
4. **清理计划**{{close_candidate_count}} 个 Issue 长期无活动,建议批量确认后关闭
5. {{#if no_tags}}本仓库未使用 Issue 标签系统建议建立标签体系bug/feature/docs/question/meta/help-wanted/good-first-issue以提升管理效率{{/if}}
6. {{#if status_anomalies}}本批次有 {{status_anomaly_count}} 个 Issue 状态异常status_id=0建议在平台上手动确认{{/if}}
```
---
## 异常场景处理
| 场景 | 处理方式 |
|------|----------|
| 无开放 Issue | 输出 `repo +info` 概览后,恭喜维护者"Issue 池已清空" |
| Issue 数量 >50 | 优先分析最近 30 天更新的 Issue其余标记为"待分批处理" |
| 全部 Issue 无标签/无优先级 | 分类完全依赖标题和描述关键词分析,并在报告末尾建议建立标签体系 |
| `description` 为空或仅含图片/附件链接 | 标注"描述缺失",类型仅根据标题判断,复杂度标为 hard建议标记为 discuss |
| `status_id` = 0未知 | 纳入分析但标注"状态异常" |
---
## 注意事项
- ✅ **所有命令使用 `--format json`**,确保可解析
- ✅ **`issue +view` 使用 `--number`(网页编号)**,非数据库 ID
- ✅ **本 Skill 为纯只读分析**,不会修改任何 Issue
- ✅ **Owner/repo 优先从 `git remote` 自动解析**,无 git 上下文时询问用户
- ⚠️ **`issue +list --state open` 过滤不准确**,必须客户端按 `status_id` 二次过滤
- ⚠️ **分类规则是启发式的**AI 应根据实际内容做判断,不要机械匹配关键词
- ⚠️ **Issue 数量多时分批处理**,超过 50 条建议先按更新时间排序,优先分析最近活跃的

View File

@ -0,0 +1,246 @@
# gitlink-research-tracker科研热点追踪
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
**CRITICAL — 本 Skill 为只读操作,不会修改任何仓库。无需用户额外确认即可执行。**
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`GitHub CLI操作 GitLink 资源。**
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
---
## 功能概述
面向科研场景的技术调研工具,帮助研究者快速了解 GitLink 平台上的技术格局:
1. **多关键词搜索** — 将研究主题拆解为多个关键词,全面覆盖相关项目
2. **项目深度评估** — 从活跃度、社区规模、代码产出等维度评估项目健康度
3. **横向对比** — 对比同类项目的核心指标,识别领先者和潜力项目
4. **趋势洞察** — 基于更新时间、贡献者增长、版本发布频率等推断技术趋势
5. **调研报告** — 生成结构化的技术调研简报
---
## 工作流:技术调研全流程
### Step 1理解研究主题拆解搜索关键词
根据用户的研究主题,拆解 3~5 个搜索关键词:
| 研究主题示例 | 拆解关键词 |
|-------------|-----------|
| "AI Agent 工具" | `agent`, `AI`, `LLM`, `智能体`, `copilot` |
| "DevOps 流水线" | `devops`, `pipeline`, `CI/CD`, `自动化部署`, `容器` |
| "开源合规" | `license`, `compliance`, `合规`, `sbom`, `供应链安全` |
| "微服务框架" | `microservice`, `微服务`, `rpc`, `服务网格`, `cloud native` |
> **原则**:关键词应覆盖中英文、缩写全称、技术术语和行业叫法。每个关键词独立搜索。
### Step 2多关键词搜索
对每个关键词执行搜索:
```bash
gitlink-cli search +repos -k <关键词> --format json
```
对每个搜索结果,提取:
| SKILL 中用到的概念 | 实际字段来源 | 说明 |
|-------------------|-------------|------|
| owner/repo 标识 | `author.login` + `/` + `identifier` | 搜索结果**没有** `full_name`,需手动拼接。`identifier` 是仓库的唯一标识符 |
| 项目描述 | `description` | 直接可用 |
| 关注度 | `praises_count` | 搜索结果中叫 `praises_count`**不是** `stars`。`watchers_count` 仅在 `repo +info` 中返回 |
| Fork 数 | `forked_count` | 搜索结果中叫 `forked_count`**不是** `forks_count` |
| 编程语言 | `language.name` | `language` 是嵌套对象 `{id, name}`,需取 `.name`。可能为 `null` |
| 更新时间 | `last_update_time`Unix 时间戳)或 `full_last_update_time`ISO 8601 字符串) | 搜索结果中**没有** `updated_at` |
| 是否镜像 | `mirror` | 仅在 `repo +info` 返回。GitLink 上大量仓库是 GitHub 镜像,需特别标注 |
**去重规则**:用 `author.login/identifier` 作为唯一标识。同一仓库出现在多个关键词结果中时,只保留一次,标注匹配了哪些关键词。
**数量控制**:每个关键词保留前 8 个结果。合并去重后总数控制在 20 个以内。超出时优先保留匹配多关键词的和 `last_update_time` 最近的。
### Step 3重点项目深度评估
对合并去重后的每个项目,获取详细指标:
```bash
gitlink-cli repo +info --owner <owner> --repo <repo> --format json
```
从返回数据中提取核心评估维度:
| 维度 | 字段 | 评估标准 |
|------|------|----------|
| **社区活跃度** | `contributor_users_count` | >20 大社区5~20 中等,<5 小团队 |
| **关注度** | `watchers_count`, `praises_count` | 反映项目知名度 |
| **研发节奏** | `version_releases_count` | >20 快速迭代5~20 正常,<5 慢速 |
| **代码规模** | `size` | 粗略判断项目复杂度 |
| **开放性** | `forked_count` | fork 数反映二次开发热度 |
| **PR 活跃度** | `pull_requests_count` | 反映代码贡献频率 |
可选补充(如有需要):
```bash
# 查看最近 Issue 活跃度
gitlink-cli issue +list --owner <owner> --repo <repo> --state open --format json
# 查看最近 Release 情况
gitlink-cli release +list --owner <owner> --repo <repo> --format json
```
> ⚠️ **控制分析数量**:深度评估仅对最有价值的 5~8 个项目执行优先匹配多关键词、watchers 多、updated_at 最近的项目),避免过多 API 调用。
### Step 4横向对比与趋势分析
#### 4.1 项目分类与镜像识别
在评分之前,先通过 `repo +info` 的 `mirror` 字段区分项目类型:
| 类型 | 判定 | 处理 |
|------|------|------|
| **镜像仓库** | `mirror: true` | 标注 `[镜像]`。GitLink 上的 `contributor_users_count`/`watchers_count` 等指标均为 0不代表真实社区活跃度。评分仅作参考 |
| **原创仓库** | `mirror: false` 且 `forked_from_project_id: null` | 正常评分 |
| **Fork 仓库** | `forked_from_project_id` 非 null | 标注 `[Fork]`,评分反映的是 Fork 后的独立开发情况 |
#### 4.2 项目成熟度评分
对每个深度评估的项目,按以下标准打分(满分 25
| 维度 | 权重 | 评分标准 |
|------|------|----------|
| 社区规模 | 5 | contributor_users_count: >20=5, >10=4, >5=3, >2=2, ≤2=1 |
| 关注度 | 5 | repo +info 的 watchers_count: >30=5, >15=4, >8=3, >3=2, ≤3=1 |
| 研发节奏 | 5 | version_releases_count: >10=5, >5=4, >1=3, 0=2。**镜像仓库此项固定给 1**(镜像通常不通过 GitLink 发版)。注意 GitLink 平台 Release 功能使用率低,即使原创仓库 release=0 也建议给 2 而非 1 |
| 开发活跃 | 5 | 最近 30 天有更新=5, 60 天=4, 90 天=3, 180 天=2, >180 天=1。基于 `repo +info` 的更新时间或搜索结果中的 `last_update_time` |
| 开放性 | 5 | forked_count: >30=5, >15=4, >8=3, >3=2, ≤3=1 |
> **镜像修正**:镜像仓库的社区规模、关注度、开放性三项在 GitLink 上均为 0应标注"数据为 GitLink 平台内数据不代表项目在原始平台GitHub的真实影响力",不参与排名比较。
#### 4.3 技术趋势推断
- **增长信号**:近期频繁 Release + contributor 增长 + fork 增长 → 技术热点上升期
- **成熟信号**:大量 watcher + 稳定 Release 节奏 + 大社区 → 技术趋于成熟
- **衰退信号**:超过 180 天无更新 + 少量 contributor + 无新 Release → 可能已不活跃
- **新兴信号**:小社区 + 快速迭代 + 最新更新时间近 → 可能是新兴项目
### Step 5生成技术调研报告
---
## 输出模板
```markdown
# 🔬 技术调研报告:{{研究主题}}
> 调研时间:{{当前时间}}
> 搜索关键词:{{keyword_list}}
> 搜索命中:{{total_hits}} 个仓库,去重后 {{unique_count}} 个,深度分析 {{deep_analysis_count}} 个
---
## 一、技术格局概览
| 指标 | 数值 |
|------|------|
| 相关项目总数 | {{unique_count}} |
| 主要编程语言 | {{top_languages}} |
| 平均社区规模 | {{avg_contributors}} 人 |
| 近 30 天活跃项目 | {{active_30d_count}}{{active_30d_pct}}% |
| 高成熟度项目≥20分 | {{high_maturity_count}} |
---
## 二、项目成熟度排行榜
| 排名 | 项目 | 类型 | 评分 | 语言 | Watch | 贡献者 | Release | Fork | 关键词匹配 |
|------|------|------|------|------|-------|--------|---------|------|------------|
| 1 | {{full_name}} {{#if mirror}}[镜像]{{/if}} | {{原创/镜像/Fork}} | {{score}}/25 | {{language}} | {{watchers}} | {{contributors}} | {{releases}} | {{forks}} | {{matched_keywords}} |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
---
## 三、重点项
> 仅展示评分 ≥15 或匹配 3+ 关键词的高潜力项目。
### 🥇 {{项目名}}{{score}}/25
| 维度 | 评分 | 说明 |
|------|------|------|
| 社区规模 | {{community_score}}/5 | {{contributor_users_count}} 位贡献者 |
| 关注度 | {{popularity_score}}/5 | {{watchers_count}} watch, {{praises_count}} star |
| 研发节奏 | {{release_score}}/5 | {{version_releases_count}} 个版本发布 |
| 开发活跃 | {{activity_score}}/5 | 最后更新于 {{last_update}} |
| 开放性 | {{openness_score}}/5 | {{forked_count}} 次 fork |
**亮点**{{一句话总结项目最大优势}}
**关注点**{{一句话指出需要关注的风险或不足}}
---
### 🥈 {{项目名}}{{score}}/25
(同上格式)
---
## 四、技术趋势洞察
1. **热点方向**{{当前最热的技术方向,基于项目分布推断}}
2. **新兴项目**{{列出 1~3 个"新兴信号"明显的项目}}
3. **成熟生态**{{列出 1~2 个"成熟信号"明显的项目,适合作为技术选型参考}}
4. **风险提示**{{列出 1~2 个"衰退信号"项目或值得关注的生态空白}}
---
## 五、调研建议
### 技术选型推荐
| 场景 | 推荐项目 | 理由 |
|------|----------|------|
| 生产环境使用 | {{最成熟的项目}} | 社区大、更新稳定、文档完善 |
| 学习入门 | {{最简单的项目}} | 代码量小、贡献门槛低 |
| 前沿探索 | {{最新兴的项目}} | 技术新颖、迭代快速 |
### 研究选题建议
- {{基于当前生态,建议 2~3 个可深入研究的选题}}
- {{指出 1~2 个生态空白,可能是创新机会}}
---
## 六、数据来源
所有数据通过 `gitlink-cli` 从 GitLink 平台实时获取,每个项目均已通过 `repo +info` 验证。
```
---
## 异常场景处理
| 场景 | 处理方式 |
|------|----------|
| 关键词无搜索结果 | 尝试近义词或更宽泛的关键词重试,仍无结果则标注"该方向暂无相关项目" |
| 搜索返回大量结果(>50 | `search +repos` 无分页参数,实际返回约 20 条/关键词。合并后按 `praises_count` 降序取前 20 |
| 某项目 `repo +info` 返回 404 | 该项目可能为私有或已删除,从列表中移除 |
| `repo +info` 网络超时/TLS 错误 | 等待 5 秒后重试一次。仍失败则标注"网络请求失败",跳过该项目继续分析其余 |
| 大量搜索结果来自镜像仓库 | 优先分析 `mirror: false` 的原创项目。镜像项目保留但标注,评分仅作参考 |
| 所有项目评分均 <15 | 说明该领域尚未形成成熟生态,调整报告语气为"早期探索阶段" |
| 用户未提供具体关键词 | 引导用户明确研究主题,提供几个示例关键词供选择 |
| `language` 字段为 `null` | 标注为"未知" |
---
## 注意事项
- ✅ **所有命令使用 `--format json`**,确保可解析
- ✅ **本 Skill 为纯只读分析**,不会修改任何仓库
- ✅ **搜索关键词建议中英文各覆盖**,提高命中率
- ✅ **深度评估控制在 5~8 个项目**,避免调用过多 API
- ⚠️ **`search +repos` 和 `repo +info` 字段名不同**:搜索结果用 `praises_count`/`forked_count`/`author.login+identifier``repo +info` 才有 `watchers_count`/`full_name`/`mirror`。详见 Step 2 字段映射表
- ⚠️ **`repo +info` 并发请求可能触发 TLS 超时**,失败时等 5 秒重试一次,不要放弃
- ⚠️ **GitLink 平台镜像仓库比例高**,镜像仓库的社区数据为 0不代表项目真实影响力。在报告中标注 `[镜像]` 并单独说明
- ⚠️ **GitLink Release 功能使用率低**,大部分项目 `version_releases_count`=0。评分时 Release 维度降低权重预期0 个 Release 给 2 分(而非 1 分)
- ⚠️ **搜索结果无分页参数**,每次返回约 20 条。关键词超过 5 个时需手动截断合并结果
- ⚠️ **本 Skill 场景适配 GitLink 平台**GitLink 以国内开发者和企业项目为主,搜索结果可能偏向中文技术生态,且镜像项目较多

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitLink Skills</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body class="bg-gray-50 min-h-screen">
<nav class="bg-white shadow-sm border-b">
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
<a href="/" class="text-xl font-bold text-blue-600">🔧 GitLink Skills</a>
<div class="text-sm text-gray-500">科技赋能 · 开源创新</div>
</div>
</nav>
<main class="max-w-6xl mx-auto px-4 py-8">
{% block content %}{% endblock %}
</main>
<footer class="text-center text-gray-400 text-sm py-8">
GitLink Skills Web Service &mdash; 基于 Flask + DeepSeek API + gitlink-cli
</footer>
</body>
</html>

View File

@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block content %}
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-4">GitLink Skills 智能分析服务</h1>
<p class="text-lg text-gray-500 max-w-2xl mx-auto">
基于 DeepSeek API 和 gitlink-cli通过 AI Agent 自动搜索、分析 GitLink 平台数据
</p>
</div>
<!-- 工具区 -->
<h2 class="text-2xl font-bold text-gray-800 mb-4">🔧 工具</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-12">
{% for key, skill in skills.items() %}
{% if not key.startswith("lab-") %}
<a href="/skills/{{ url_map.get(key, key) }}" class="block p-5 bg-white rounded-xl shadow-sm hover:shadow-md transition border hover:border-blue-200">
<h3 class="text-lg font-semibold text-gray-800 mb-1">{{ skill.title }}</h3>
<div class="mt-2 text-blue-600 text-sm font-medium">开始使用 →</div>
</a>
{% endif %}
{% endfor %}
</div>
<!-- 科研实验室: 流程图布局 -->
<h2 class="text-2xl font-bold text-green-700 mb-4">🔬 科研实验室</h2>
<p class="text-gray-500 text-sm mb-6">多技能串联合成的科研辅助工作流,覆盖完整科研项目生命周期</p>
<div class="flex flex-col md:flex-row items-center justify-center gap-0 max-w-5xl mx-auto">
{% set flow = [
("lab-hotspot", "🔍 热点追踪", "搜项目 → 深挖 → 提取Fork关系 → 生成知识图谱"),
("lab-insight", "📊 项目洞悉", "仓库信息+贡献者+PR/Issue → 全息分析报告"),
("lab-compliance", "✅ 合规检查", "检查License/CI/Release/文档 → 合规评分与改进建议"),
("lab-match", "🤝 协作匹配", "分析Issue+社区健康度 → 评估新手友好度与入门方向"),
("lab-track", "📈 进度跟踪", "批量巡检多仓库 → 健康/警告/危险三级告警"),
] %}
{% for key, title, desc in flow %}
<div class="relative group w-36">
<a href="/skills/{{ key }}" class="block">
<div class="bg-green-50 border-2 border-green-300 rounded-xl p-4 text-center hover:bg-green-100 hover:border-green-500 hover:shadow-lg transition-all duration-200">
<div class="text-sm font-bold text-green-800">{{ title }}</div>
</div>
</a>
<div class="absolute z-10 invisible group-hover:visible opacity-0 group-hover:opacity-100 transition-opacity duration-200 bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-gray-800 text-white text-xs rounded-lg shadow-lg w-48 pointer-events-none">
{{ desc }}
<div class="absolute top-full left-1/2 -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-800"></div>
</div>
</div>
{% if not loop.last %}
<div class="flex items-center justify-center w-8 h-8">
<svg class="w-6 h-6 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,168 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-4xl mx-auto">
<a href="/skills/" class="text-blue-600 hover:underline text-sm mb-4 inline-block">&larr; 返回首页</a>
<h1 class="text-3xl font-bold text-gray-800 mb-2">{{ info.title }}</h1>
{% if skill_name.startswith("lab-") and info.intro %}
<div class="bg-green-50 border border-green-200 rounded-xl p-4 mb-6 text-sm text-green-800">
{{ info.intro }}
</div>
{% endif %}
<!-- 功能介绍 + 预期输出 -->
<div class="space-y-2 mb-6">
<details class="bg-white border border-gray-200 rounded-xl overflow-hidden">
<summary class="px-4 py-3 cursor-pointer font-medium text-gray-700 hover:bg-gray-50 transition flex items-center gap-2">
<span class="text-blue-500">📖</span> 功能介绍
</summary>
<div class="px-4 py-3 text-sm text-gray-600 border-t border-gray-100">
{% if info.intro %}{{ info.intro }}{% endif %}
</div>
</details>
<details class="bg-white border border-gray-200 rounded-xl overflow-hidden">
<summary class="px-4 py-3 cursor-pointer font-medium text-gray-700 hover:bg-gray-50 transition flex items-center gap-2">
<span class="text-blue-500">📋</span> 预期输出格式与内容
</summary>
<div class="px-4 py-3 text-sm text-gray-600 border-t border-gray-100">
{% if info.output_desc %}{{ info.output_desc|replace('\n', '<br>')|safe }}{% else %}
<p>生成中文分析报告,包含:总览、详细分析、排行榜、建议等。</p>
{% endif %}
</div>
</details>
</div>
<!-- 输入表单 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
<label class="block font-medium text-gray-700 mb-2">{{ info.prompt_label }}</label>
<input id="user-input" type="text"
class="w-full border rounded-lg px-4 py-3 text-gray-700 focus:ring-2 focus:ring-blue-300 focus:border-blue-400 outline-none"
placeholder="{{ info.prompt_placeholder }}">
<p id="hint-text" class="text-sm text-gray-400 mt-2"></p>
<button onclick="runSkill()" id="run-btn"
class="mt-4 bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-3 rounded-lg transition disabled:opacity-50">
🚀 开始分析
</button>
</div>
<!-- 进度 -->
<div id="progress" class="hidden mb-6">
<div class="bg-white rounded-xl shadow-sm p-6">
<div class="flex items-center gap-3 mb-4">
<div class="animate-spin w-5 h-5 border-2 border-blue-600 border-t-transparent rounded-full"></div>
<span class="text-gray-600 font-medium">AI 正在分析中...</span>
</div>
<div id="log" class="text-sm text-gray-500 space-y-1 font-mono max-h-48 overflow-y-auto"></div>
</div>
</div>
<!-- 报告输出 -->
<div id="report" class="hidden">
<div class="bg-white rounded-xl shadow-sm p-6 overflow-auto">
<div id="report-content" class="markdown-body"></div>
</div>
<div class="mt-4 flex gap-3">
<button onclick="downloadTxt()" class="text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg transition font-medium">
📥 下载 TXT
</button>
</div>
</div>
<!-- 错误 -->
<div id="error" class="hidden bg-red-50 border border-red-200 rounded-xl p-6 text-red-700"></div>
</div>
<script>
const skillName = "{{ skill_name }}";
const hints = {
"research-tracker": "例如微服务框架、AI Agent、DevOps 流水线、大模型、数据可视化",
"contributor-insight": "例如ci4s/ci4sManagement-cloud, jiangtx/gitlink-cli",
"issue-triage": "例如Gitlink/gitlink-cli, ci4s/ci4sManagement-cloud",
"ci-health": "例如jiangtx/gitlink-cli, Gitlink/gitlink-cli",
"lab-hotspot": "例如大模型、AI Agent、微服务、DevOps、数据可视化",
"lab-insight": "例如ci4s/ci4sManagement-cloud, Gitlink/gitlink-cli",
"lab-compliance": "例如ci4s/ci4sManagement-cloud, Gitlink/gitlink-cli",
"lab-match": "例如ci4s/ci4sManagement-cloud, Gitlink/gitlink-cli",
"lab-track": "例如ci4s/ci4sManagement-cloud, Gitlink/gitlink-cli",
};
document.getElementById('hint-text').textContent = '💡 ' + (hints[skillName] || '');
function runSkill() {
const input = document.getElementById('user-input').value.trim();
if (!input) { alert('请输入参数'); return; }
document.getElementById('progress').classList.remove('hidden');
document.getElementById('report').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
document.getElementById('run-btn').disabled = true;
document.getElementById('log').innerHTML = '<div>⏳ 提交中...</div>';
fetch('/api/run', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `skill=${encodeURIComponent(skillName)}&input=${encodeURIComponent(input)}`
})
.then(r => r.json())
.then(data => {
if (data.error) {
document.getElementById('error').classList.remove('hidden');
document.getElementById('error').textContent = '❌ ' + data.error;
return;
}
document.getElementById('log').innerHTML = '<div class="text-green-600">✅ 分析完成</div>';
// Render Markdown
const reportDiv = document.getElementById('report-content');
reportDiv.innerHTML = marked.parse(data.report || '');
document.getElementById('report').classList.remove('hidden');
// Render Mermaid diagrams (find code.language-mermaid)
document.querySelectorAll('code.language-mermaid').forEach((code) => {
const pre = code.parentElement;
const div = document.createElement('div');
div.className = 'mermaid';
div.textContent = code.textContent;
pre.parentElement.replaceChild(div, pre);
});
if (typeof mermaid !== 'undefined') {
mermaid.run({ nodes: document.querySelectorAll('.mermaid') });
}
})
.catch(err => {
document.getElementById('error').classList.remove('hidden');
document.getElementById('error').textContent = '❌ 请求失败: ' + err.message;
})
.finally(() => {
document.getElementById('progress').classList.add('hidden');
document.getElementById('run-btn').disabled = false;
});
}
function downloadTxt() {
const text = document.getElementById('report-content').textContent;
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'report-' + new Date().toISOString().slice(0,10) + '.txt';
a.click();
URL.revokeObjectURL(a.href);
}
</script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css">
<style>
#report-content.markdown-body { padding: 0; }
#report-content.markdown-body table { display: table; width: 100%; }
#report-content.markdown-body pre { background: #f6f8fa; border-radius: 6px; position: relative; }
#report-content.markdown-body code { font-size: 13px; }
#report-content.markdown-body img { max-width: 100%; }
/* Mermaid SVG should be visible */
#report-content.markdown-body .mermaid svg { max-width: 100%; height: auto; }
</style>
{% endblock %}