866 lines
42 KiB
Python
866 lines
42 KiB
Python
#!/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-analytics:PR 效率分析 — 分析合并时间、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. 社区活跃度对比(贡献者、watch、fork)
|
||
3. 开发活动对比(PR、Issue、Release)
|
||
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. 社区活跃度(贡献者、PR、Issue)
|
||
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)
|