gitlink-cli/gitlink-web/app.py

866 lines
42 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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. 社区活跃度对比贡献者、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)