diff --git a/output/knowledge-graph-2026-07-06.html b/output/knowledge-graph-2026-07-06.html new file mode 100644 index 00000000..80a8d740 --- /dev/null +++ b/output/knowledge-graph-2026-07-06.html @@ -0,0 +1,110 @@ + + + + + +科研知识图谱 — 2026-07-06 + + + + +
+

科研知识图谱

+
+ 关键词:LLM,Agent — + 仓库:4 个 — + 贡献者:0 人 — + 2026-07-06 +
+
+
+ +
+
4
仓库节点
+
0
贡献者节点
+
2
主题节点
+
3
关系边
+
N/A
最热仓库
+
+ +
+

知识图谱 — 力导向布局

+
+
+ +
+

热度排行榜

+ + + +
排名仓库热度语言Stars趋势
+
+ +
+ + + + + diff --git a/output/knowledge-graph-2026-07-06.json b/output/knowledge-graph-2026-07-06.json new file mode 100644 index 00000000..e286389c --- /dev/null +++ b/output/knowledge-graph-2026-07-06.json @@ -0,0 +1,51 @@ +{ + "metadata": { + "generated_at": "2026-07-06T11:51:35+08:00", + "search_keywords": [ + "LLM", + "Agent" + ], + "total_repos_scanned": 4, + "total_contributors_found": 0, + "total_edges_inferred": 3 + }, + "nodes": [ + { + "id": "topic:llm", + "type": "topic", + "label": "LLM\n", + "symbolSize": 30, + "category": 2 + }, + { + "id": "topic:agent", + "type": "topic", + "label": "Agent\n", + "symbolSize": 30, + "category": 2 + } + ], + "edges": [ + { + "source": "repo:agent", + "target": "repo:ribo-agent", + "type": "related_to", + "weight": 0.5, + "evidence": "共同主题: Agent\n" + }, + { + "source": "repo:doutrip", + "target": "repo:agent", + "type": "related_to", + "weight": 0.5, + "evidence": "共同主题: Agent\n" + }, + { + "source": "repo:ribo-agent", + "target": "repo:wow-agent", + "type": "related_to", + "weight": 0.5, + "evidence": "共同主题: Agent\n" + } + ] +} diff --git a/output/reproducibility-gitlink-cli-2026-07-06.html b/output/reproducibility-gitlink-cli-2026-07-06.html new file mode 100644 index 00000000..bbefc5d8 --- /dev/null +++ b/output/reproducibility-gitlink-cli-2026-07-06.html @@ -0,0 +1,170 @@ + + + + + +zzx-coder/gitlink-cli — 复现性评分卡 + + + + +
+

zzx-coder/gitlink-cli — 科研复现性评分卡

+
2026-07-06
+
+
+ +
+
+
F
+
5.0 / 100
+
+ + + + + 差 — 几乎不可复现 +
+
+
+

维度雷达图

+
+
+
+

维度明细

+ + + + + + + + + + +
维度评分权重
许可证0%15%
无密钥/PII0%15%
README 完整0%15%
依赖声明0%15%
构建说明50%10%
CI 配置0%10%
测试证据0%10%
数据可用性0%10%
+
+
+ +
+

详细评估与改进建议

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
维度评分证据建议
许可证未扫描(无本地仓库)建议添加 MIT/Apache-2.0/GPL-3.0 许可证
无密钥/PII⚠️未扫描(无本地仓库)立即移除泄露的密钥,使用环境变量管理敏感信息
README 完整README 缺失或过于简略补充项目目的、安装、使用、许可和引用章节
依赖声明无依赖声明添加 package.json/go.mod/requirements.txt 等标准依赖文件
构建说明⚠️部分构建说明添加 Makefile/Dockerfile + README 中的构建步骤
CI 配置无 CI 配置配置 GitLink CI 或 GitHub Actions 自动构建和测试
测试证据无测试证据添加单元测试和集成测试,在 README 中说明如何运行
数据可用性无数据可用性声明说明数据集来源,提供 Zenodo/Figshare 链接或生成脚本
+
+ +
+ + + + + diff --git a/output/research-insights-gitlink-cli-2026-07-06.html b/output/research-insights-gitlink-cli-2026-07-06.html new file mode 100644 index 00000000..cbc6c469 --- /dev/null +++ b/output/research-insights-gitlink-cli-2026-07-06.html @@ -0,0 +1,149 @@ + + + + + +zzx-coder/gitlink-cli — 科研项目洞察报告 + + + + +
+

zzx-coder/gitlink-cli

+
科研项目洞察报告 — 2026-07-06
+
+
+ +
+
+
热度评分
+
54.3
+
Hot
+
+
+
Stars
+
0
+
+
+
Forks
+
0
+
+
+
贡献者
+
3
+
+
+
开放 Issues
+
44
+
+
+
PR 合并率
+
50.0%
+
+
+ +
+
+

项目概况

+ + + + + + + + + + +
项目名称gitlink-cli
描述No description
主要语言Unknown
技术栈Unknown
创建时间
最后更新 (365 天前)
科研特征
+
+ +
+

活动概览

+
+
+
+ +
+
+

健康指标

+ + + + + + + + +
指标数值状态
Issue 总量44 开放 / 44 已关闭需关注
PR 合并率50.0%需改进
Release 数6已发布
CI 通过率0% (0 次构建)不稳定
贡献者数3 人单人项目
活跃度365 天前更新不活跃
+
+ +
+

热度构成

+
+
+
+ +
+ + + + + diff --git a/shortcuts/issue/batch.go b/shortcuts/issue/batch.go index 429a12a9..5fa4a2fc 100644 --- a/shortcuts/issue/batch.go +++ b/shortcuts/issue/batch.go @@ -399,7 +399,7 @@ func newBatchLabelShortcut() *common.Shortcut { Name: "batch-label", Description: "Change tracker label for multiple issues", Flags: []common.Flag{ - {Name: "label", Short: "l", Usage: "Target label: bug, feature, support, doc, test, duplicate, question", Required: true}, + {Name: "label", Short: "l", Usage: "Target label: bug, feature, support, doc, test, duplicate, question, or Chinese names (缺陷/功能/文档/重复/疑问/支持/任务/测试/协助/搁置)", Required: true}, {Name: "numbers", Short: "n", Usage: "Comma-separated issue numbers, e.g. 1,2,3"}, {Name: "from", Usage: "Read issue numbers from a CSV file"}, {Name: "dry-run", Usage: "Preview without making changes", Bool: true, Default: "false"}, @@ -444,7 +444,7 @@ func runBatchLabel(ctx *common.RuntimeContext) error { summary.Results = append(summary.Results, result) continue } - if err := updateIssueField(ctx, number, map[string]interface{}{"tracker_id": trackerID}); err != nil { + if err := updateIssueField(ctx, number, map[string]interface{}{"issue_tag_ids": []int{trackerID}}); err != nil { result.Status = "failed" result.Error = err.Error() summary.Failed++ @@ -552,7 +552,14 @@ func parsePriority(p string) (int, error) { } func parseTracker(label string) (int, error) { - switch strings.ToLower(strings.TrimSpace(label)) { + trimmed := strings.TrimSpace(label) + + // Check Chinese tag names first + if id, ok := tagIDs[trimmed]; ok { + return id, nil + } + + switch strings.ToLower(trimmed) { case "bug": return trackerBug, nil case "feature": @@ -571,7 +578,7 @@ func parseTracker(label string) (int, error) { if id, err := strconv.Atoi(label); err == nil { return id, nil } - return 0, fmt.Errorf("invalid label %q: use bug, feature, support, doc, test, duplicate, or question", label) + return 0, fmt.Errorf("invalid label %q: use bug, feature, support, doc, test, duplicate, question, or Chinese names (%s)", label, labelNames(tagIDs)) } } diff --git a/skills/gitlink-research/SKILL.md b/skills/gitlink-research/SKILL.md new file mode 100644 index 00000000..6d7f3a55 --- /dev/null +++ b/skills/gitlink-research/SKILL.md @@ -0,0 +1,125 @@ +--- +name: gitlink-research +version: 1.0.0 +description: "GitLink 科研辅助系统:项目洞察、热点追踪、合规复现、协作匹配、进度预警、论文引用。服务科研工作者、课题组、科研团队。" +metadata: + requires: + bins: ["gitlink-cli"] + triggers: + - "科研" + - "research" + - "论文" + - "citation" + - "知识图谱" + - "knowledge graph" + - "热点追踪" + - "合规检查" + - "复现性" + - "reproducibility" + - "协作匹配" + - "进度跟踪" + - "项目洞察" + - "引用格式" + - "BibTeX" +--- + +# gitlink-research(科研辅助系统) + +> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。 +> +> **实现方式:** 场景 1/3/5/6 有可执行脚本(`workflows/academic/`);场景 2/4 由 AI Agent 直接执行(需理解、推理、判断)。 + +**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh` 操作 GitLink 资源。** + +本 Skill 将 GitLink 平台的代码托管与协作数据转化为科研创新支撑能力,实现科研项目分析、主体画像、热点追踪、创新启发、合规校验等全链路辅助服务。 + +## 功能菜单 + +``` +====== GitLink 科研辅助系统 ====== + +请选择功能: + + 1. 仓库级科研项目洞察 + → 仓库深度画像:项目定位、技术栈、活动健康、贡献者网络 + + 2. 科研热点追踪与知识图谱 + → 跨仓库搜索、热点趋势、知识图谱可视化 + + 3. 科研项目合规与复现性检查 + → 许可证合规 + 8维复现性评分卡 + + 4. 科研协作智能匹配 + → 互补项目发现、潜在合作者推荐 + + 5. 科研进度智能跟踪与预警 + → 里程碑跟踪、异常检测、周报生成 + + 6. 一键生成论文引用格式 + → BibTeX / APA / MLA / GB/T 7714 / CITATION.cff + +请输入编号(1-6)或功能名称: +``` + +## 用户输入 → 场景映射 + +| 用户输入 | 执行的场景 | Reference 文件 | 实现方式 | +|----------|-----------|---------------|---------| +| `1` / "项目洞察" | 仓库级科研项目洞察 | [`research-project-insights.md`](references/research-project-insights.md) | `workflows/academic/06-research-insights.sh` 脚本 | +| `2` / "热点追踪" / "知识图谱" | 科研热点追踪与知识图谱 | [`research-hotspot-tracking.md`](references/research-hotspot-tracking.md) | **AI Agent 直接执行** | +| `3` / "合规复现" / "合规检查" | 项目合规与复现性检查 | [`research-compliance-repro.md`](references/research-compliance-repro.md) | `workflows/academic/08-research-compliance.sh` 脚本 | +| `4` / "协作匹配" | 科研协作智能匹配 | [`research-collab-matching.md`](references/research-collab-matching.md) | **AI Agent 直接执行** | +| `5` / "进度预警" / "进度跟踪" | 进度跟踪与预警 | [`research-progress-tracking.md`](references/research-progress-tracking.md) | `workflows/academic/10-research-progress.sh` 脚本 | +| `6` / "引用格式" / "论文引用" | 论文引用格式生成 | [`research-citation-format.md`](references/research-citation-format.md) | `workflows/academic/11-research-citation.sh` 脚本 | + +## 执行流程 + +1. **展示菜单** — 列出 6 个科研辅助场景 +2. **获取用户选择** — 用户输入编号或功能名称 +3. **读取 Reference** — 根据选择读取对应的 reference 文件 +4. **确认参数** — 询问必要的参数(owner/repo/keywords 等) +5. **执行**: + - **脚本场景**(1/3/5/6)→ 运行 `workflows/` 下的 Shell 脚本 + - **AI 场景**(2/4)→ Agent 直接调用 `gitlink-cli` 收集数据,AI 完成分析和生成 +6. **展示结果** — 输出摘要,HTML 报告自动打开 + +## 快捷触发 + +用户可以直接说特定意图,跳过菜单: + +| 用户说的话 | 直接执行 | +|------------|----------| +| "分析一下这个仓库的科研价值" | 场景 1:项目洞察 | +| "帮我追踪 NLP 的热点" | 场景 2:热点追踪 | +| "检查这个项目的复现性" | 场景 3:合规复现 | +| "帮我找合作者" | 场景 4:协作匹配 | +| "看一下项目进度有没有风险" | 场景 5:进度预警 | +| "生成这个项目的论文引用" | 场景 6:引用格式 | + +## 参数说明 + +| 参数 | 说明 | 获取方式 | +|------|------|----------| +| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 | +| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 | +| `--keywords` | 搜索关键词(逗号分隔) | 询问用户(场景 2、4) | +| `--format` | 引用格式(场景 6) | 询问用户,默认 all | +| `--org` | 组织名称 | 询问用户(场景 5 多仓库模式) | + +## 输出产物 + +每个场景生成的产物: + +| 场景 | 产物 | +|------|------| +| 1. 项目洞察 | `output/research-insights-{repo}-{date}.html` + Wiki 页面 | +| 2. 知识图谱 | `output/knowledge-graph-{date}.json` + `output/knowledge-graph-{date}.html` + Wiki | +| 3. 合规复现 | `output/reproducibility-{repo}-{date}.html` + Wiki | +| 4. 协作匹配 | `output/collab-match-{date}.html` + Wiki | +| 5. 进度预警 | `output/progress-weekly-{date}.html` + Wiki | +| 6. 引用格式 | 控制台输出 + `CITATION.cff` 文件(可选) | + +## References + +- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数 +- [gitlink-workflow](../gitlink-workflow/SKILL.md) — 通用 AI 工作流 diff --git a/skills/gitlink-research/references/research-citation-format.md b/skills/gitlink-research/references/research-citation-format.md new file mode 100644 index 00000000..15701d66 --- /dev/null +++ b/skills/gitlink-research/references/research-citation-format.md @@ -0,0 +1,114 @@ +# 场景 6:一键生成论文引用格式 + +## 目标 + +从 GitLink 仓库提取元数据,自动生成学术论文引用格式(BibTeX / APA / MLA / GB/T 7714 / CITATION.cff)。 + +## 参数 + +| 参数 | 必需 | 说明 | +|------|------|------| +| `--owner` | 是 | 仓库所有者 | +| `--repo` | 是 | 仓库名称 | +| `--format` | 否 | bibtex / apa / mla / gbt7714 / cff / all(默认 all) | +| `--output` | 否 | 输出文件路径(默认 stdout) | + +## 数据收集步骤 + +### Step 1: 获取仓库基本信息 +```bash +gitlink-cli repo +info --owner $OWNER --repo $REPO --format json +``` +提取字段: +- `.data.name` → 项目名称 +- `.data.description` → 描述 +- `.data.owner.login` → 所有者用户名 +- `.data.updated_at` / `.data.created_at` → 日期 + +### Step 2: 获取最新 Release +```bash +gitlink-cli release +list --owner $OWNER --repo $REPO --limit 1 --format json +``` +提取字段: +- `.data[0].tag_name` → 版本号 +- `.data[0].created_at` → 发布日期 + +### Step 3: 获取贡献者列表 +```bash +gitlink-cli repo +members --owner $OWNER --repo $REPO --limit 20 --format json +``` +提取 `.data[].login` 和 `.data[].name` 组装作者列表。 + +### Step 4: 检测 DOI(可选) +在仓库描述和 README 中扫描 DOI 模式 `10.\d{4,}/[\w.\-/]+`: +```bash +gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null +``` +用 grep 提取 DOI。 + +### Step 5: 获取仓库 URL +```bash +git remote get-url origin +``` + +## 格式模板 + +### BibTeX +```bibtex +@software{${REPO_SHORTNAME}, + author = {${AUTHOR_LIST_BIBTEX}}, + title = {${REPO_NAME}}, + version = {${VERSION}}, + date = {${RELEASE_DATE}}, + publisher = {GitLink}, + url = {${REPO_URL}}, + note = {${DESCRIPTION}} +} +``` + +### APA 7th +``` +${AUTHOR_LIST_APA} (${YEAR}). ${REPO_NAME} (Version ${VERSION}) [Computer software]. + GitLink. ${REPO_URL} +``` + +### MLA 9th +``` +${AUTHOR_LIST_MLA}. ${REPO_NAME}. Version ${VERSION}, GitLink, + ${RELEASE_DATE}, ${REPO_URL}. +``` + +### GB/T 7714-2015 +``` +[1] ${AUTHOR_LIST_GB}. ${REPO_NAME}[CP/OL]. ${VERSION}. GitLink, + ${RELEASE_DATE}[${CITE_DATE}]. ${REPO_URL}. +``` + +### CITATION.cff +```yaml +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: ${FAMILY_NAME} + given-names: ${GIVEN_NAME} +title: ${REPO_NAME} +version: ${VERSION} +date-released: ${RELEASE_DATE} +url: ${REPO_URL} +repository-code: ${REPO_GIT_URL} +``` + +## 作者列表格式化规则 + +- **BibTeX**: `Last1, First1 and Last2, First2` +- **APA**: `Last1, F., & Last2, F. (YYYY)` +- **MLA**: `Last1, First1, et al.`(超过 2 人用 et al.) +- **GB/T 7714**: `作者1, 作者2`(英文名保留原名,中文名用中文) + +## 注意事项 + +- 如果仓库没有 Release,版本号用 "v0.0.0-dev",日期用仓库最后更新时间 +- 作者列表优先使用 `name` 字段,回退到 `login` +- 超过 10 个贡献者时,只取前 5 个 + "et al." +- DOI 不存在时,BibTeX 省略 `doi` 字段 +- 确保生成的 `.cff` 文件是合法 YAML diff --git a/skills/gitlink-research/references/research-collab-matching.md b/skills/gitlink-research/references/research-collab-matching.md new file mode 100644 index 00000000..e55c4e04 --- /dev/null +++ b/skills/gitlink-research/references/research-collab-matching.md @@ -0,0 +1,117 @@ +# 场景 4:科研协作智能匹配 + +> **执行方式:AI Agent 直接执行**(无脚本,需语义理解匹配理由) + +## 目标 + +分析研究者的代码仓库特征,在 GitLink 上搜索互补项目和潜在合作者,生成有说服力的匹配推荐及理由。 + +## 参数获取 + +向用户询问: +1. **源仓库**(必需):owner 和 repo(可从当前目录 git remote 自动解析) +2. **额外搜索关键词**(可选):不填则从仓库自动提取 +3. **推荐上限**(可选,默认 5) + +## Agent 执行步骤 + +### Step 1: 分析源仓库画像 + +```bash +gitlink-cli repo +info --owner --repo --format json +gitlink-cli repo +members --owner --repo --limit 20 --format json +gitlink-cli issue +list --owner --repo --state open --limit 30 --format json +``` + +从返回数据中提取(需 AI 理解): +- **技术栈**:从 language 字段 + description 推断具体技术(如 "Go + CLI + DevOps") +- **领域主题**:从 description 提取 3-5 个有意义的科研/技术领域词 +- **现有成员技能画像**:成员数量和活跃度 +- **help-wanted 需求**:扫描 Issue 标题含 "help wanted" / "求助" / "good first issue" 的 +- **仓库定位**:工具类 / 库 / 应用 / 论文代码 / 数据集 + +### Step 2: 提取搜索关键词 + +AI 从源仓库画像中提取 3-5 个搜索关键词: +- 互补语言/框架(如源用 Python,则搜索 C++/Rust 高性能库) +- 相关领域词(如源做 NLP,则搜索 "text processing", "tokenizer", "embedding") +- 不要用过于宽泛的词(如 "code", "test", "tool") + +### Step 3: 搜索候选仓库 + +对每个关键词: +```bash +gitlink-cli search +repos --keyword "" --limit 5 --format json +``` + +合并去重(按 full_name),排除源仓库自身。 +API 返回结构:`{ok, data: {projects: [...]}}` + +如果候选太多(>15),AI 筛选最相关的 10 个进行深度分析。 + +### Step 4: 分析候选仓库 + +对每个候选仓库获取: +```bash +gitlink-cli repo +info --owner --repo --format json +gitlink-cli repo +members --owner --repo --limit 20 --format json +gitlink-cli issue +list --owner --repo --state open --limit 30 --format json +``` + +### Step 5: AI 匹配评估(核心) + +对每个候选仓库,AI 从五个维度评估并给出 0-100 的匹配评分: + +#### 1. 技能互补性(权重 30%) +- 源仓库和候选仓库的技术栈不同 → 互补性高 +- 完全相同的技术栈 → 互补性低,但可能有协作深化机会 +- AI 需要判断:技术差异是有意义的互补还是无关 + +#### 2. 领域重叠度(权重 20%) +- 两个仓库的领域主题有多少重叠 +- 完全无关的领域 → 低分;相同领域不同方法 → 高分 + +#### 3. help-wanted 匹配度(权重 20%) +- 候选仓库有没有源方技能可以解决的 help-wanted issue +- 不只是计数,要判断 Issue 内容是否匹配源方技能 + +#### 4. 生态桥接(权重 15%) +- 两个仓库是否在同一个技术生态中 +- 比如都用 PyTorch、都做数据处理管线、都是 CLI 工具等 + +#### 5. 已有合作基础(权重 15%) +- 是否有共享的贡献者 +- 是否相互引用(README 中的 URL 引用) + +**匹配等级:** +| 评分 | 等级 | 建议 | +|------|------|------| +| ≥ 70 | Strong | 强烈推荐,可主动联系 | +| 50-69 | Good | 推荐关注 | +| 30-49 | Possible | 可保持关注 | +| < 30 | Weak | 不推荐 | + +### Step 6: 生成推荐理由 + +**关键:每个推荐必须有具体的、有说服力的理由,不能是模板套话。** + +好理由 vs 坏理由: + +| 坏理由(不要写) | 好理由(应该写) | +|-----------------|-----------------| +| "技术栈互补" | "源方是 Go CLI 工具,候选是 Rust 高性能计算库,可在数据处理管线协作" | +| "领域重叠" | "双方都在 NLP 领域,源方做推理优化,候选做模型量化,技术上有直接结合点" | +| "有 help-wanted" | "候选有3个关于 API 文档的 help-wanted Issue,源方擅长 CLI 开发正好互补" | + +### Step 7: 输出结果 + +1. **控制台摘要**:Top 5 匹配结果表格(排名、仓库、评分、等级、一句话理由) +2. **JSON 文件**(可选):`output/collab-match-{date}.json` +3. **HTML 卡片**(可选):使用 `skills/gitlink-research/workflows/templates/collab-match.html` 模板 + +## 注意事项 + +- 推荐质量 > 数量,宁可只推荐 2 个优质匹配,不要堆砌 10 个低质量匹配 +- 如果候选仓库没有实质内容(空 description、0 成员、僵尸仓库),直接跳过 +- 匹配理由要用中文、短句、具体 +- 如果找不到高质量匹配,诚实告知用户"当前领域在 GitLink 上暂无可协作项目" diff --git a/skills/gitlink-research/references/research-compliance-repro.md b/skills/gitlink-research/references/research-compliance-repro.md new file mode 100644 index 00000000..3da67d77 --- /dev/null +++ b/skills/gitlink-research/references/research-compliance-repro.md @@ -0,0 +1,138 @@ +# 场景 3:科研项目合规与复现性检查 + +## 目标 + +检查科研代码仓库的许可证合规性、信息安全风险,并评估项目的可复现性,生成评分卡报告。 + +## 参数 + +| 参数 | 必需 | 说明 | +|------|------|------| +| `--owner` | 是 | 仓库所有者 | +| `--repo` | 是 | 仓库名称 | +| `--output` | 否 | 输出文件路径 | +| `--local-path` | 否 | 本地仓库路径(用于 compliance 扫描,默认当前目录) | + +## 数据收集步骤 + +### Step 1: 合规扫描 +需要先在本地克隆的仓库目录中运行: +```bash +cd $LOCAL_REPO_PATH +gitlink-cli compliance +scan --format json +``` + +五个模块: +- **license**: 检测 LICENSE 文件是否存在、许可证类型 +- **deps**: 检查依赖是否与许可证兼容 +- **secrets**: 扫描硬编码密钥/Access Token +- **exposure**: 检测 PII(邮箱、手机号)和内部 URL 暴露 +- **vocab**: 敏感词汇扫描 + +### Step 2: README 完整性 +```bash +gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null +``` +检测以下章节是否存在(每项 0/0.5/1.0): +- 项目标题与描述 +- 安装说明(Install/安装/Setup) +- 使用说明(Usage/使用/Quick Start) +- 依赖声明(Requirements/依赖/Dependencies) +- 许可证信息 +- 引用/致谢(Citation/Acknowledgement/引用) + +### Step 3: 依赖声明检测 +```bash +gitlink-cli api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" --format json +``` +检测依赖文件存在性: +- `package.json` (Node.js) +- `go.mod` (Go) +- `requirements.txt` / `pyproject.toml` / `Pipfile` (Python) +- `Cargo.toml` (Rust) +- `CMakeLists.txt` / `conanfile.txt` (C/C++) +- `pom.xml` / `build.gradle` (Java) +- `Gemfile` (Ruby) +- `DESCRIPTION` (R) +- `Project.toml` (Julia) + +### Step 4: 构建说明检测 +- README 中搜索关键词:build, install, compile, make, 构建, 安装, 编译 +- 检测 Makefile / Dockerfile / docker-compose.yml 的存在 +- 检测 CI 配置文件(.github/workflows/, .gitlab-ci.yml, Jenkinsfile) + +### Step 5: CI/CD 配置 +```bash +gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 10 --format json +``` +- builds 列表非空 = 有 CI 配置 +- 最近构建状态 = CI 是否通过 + +### Step 6: 测试证据 +- 检测 test/、tests/、spec/、__tests__/ 目录 +- README 中搜索 test、测试、validate +- 检测测试框架文件(*_test.go, *_test.py, *.test.js, *.spec.ts) + +### Step 7: 数据可用性声明 +- 扫描 README + 描述中的: + - URL 指向 data/ 目录 + - dataset / 数据集关键词 + - Zenodo / Figshare / Kaggle / HuggingFace 链接 + - DOI 引用 + +## 复现性评分 + +8 维度加权评分(每维 0 / 0.5 / 1.0): + +| 维度 | 权重 | 评分标准 | +|------|------|---------| +| 许可证 | 15% | 1: 有 OSI 合规许可证; 0.5: 有非标准许可证; 0: 无 | +| 无密钥/PII | 15% | 1: 无发现; 0.5: 有低风险发现; 0: 发现密钥 | +| README 完整 | 15% | 1: ≥5 个必需章节; 0.5: 3-4 个; 0: <3 个 | +| 依赖声明 | 15% | 1: 有标准依赖文件; 0.5: README 中列出依赖; 0: 无 | +| 构建说明 | 10% | 1: 详细步骤; 0.5: 简要提及; 0: 无 | +| CI 配置 | 10% | 1: CI 存在且通过; 0.5: CI 存在但失败; 0: 无 CI | +| 测试证据 | 10% | 1: 有测试目录+说明; 0.5: 其中之一; 0: 无 | +| 数据可用性 | 10% | 1: 明确数据引用; 0.5: 隐含提及; 0: 无 | + +总分 100: +``` +Score = SUM(dimension_score_i * weight_i) * 100 +``` + +等级: +- **A** (>=85):优秀,高度可复现 +- **B** (70-84):良好,基本可复现 +- **C** (55-69):一般,部分可复现 +- **D** (40-54):不足,复现困难 +- **F** (<40):差,几乎不可复现 + +## 输出 + +1. **HTML 评分卡** (`output/reproducibility-{repo}-{date}.html`): + - 总体评分仪表盘 + - 8 维度雷达图 + - 各维度明细表(评分 + 证据 + 改进建议) + - 合规风险汇总 + +2. **Wiki Markdown** — 精简评分卡 + +## 可执行脚本 + +```bash +cd /path/to/local/repo +bash workflows/08-research-compliance.sh --owner zzx-coder --repo gitlink-cli +``` + +## 改进建议生成 + +根据评分自动生成改进建议: + +| 缺失项 | 建议 | +|--------|------| +| 无 LICENSE | 建议添加 MIT/Apache-2.0/GPL-3.0 许可证 | +| 无 README | 建议添加 README 说明项目目的、安装和使用 | +| 无依赖文件 | 建议添加 package.json/go.mod/requirements.txt | +| 无 CI | 建议配置 .github/workflows 或 GitLink CI | +| 无测试 | 建议添加 unit test 和 smoke test | +| 无数据声明 | 建议说明数据集来源或生成方法 | diff --git a/skills/gitlink-research/references/research-hotspot-tracking.md b/skills/gitlink-research/references/research-hotspot-tracking.md new file mode 100644 index 00000000..08e7ac99 --- /dev/null +++ b/skills/gitlink-research/references/research-hotspot-tracking.md @@ -0,0 +1,126 @@ +# 场景 2:科研热点追踪与知识图谱构建 + +> **执行方式:AI Agent 直接执行**(无脚本,需语义理解和关系推断) + +## 目标 + +按关键词搜索 GitLink 上的科研仓库,AI 分析仓库内容后构建领域知识图谱,识别热点趋势,生成 JSON + HTML 力导向图可视化。 + +## 参数获取 + +向用户询问: +1. **关键词**(必需):逗号分隔,如 "LLM,RAG,Agent" +2. **每个关键词搜索数**(可选,默认 5,最多 10) +3. **是否限制组织**(可选) + +## Agent 执行步骤 + +### Step 1: 搜索仓库 + +对每个关键词调用: + +```bash +gitlink-cli search +repos --keyword "" --limit --format json +``` + +合并所有结果,按 `full_name` 去重。同一仓库可能被多个关键词命中(说明领域关联强)。 + +API 返回结构:`{ok, data: {projects: [...]}}` + +### Step 2: 获取每个仓库的深度数据 + +对每个仓库(上限 20 个)获取: + +```bash +gitlink-cli repo +info --owner --repo --format json +gitlink-cli release +list --owner --repo --limit 3 --format json +gitlink-cli repo +members --owner --repo --limit 20 --format json +``` + +### Step 3: AI 语义分析(核心) + +**不要机械匹配,需要 AI 理解:** + +1. **领域主题提取**:阅读每个仓库的 description,从中提取真正的科研领域关键词(不是文件后缀或框架名),去重去噪 +2. **仓库类型判断**:是论文复现代码 / 工具库 / 数据集 / 实验脚本 / 教学材料? +3. **趋势方向**:比较各仓库的最近更新时间、Issue 活跃度,判断 rising / stable / declining +4. **关系推断**: + - `has_topic`:仓库 ↔ 领域主题 + - `related_to`:同一主题下的仓库对,证据写明共同主题 + - `contributes_to`:成员 ↔ 仓库 + - `similar_tech`:技术栈重叠的仓库对 + +### Step 4: 构建知识图谱 JSON + +按以下结构输出到 `output/knowledge-graph-{date}.json`: + +```json +{ + "metadata": { + "generated_at": "", + "search_keywords": ["关键词列表"], + "total_repos_scanned": <数字>, + "total_contributors_found": <数字>, + "total_edges_inferred": <数字> + }, + "nodes": [ + {"id": "topic:", "type": "topic", "label": "<关键词>", "category": 2, "symbolSize": 30}, + {"id": "repo:/", "type": "repo", "label": "", + "desc": "<描述截断100字>", "stars": <数字>, "language": "<语言>", + "hotness": <评分>, "trend": "rising|stable|declining", + "category": 0, "symbolSize": <15+hotness*0.5>}, + {"id": "contributor:", "type": "contributor", "label": "", + "category": 1, "symbolSize": 20} + ], + "edges": [ + {"source": "repo:...", "target": "topic:...", "type": "has_topic", "weight": 1.0, "evidence": "关键词匹配"}, + {"source": "repo:...", "target": "repo:...", "type": "related_to", "weight": 0.5, "evidence": "共同主题: LLM"}, + {"source": "contributor:...", "target": "repo:...", "type": "contributes_to", "weight": 0.8} + ] +} +``` + +### Step 5: 生成 HTML 可视化 + +使用 `workflows/templates/knowledge-graph.html` 模板,替换以下占位符: + +| 占位符 | 内容 | +|--------|------| +| `{{REPORT_DATE}}` | 当前日期 | +| `{{KEYWORDS}}` | 搜索关键词串 | +| `{{TOTAL_REPOS}}` | 仓库节点数 | +| `{{TOTAL_CONTRIBUTORS}}` | 贡献者节点数 | +| `{{TOTAL_TOPICS}}` | 主题节点数 | +| `{{TOTAL_EDGES}}` | 关系边总数 | +| `{{GRAPH_NODES}}` | nodes JSON(紧凑格式) | +| `{{GRAPH_EDGES}}` | edges JSON(紧凑格式) | +| `{{TABLE_ROWS}}` | 热度排行 HTML `` 行 | +| `{{HOTTEST_REPO}}` | 热度最高的仓库名 | + +模板路径:`skills/gitlink-research/workflows/templates/knowledge-graph.html` + +### Step 6: 输出摘要 + +用中文展示: +- 搜索了哪些关键词,找到几个仓库 +- 热度 Top 5 排行榜(仓库名 + 热度 + 语言 + 趋势方向) +- 知识图谱规模:节点数、关系边数 +- 主要发现:这个领域的活跃度/主流技术/热门方向 + +## 热度估算逻辑 + +由于 API 不直接返回 30 天数据,用以下方式估算: + +``` +hotness = stars*0.15 + forks*0.10 + open_issues*0.20 + member_count*0.20 + + releases*0.15 + (recency_30d?100:recency_90d?50:10)*0.20 +``` + +趋势:最近 30 天更新 → rising,30-90 天 → stable,>90 天 → declining + +## 注意事项 + +- 温度由 AI 判断,不要机械套公式 +- 边标签(evidence)要有意义,不能只是"共同主题"这种空泛表述 +- 主题列表控制在 10 个以内,质量胜于数量 +- 如果模板文件不存在,直接用内联 HTML 生成 diff --git a/skills/gitlink-research/references/research-progress-tracking.md b/skills/gitlink-research/references/research-progress-tracking.md new file mode 100644 index 00000000..2834e188 --- /dev/null +++ b/skills/gitlink-research/references/research-progress-tracking.md @@ -0,0 +1,140 @@ +# 场景 5:科研进度智能跟踪与预警 + +## 目标 + +跟踪科研项目的开发进度,检测异常信号,生成周报和早期预警。 + +## 参数 + +| 参数 | 必需 | 说明 | +|------|------|------| +| `--owner` | 是 | 仓库所有者 | +| `--repo` | 是 | 仓库名称 | +| `--org` | 否 | 组织名称(多仓库模式) | +| `--weeks` | 否 | 回溯周数(默认 4) | +| `--output` | 否 | 输出文件路径 | + +## 数据收集步骤 + +### Step 1: Issue 数据 +```bash +gitlink-cli issue +list --owner $OWNER --repo $REPO --state open --limit 100 --format json +gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --limit 100 --format json +``` +提取: +- 每个 Issue 的状态、创建时间、更新时间、标签 +- 按周统计创建数/关闭数 +- 识别停滞 Issue(open + 60天无更新) +- 识别未分配 Issue + +### Step 2: PR 数据 +```bash +gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --limit 100 --format json +gitlink-cli pr +list --owner $OWNER --repo $REPO --state open --limit 50 --format json +``` +提取: +- 按周统计合并数/新建数 +- 开放 PR 的平均年龄 +- PR 瓶颈检测:开放 > 5 且平均年龄 > 14 天 + +### Step 3: Release 数据 +```bash +gitlink-cli release +list --owner $OWNER --repo $REPO --limit 20 --format json +``` +- 计算发布间隔 +- 检测长期无发布(> 180 天) + +### Step 4: CI 数据 +```bash +gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 20 --format json +``` +- 构建成功率 +- 最近失败次数 + +### Step 5: 多仓库模式(可选) +如果指定 `--org`: +```bash +gitlink-cli repo +list --user $ORG --limit 50 --format json +``` +对每个仓库执行 Step 1-4,生成聚合报告。 + +## 健康评分 + +``` +Health = issue_velocity * 0.30 + + pr_merge_rate * 0.25 + + milestone_ok * 0.25 + + release_cadence * 0.10 + + activity_trend * 0.10 +``` + +### issue_velocity +``` +velocity = issues_closed_28d / 28 +score = min(velocity / 1.0, 1.0) # 目标:日均关闭 1 个 Issue +``` + +### pr_merge_rate +``` +score = merged_prs_90d / max(total_prs_90d, 1) +``` + +### release_cadence +``` +interval = avg_days_between_last_3_releases +score = interval <= 30 ? 1.0 : interval <= 90 ? 0.5 : interval <= 180 ? 0.2 : 0 +``` + +### activity_trend +``` +trend = (activity_this_month - activity_last_month) / max(activity_last_month, 1) +score = clamp(trend + 0.5, 0, 1) +``` + +## 异常检测规则 + +| 异常类型 | 触发条件 | 严重程度 | +|----------|---------|---------| +| 🔴 Issue 停滞 | open > 60 天,无更新 > 14 天 | Warning | +| 🔴 里程碑逾期 | 超过截止日期,progress < 100% | Critical | +| 🟡 活动骤降 | 月活动量环比下降 > 50% | Warning | +| 🟡 PR 积压 | open PRs > 5, avg_age > 14 天 | Warning | +| 🟠 长期无发布 | 距上次 release > 180 天 | Info | +| 🟠 CI 持续失败 | 最近 5 次构建中 ≥ 3 次失败 | Warning | +| 🟢 无人认领 | 未分配 Issue > 总数 30% | Info | + +## 输出 + +1. **HTML 周报** (`output/progress-weekly-{date}.html`): + - 健康评分仪表盘 + - Issue/PR 流速趋势折线图(4 周窗口) + - 开放 vs 关闭 Issue 堆叠柱状图 + - Release 时间线 + - 异常预警表(严重程度着色) + +2. **Wiki Markdown** — 周报摘要 + 异常列表 + +3. **控制台摘要** — 关键指标一目了然 + +## 可执行脚本 + +```bash +# 单仓库 +bash workflows/10-research-progress.sh --owner zzx-coder --repo gitlink-cli + +# 多仓库(组织) +bash workflows/10-research-progress.sh --org zzx-coder --weeks 4 +``` + +## 风险管理建议 + +对于检测到的异常,自动生成建议: + +| 异常 | 建议 | +|------|------| +| Issue 停滞 | 重新评估优先级,关闭或推进;通知负责人 | +| 里程碑逾期 | 重新规划里程碑时间表;拆分为更小的子任务 | +| 活动骤降 | 检查团队是否有阻塞因素;组织一次同步会议 | +| PR 积压 | 安排 Code Review 时间;简化 PR 粒度 | +| 长期无发布 | 考虑发布当前 master 的最小可用版本 | +| CI 持续失败 | 优先修复 CI;暂时阻止新 PR 合并直到 CI 恢复 | diff --git a/skills/gitlink-research/references/research-project-insights.md b/skills/gitlink-research/references/research-project-insights.md new file mode 100644 index 00000000..3b09e49d --- /dev/null +++ b/skills/gitlink-research/references/research-project-insights.md @@ -0,0 +1,150 @@ +# 场景 1:仓库级科研项目洞察 + +## 目标 + +对单个 GitLink 仓库进行深度分析,生成综合项目画像报告,包括项目定位、技术栈、活动健康、贡献者网络和热度评分。 + +## 参数 + +| 参数 | 必需 | 说明 | +|------|------|------| +| `--owner` | 是 | 仓库所有者 | +| `--repo` | 是 | 仓库名称 | +| `--output` | 否 | 输出文件路径 | + +## 数据收集步骤 + +### Step 1: 仓库元数据 +```bash +gitlink-cli repo +info --owner $OWNER --repo $REPO --format json +``` +提取:name, description, language, stars_count, forks_count, open_issues_count, updated_at, created_at, owner 信息 + +### Step 2: 技术栈检测 +```bash +gitlink-cli api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" --format json +``` +扫描根目录文件,匹配技术生态文件: +- `go.mod` → Go +- `package.json` → Node.js +- `requirements.txt` / `pyproject.toml` / `setup.py` / `setup.cfg` → Python +- `Cargo.toml` → Rust +- `CMakeLists.txt` / `Makefile` → C/C++ +- `pom.xml` / `build.gradle` / `build.gradle.kts` → Java/Kotlin +- `Gemfile` → Ruby +- `CITATION.cff` → 有引文文件(科研加分项) + +同时统计文件扩展名分布(.py, .js, .go, .rs, .java, .r, .ipynb 等) + +### Step 3: 项目定位提取 +```bash +gitlink-cli api GET "raw/$OWNER/$REPO/master/README.md" --format json 2>/dev/null +``` +- 截取 README 前 2000 字符 +- 提取一级/二级标题作为结构摘要 +- 扫描关键词:research, paper, experiment, dataset, model, benchmark, 研究, 实验, 数据, 模型 +- 扫描 DOI 链接:`10.\d{4,}/[\w.\-/]+` + +### Step 4: 活动健康指标 +```bash +# Issue 数据 +gitlink-cli issue +list --owner $OWNER --repo $REPO --state open --limit 100 --format json +gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --limit 100 --format json + +# PR 数据 +gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --limit 100 --format json +gitlink-cli pr +list --owner $OWNER --repo $REPO --state open --limit 50 --format json + +# Release 数据 +gitlink-cli release +list --owner $OWNER --repo $REPO --limit 20 --format json + +# CI 数据 +gitlink-cli ci +builds --owner $OWNER --repo $REPO --limit 20 --format json +``` + +指标计算: +- Issue 流速:最近 30/90 天创建和关闭的 Issue 数 +- PR 合并率:merged / (merged + closed) +- 发布频率:最近 3 次发布的间隔天数 +- CI 通过率:成功构建数 / 总构建数 +- 平均 Issue 响应时间(估算) + +### Step 5: 贡献者网络 +```bash +gitlink-cli repo +members --owner $OWNER --repo $REPO --limit 50 --format json +``` +- 提取所有贡献者列表 +- 从 Issue/PR 数据中提取贡献者共现关系 +- 构建合作邻接矩阵(两个贡献者参与同一 Issue/PR 则建立边) + +## 热度评分公式 + +``` +Hotness = stars_norm * 0.15 + + forks_norm * 0.10 + + issues_30d * 0.20 + + prs_30d * 0.20 + + releases_90d * 0.15 + + commits_30d * 0.10 + + recency * 0.10 +``` + +其中各项均归一化到 0-100: +- stars_norm = min(stars / max_repo_stars * 100, 100) +- recency = updated_within_30d ? 100 : (updated_within_90d ? 50 : 10) + +热度等级:Hot (>=50) / Warm (30-49) / Cool (<30) + +## 输出 + +1. **HTML 报告** (`output/research-insights-{repo}-{date}.html`): + - 摘要卡片(名称、语言、星标、Fork、热度评分) + - 技术栈饼图 + - 活动时间线折线图 + - 贡献者网络力导向图 + - 健康评分仪表盘 + +2. **Wiki Markdown** — 精简版报告发布到 GitLink Wiki: +```bash +gitlink-cli wiki +create --owner $OWNER --repo $REPO \ + --title "[Research:Insights] $REPO 项目洞察 $(date +%Y-%m-%d)" \ + --content "$WIKI_CONTENT" +``` + +## 可执行脚本 + +直接运行 `workflows/06-research-insights.sh`: +```bash +bash workflows/06-research-insights.sh --owner zzx-coder --repo gitlink-cli +``` + +--- + +## 场景 1 补充:科研项目画像维度 + +除了基础的项目洞察外,针对科研项目增加以下分析维度: + +### 科研特征识别 + +从 README、描述和代码中识别科研特征: + +| 特征 | 检测方式 | +|------|---------| +| 引用论文 | DOI 模式 `10.\d{4,}/` | +| 数据集 | data/ 目录、dataset 关键词、Zenodo/Figshare 链接 | +| 实验脚本 | scripts/ 目录、run_experiment、train、evaluate 关键词 | +| 基准测试 | benchmark/ 目录、benchmark 关键词 | +| Jupyter Notebooks | .ipynb 文件存在 | +| 预训练模型 | .pt/.pth/.h5/.onnx 文件或 model/checkpoint 目录 | +| Docker | Dockerfile 或 docker-compose.yml | +| 结果可视化 | 图片目录、图表关键词 | + +### 科研影响力量化 + +``` +Research Impact = 0.30 * citation_count_estimate + + 0.25 * dataset_availability + + 0.20 * paper_links + + 0.15 * fork_productivity(fork 后是否产生新研究) + + 0.10 * cross_repo_references +``` diff --git a/skills/gitlink-workflow/SKILL.md b/skills/gitlink-workflow/SKILL.md index ab34f7d6..75d23dff 100644 --- a/skills/gitlink-workflow/SKILL.md +++ b/skills/gitlink-workflow/SKILL.md @@ -1,177 +1,100 @@ --- name: gitlink-workflow version: 2.0.0 -description: "AI 自动化工作流:代码质量审查(PR Review)。当用户需要 AI 审查 PR 代码质量时触发。" +description: "AI 自动化工作流:社区运营、代码审查、项目初始化、多仓库协同、贡献者成长。当用户需要 AI 自动化 GitLink 操作时触发。" metadata: requires: bins: ["gitlink-cli"] + triggers: + - "工作流" + - "workflow" + - "自动化" + - "帮我跑" + - "执行" --- -# gitlink-workflow(AI 代码质量看门人) +# gitlink-workflow(AI 自动化工作流) > **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) -**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。** +**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。** -## 工作流:代码质量看门人(Code Quality Gatekeeper) +本技能提供 Claude Code 可直接执行的高级工作流模板。 -**触发词**:代码审查、PR review、质量检查、gatekeeper、审查 PR - -**参数**: -- `--owner`:仓库所属组织或用户 -- `--repo`:仓库名称 -- `--pr-id`:指定 PR ID(可选,默认审查所有 open PR) -- `--threshold`:质量阈值(默认 80) - -### 流程总览 +## 功能菜单 ``` -PR 提交 → 获取详情 → 获取 Diff → AI 四维度评分 → 发布审查评论 → 检查 CI → 自动合并 +====== GitLink 自动化工作流 ====== + +请选择要执行的工作流: + + 1. 社区运营自动化 + → Issue 自动分类、负责人分配、周报生成、Release Notes + + 2. 代码质量审查 + → PR Review、AI 四维度评分、自动合并 + + 3. 项目一键初始化 + → 创建仓库、README、CI 配置、初始 Issues、分支保护 + + 4. 多仓库协同 + → 跨仓库 Issue/PR 追踪、状态 Dashboard、协同发版 + + 5. 贡献者成长体系 + → 数据收集、AHP 评分、排行榜、Wiki 发布、Badge 颁发 + +请输入编号(1-5)或功能名称: ``` -### 审查单个 PR 的步骤 +## 用户输入 → 工作流映射 -**步骤 1** — 获取 PR 详情: -```bash -gitlink-cli pr +view --owner {OWNER} --repo {REPO} --id {PR_ID} --format json -``` -提取字段: -- title: `.data.title // .data.subject // .data.issue.subject` -- author: `.data.author.login // .data.author.username` -- state: `.data.state // .data.status` +| 用户输入 | 执行的工作流 | Reference 文件 | +|----------|-------------|---------------| +| `1` 或 "社区运营" | 社区运营自动化 | [`workflow-community-ops.md`](references/workflow-community-ops.md) | +| `2` 或 "代码审查" | 代码质量审查 | [`workflow-pr-review.md`](references/workflow-pr-review.md) | +| `3` 或 "项目初始化" | 项目一键初始化 | [`workflow-repo-setup.md`](references/workflow-repo-setup.md) | +| `4` 或 "多仓库" | 多仓库协同 | [`workflow-multi-repo.md`](references/workflow-multi-repo.md) | +| `5` 或 "贡献者" | 贡献者成长体系 | [`workflow-contributor-growth.md`](references/workflow-contributor-growth.md) | -**步骤 2** — 获取变更文件列表: -```bash -gitlink-cli pr +files --owner {OWNER} --repo {REPO} --id {PR_ID} --format json -``` -文件列表路径:`.data.files[]`,字段:`.name`(或 `.filename`)、`.additions`、`.deletions` +## 执行流程 -**步骤 3** — 获取代码差异: -```bash -gitlink-cli pr +diff --owner {OWNER} --repo {REPO} --id {PR_ID} --format json -``` -提取 diff:`.data.files[].sections[].lines[].content`,截取前 5000 字符 +1. **展示菜单** — 列出所有可用工作流 +2. **获取用户选择** — 用户输入编号或功能名称 +3. **读取 Reference** — 根据选择读取对应的 reference 文件 +4. **确认参数** — 询问必要的参数(owner/repo 等) +5. **执行工作流** — 按照 reference 文件的步骤执行 +6. **展示结果** — 输出执行结果和摘要 -**步骤 4** — AI 四维度代码审查(总分 100): +## 快捷触发 -| 维度 | 满分 | 检查项 | -|------|------|--------| -| 代码质量 | 25 | 复杂度、命名规范、注释、格式一致性 | -| 安全性 | 25 | SQL注入、XSS、硬编码凭证、认证绕过、输入验证 | -| 性能 | 25 | 循环效率、资源泄漏、N+1查询、内存占用、阻塞调用 | -| 可维护性 | 25 | 代码重复、职责单一、依赖耦合、测试覆盖 | +用户也可以直接说特定意图,跳过菜单直接执行: -评分标准: -- 90-100:优秀,可直接合并 -- 75-89:良好,建议合并 -- 60-74:一般,需要改进 -- <60:较差,不建议合并 +| 用户说的话 | 直接执行 | +|------------|----------| +| "帮我跑一下社区运营" | 社区运营自动化 | +| "审查一下这个 PR" | 代码质量审查 | +| "创建一个新项目" | 项目一键初始化 | +| "看看组织下所有仓库" | 多仓库协同 | +| "生成贡献者排行榜" | 贡献者成长体系 | -问题严重级别: -- CRITICAL:阻止合并(安全漏洞、数据丢失风险) -- HIGH:强烈建议修复(性能问题、逻辑错误) -- MEDIUM:建议修复(代码质量、可维护性) -- LOW:可选修复(风格、命名) +## 参数说明 -**输出要求**:AI 必须输出以下 JSON 结构: -```json -{ - "total": 85, - "quality": 22, - "security": 25, - "performance": 20, - "maintainability": 18, - "issues": [ - { - "severity": "MEDIUM", - "category": "quality", - "file": "src/main.go", - "rule": "naming", - "description": "变量名过于简短", - "suggestion": "使用更具描述性的变量名" - } - ], - "positive_notes": [ - {"description": "错误处理完善"} - ], - "recommendations": [ - "建议添加单元测试" - ], - "verdict": "PASS" -} -``` +| 参数 | 说明 | 获取方式 | +|------|------|----------| +| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 | +| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 | +| `--org` | 组织名称 | 询问用户(多仓库协同时需要) | -**AI 不可用时的降级方案**(关键词检测): -- 检测到 password/secret/token/api_key/private_key → security -15 -- 检测到 eval()/exec()/system()/os.system → security -10 -- 检测到 TODO/FIXME/HACK/XXX → quality -5 -- 检测到 SELECT */findAll()/.all() → performance -10 -- 检测到 sleep()/Thread.sleep → performance -5 -- 变更文件 > 20 个 → maintainability -10 +## 最佳实践 -**步骤 5** — 组装审查评论 Markdown: +- 所有工作流命令使用 `--format json` 以便解析输出 +- 写入操作前确认用户意图 +- 批量操作建议先用小范围测试 +- 保存工作流执行结果以便回溯 +- 自动从 git remote 解析 owner/repo,解析失败时询问用户 +- 支持 `--dry-run` 预览模式(部分工作流) -```markdown -## AI Code Quality Review - PR #{PR_ID} +## References -### Scores -| Dimension | Score | Max | -|-----------|-------|-----| -| Code Quality | {QUALITY} | 25 | -| Security | {SECURITY} | 25 | -| Performance | {PERFORMANCE} | 25 | -| Maintainability | {MAINTAINABILITY} | 25 | -| **Total** | **{TOTAL}** | **100** | - -### Issues Found -- [{SEVERITY}] {CATEGORY}: {DESCRIPTION} ({FILE}) → {SUGGESTION} - -### Positive Notes -- {NOTE} - -### Recommendations -- {REC} - -### Verdict -{PASS/FAIL} - Score {TOTAL} {>=/<} threshold {THRESHOLD} - ---- -*Auto-reviewed by gitlink-cli code-quality-gatekeeper workflow* -``` - -**步骤 6** — 发布审查评论: -```bash -gitlink-cli api POST /{OWNER}/{REPO}/pulls/{PR_ID}/reviews --body '{"body":"{REVIEW_MD}","event":"{EVENT}"}' -``` -- 分数 >= 阈值 → event = "APPROVE" -- 分数 < 阈值 → event = "COMMENT" - -**步骤 7** — 检查 CI 构建状态: -```bash -gitlink-cli ci +builds --owner {OWNER} --repo {REPO} --format json -``` -遍历 `.data.builds[]` 或 `.data[]`,检查 status 是否为 success/passed/completed - -**步骤 8** — 自动合并(条件:分数 >= 阈值 且 CI 全部通过): -```bash -gitlink-cli pr +merge --owner {OWNER} --repo {REPO} --id {PR_ID} --method merge -``` - -### 审查所有 open PR - -如果用户没指定 `--pr-id`,先获取列表再逐个审查: -```bash -gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state open --limit 50 --format json -``` -PR ID 提取:`.data.issues[]` 或 `.data.pulls[]` 或 `.data[]` 中的 `.pull_request_number // .number // .id` - -## 其他自动化工作流 - -以下工作流不需要 AI,已实现为 PowerShell 脚本(`workflows/` 目录): - -| 工作流 | 脚本 | 用法 | -|--------|------|------| -| 社区运营自动化 | `01-community-ops.ps1` | `pwsh workflows/01-community-ops.ps1 -Owner zzx-coder -Repo gitlink-cli` | -| 项目一键初始化 | `03-project-init.ps1` | `pwsh workflows/03-project-init.ps1 -Owner org -Name my-app -Desc "描述" -Lang go` | -| 多仓库协同 | `04-multi-repo-collab.ps1` | `pwsh workflows/04-multi-repo-collab.ps1 -Org myorg` | -| 贡献者成长体系 | `05-contributor-growth.ps1` | `pwsh workflows/05-contributor-growth.ps1 -Owner zzx-coder -Repo gitlink-cli` | +- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数 +- [gitlink-workflows](../gitlink-workflows/SKILL.md) — 工作流总入口(含可执行脚本) diff --git a/skills/gitlink-workflow/examples/full-automation-workflow.md b/skills/gitlink-workflow/examples/full-automation-workflow.md deleted file mode 100644 index ff1cc982..00000000 --- a/skills/gitlink-workflow/examples/full-automation-workflow.md +++ /dev/null @@ -1,579 +0,0 @@ -# AI Agent 自动化工作流完整示例 - -本文档展示如何组合使用多个 gitlink-cli 工作流,实现完整的 AI Agent 自动化项目管理。 - -## 🤖 AI Agent 完整工作流 - -### 场景:新项目从创建到发布的完整自动化 - -这个示例展示 AI Agent 如何自动化管理一个软件项目的完整生命周期,从仓库创建到版本发布。 - -## 工作流组合 - -### 1. 项目初始化阶段 - -```python -# AI Agent 项目初始化 -def initialize_new_project(project_name, description): - """完整的项目初始化工作流""" - - # 1. 创建仓库 - repo = create_repository(project_name, description) - - # 2. 初始化项目结构 - setup_project_structure(repo) - - # 3. 配置 CI/CD - configure_ci_cd(repo) - - # 4. 创建初始 Issue - create_initial_issues(repo) - - return repo - -# 执行 -project = initialize_new_project( - "my-awesome-project", - "一个很棒的项目,用于演示自动化工作流" -) -``` - -### 2. 开发阶段自动化 - -```python -# AI Agent 开发管理 -def manage_development_workflow(repo): - """开发阶段的自动化管理""" - - while development_in_progress: - # 1. 自动分类新 Issue - triage_new_issues(repo) - - # 2. 审查新的 PR - review_pull_requests(repo) - - # 3. 更新项目进度 - update_project_status(repo) - - # 4. 检查是否需要发布 - if should_release(repo): - generate_release_notes(repo) - create_release(repo) - - sleep(cycle_interval) -``` - -### 3. Sprint 自动化 - -```python -# AI Agent Sprint 管理 -def automate_sprint_management(repo): - """完整的 Sprint 自动化管理""" - - # Sprint 开始 - sprint_number = start_new_sprint(repo) - - # Sprint 监控 - monitor_sprint_progress(repo, sprint_number) - - # Sprint 结束 - end_sprint(repo, sprint_number) - generate_sprint_report(repo, sprint_number) - -# 执行 Sprint 工作流 -sprint_result = automate_sprint_management(project) -``` - -## 完整的端到端示例 - -### 示例:自动化 Issue 到 Release 流程 - -```bash -#!/bin/bash - -# 完整的自动化工作流脚本 - -OWNER="ai-agent" -REPO="demo-project" -PROJECT_NAME="AI Agent Demo" - -echo "🤖 启动 AI Agent 自动化工作流..." - -# 阶段 1: 项目创建 -echo "📦 阶段 1: 创建项目" -gitlink-cli repo +create \ - --name "$REPO" \ - --description "$PROJECT_NAME" \ - --private false - -# 初始化本地仓库 -cd "$REPO" -git init -git remote add gitlink "https://www.gitlink.org.cn/$OWNER/$REPO.git" - -# 创建基础文件 -echo "# $PROJECT_NAME" > README.md -echo "MIT License" > LICENSE -git add . -git commit -m "Initial commit" -git push -u gitlink master:master - -# 设置分支保护 -gitlink-cli branch +protect --owner "$OWNER" --repo "$REPO" --name master - -# 阶段 2: 创建开发 Issue -echo "🎯 阶段 2: 创建开发 Issue" -FEATURES=( - "用户认证系统" - "数据管理模块" - "API 接口开发" - "前端界面设计" - "测试框架搭建" -) - -for feature in "${FEATURES[@]}"; do - gitlink-cli issue +create \ - --owner "$OWNER" \ - --repo "$REPO" \ - --title "开发 $feature" \ - --body "## 任务描述 - -实现 $feature 功能 - -## 技术要求 -- 代码规范 -- 单元测试 -- 文档完整 - -## 验收标准 -- 功能正常工作 -- 测试通过 -- 代码审查通过" -done - -# 阶段 3: 模拟开发和 PR 创建 -echo "🔧 阶段 3: 模拟开发工作" - -# 创建功能分支 -for feature in "${FEATURES[@]}"; do - # 模拟分支名(将中文转为拼音) - branch_name="feature-$(echo $feature | md5sum | cut -c1-8)" - - git checkout -b "$branch_name" - - # 模拟开发工作 - echo "// $feature 实现" > "${feature}.js" - git add . - git commit -m "Implement $feature" - git push gitlink "$branch_name" - - # 创建 PR - gitlink-cli pr +create \ - --owner "$OWNER" \ - --repo "$REPO" \ - --title "Feature: $feature" \ - --head "$branch_name" \ - --base master \ - --body "## 功能说明 - -实现 $feature 功能 - -## 变更内容 -- 添加核心功能 -- 实现相关测试 -- 更新文档 - -## 测试情况 -- 单元测试通过 -- 集成测试通过 -- 手工测试完成" - - git checkout master -done - -# 阶段 4: 自动 Issue 分类 -echo "🏷️ 阶段 4: 自动分类 Issue" - -# 获取所有开放 Issue -ISSUES=$(gitlink-cli issue +list --owner "$OWNER" --repo "$REPO" --state open --format json) - -# 为 Issue 添加标签 -echo "$ISSUES" | jq -r '.data.issues[].id' | while read issue_id; do - echo "处理 Issue #$issue_id" - - # 获取 Issue 详情 - ISSUE_DETAIL=$(gitlink-cli issue +view --owner "$OWNER" --repo "$REPO" --id "$issue_id" --format json) - TITLE=$(echo "$ISSUE_DETAIL" | jq -r '.data.subject') - - # 基于标题分类 - if echo "$TITLE" | grep -iq "认证"; then - echo " → 分类为: feature + security" - # 实际执行时取消注释 - # gitlink-cli api POST "/$OWNER/$REPO/issues/$issue_id" --body '{"issue_tag_ids":[1,5]}' - else - echo " → 分类为: feature" - # gitlink-cli api POST "/$OWNER/$REPO/issues/$issue_id" --body '{"issue_tag_ids":[1]}' - fi -done - -# 阶段 5: PR 审查 -echo "🔍 阶段 5: 自动 PR 审查" - -# 获取开放 PR -PRS=$(gitlink-cli pr +list --owner "$OWNER" --repo "$REPO" --state open --format json) - -echo "$PRS" | jq -r '.data.prs[].id' | while read pr_id; do - echo "审查 PR #$pr_id" - - # 获取 PR 详情 - PR_DETAIL=$(gitlink-cli pr +view --owner "$OWNER" --repo "$REPO" --id "$pr_id" --format json) - PR_AUTHOR=$(echo "$PR_DETAIL" | jq -r '.data.author.login') - PR_TITLE=$(echo "$PR_DETAIL" | jq -r '.data.title') - - # 简单的代码检查(这里只是模拟) - REVIEW_COMMENTS="# 🔍 自动审查结果 - -## PR 信息 -- **标题**: $PR_TITLE -- **作者**: $PR_AUTHOR -- **状态**: 待审查 - -## ✅ 自动检查 -- 代码提交正常 -- 变更描述清晰 -- 符合项目规范 - -## 💡 建议 -- 添加单元测试 -- 更新相关文档 -- 确认向后兼容性 - -## 📋 审查结论 -代码质量良好,建议合并。" - - echo " → 添加审查评论" - # 实际执行时取消注释 - # gitlink-cli api POST "/$OWNER/$REPO/pulls/$pr_id/reviews" --body "{\"body\":\"$REVIEW_COMMENTS\",\"event\":\"APPROVE\"}" -done - -# 阶段 6: 生成 Release Notes -echo "📝 阶段 6: 生成 Release Notes" - -# 合并所有 PR(模拟) -echo "合并所有功能 PR..." -MERGED_PRS=$(gitlink-cli pr +list --owner "$OWNER" --repo "$REPO" --state merged --format json) - -# 生成 Release Notes -RELEASE_NOTES="# 🎉 v1.0.0 首个版本发布 - -## 📊 版本概述 -这是 $PROJECT_NAME 的首个稳定版本,包含了核心功能的完整实现。 - -## ✨ 新功能 -- 用户认证系统:完整的登录注册功能 -- 数据管理模块:高效的数据存储和检索 -- API 接口:RESTful API 设计 -- 前端界面:现代化的用户界面 -- 测试框架:完整的自动化测试 - -## 🐛 Bug 修复 -- 修复认证过程中的边界问题 -- 解决数据一致性问题 -- 优化 API 响应性能 - -## 🔧 技术改进 -- 代码结构优化 -- 性能提升 30% -- 安全性增强 - -## 📚 文档更新 -- 用户手册完善 -- API 文档更新 -- 开发指南补充 - -## 🙏 贡献者 -感谢所有参与开发的贡献者! - -## 📥 安装方法 -\`\`\`bash -# 使用 npm 安装 -npm install $OWNER/$REPO@v1.0.0 - -# 或使用 yarn -yarn add $OWNER/$REPO@v1.0.0 -\`\`\` - -## 🔄 升级指南 -从之前的版本升级,请参考迁移指南。 - -## 📚 完整文档 -- 用户指南: https://www.gitlink.org.cn/$OWNER/$REPO/wiki -- API 文档: https://www.gitlink.org.cn/$OWNER/$REPO/api-docs - ---- -**发布日期**: $(date +%Y-%m-%d) -**下一版本**: v1.1.0 (计划于 $(date -d "1 month" +%Y-%m-%d) 发布)" - -# 创建 Release -echo "创建 Release v1.0.0..." -gitlink-cli release +create \ - --owner "$OWNER" \ - --repo "$REPO" \ - --tag "v1.0.0" \ - --name "v1.0.0" \ - --body "$RELEASE_NOTES" - -# 阶段 7: Sprint 报告 -echo "📊 阶段 7: 生成 Sprint 报告" - -SPRINT_START=$(date -d "14 days ago" +%Y-%m-%d) -SPRINT_END=$(date +%Y-%m-%d) - -SPRINT_REPORT="# 📊 Sprint 1 完成报告 - -## 📅 时间信息 -- **Sprint 周期**: $SPRINT_START 至 $SPRINT_END -- **团队规模**: AI Agent x 1 -- **工作模式**: 自动化开发 - -## 🎯 目标达成 -### 计划完成度 -- **计划 Issue**: 5 个 -- **实际完成**: 5 个 -- **完成率**: 100% - -### 质量指标 -- **代码质量**: 优秀 -- **测试覆盖率**: 95% -- **文档完整度**: 100% - -## 💻 工作统计 -### 代码提交 -- **总提交数**: 42 次 -- **日均提交**: 3 次/天 -- **代码行数**: +2,450 -180 行 - -### Issue 处理 -- **关闭 Issue**: 5 个 -- **新建 Issue**: 0 个 -- **平均处理时间**: 2.5 天 - -### PR 合并 -- **合并 PR**: 5 个 -- **平均审查时间**: 1 小时 -- **平均合并时间**: 2 小时 - -## 🎉 主要成就 -1. ✅ 完成用户认证系统开发 -2. ✅ 实现数据管理模块 -3. ✅ 构建 RESTful API -4. ✅ 设计现代化前端界面 -5. ✅ 建立完整测试体系 - -## 📈 性能指标 -- **开发效率**: 高 -- **代码质量**: 优秀 -- **自动化程度**: 95% -- **文档完整度**: 100% - -## 🔮 下期规划 -- 性能优化和改进 -- 新功能模块开发 -- 国际化支持 -- 移动端适配 - ---- -**AI Agent 自动化工作流演示** -**报告生成**: $(date +%Y-%m-%d %H:%M:%S)" - -# 保存 Sprint 报告 -REPORT_FILE="sprint_reports/sprint_1_$(date +%Y%m%d).md" -mkdir -p sprint_reports -echo "$SPRINT_REPORT" > "$REPORT_FILE" - -echo "🎉 AI Agent 自动化工作流完成!" -echo "" -echo "📊 项目统计:" -echo " 仓库: https://www.gitlink.org.cn/$OWNER/$REPO" -echo " Issue: 5 个全部完成" -echo " PR: 5 个全部合并" -echo " Release: v1.0.0 已发布" -echo "" -echo "📄 生成的文档:" -echo " - Release Notes: https://www.gitlink.org.cn/$OWNER/$REPO/releases/v1.0.0" -echo " - Sprint 报告: $REPORT_FILE" -``` - -## Claude Code 集成示例 - -### 在 Claude Code 中使用工作流 - -```markdown -# 用户指令 - -帮助我创建一个新的项目并完成首个版本的发布。 - -# Claude Code 执行 - -我会使用 gitlink-cli 的自动化工作流来完成这个任务: - -1. **创建仓库** - 使用 workflow-repo-setup -2. **管理 Issue** - 使用 workflow-issue-triage -3. **审查 PR** - 使用 workflow-pr-review -4. **生成 Release** - 使用 workflow-release-notes -5. **总结报告** - 使用 workflow-sprint-report - -让我开始执行... -``` - -### 技能组合使用 - -```python -# AI Agent 多技能组合 -class GitLinkAgent: - def __init__(self, owner, repo): - self.owner = owner - self.repo = repo - self.cli = "gitlink-cli" - - def complete_project_workflow(self): - """完整的项目工作流""" - - # 阶段 1: 初始化 - self.setup_repository() - - # 阶段 2: 开发管理 - self.manage_development() - - # 阶段 3: 质量控制 - self.automated_review() - - # 阶段 4: 发布管理 - self.create_release() - - # 阶段 5: 报告总结 - self.generate_reports() - - def setup_repository(self): - """仓库初始化""" - # 使用 workflow-repo-setup - create_repo_cmd = f"{self.cli} repo +create --name {self.repo}" - subprocess.run(create_repo_cmd.split()) - - protect_branch_cmd = f"{self.cli} branch +protect --name master" - subprocess.run(protect_branch_cmd.split()) - - def manage_development(self): - """开发管理""" - # 监控新 Issue 并自动分类 - issues = self.get_new_issues() - for issue in issues: - self.classify_issue(issue) - - # 监控新 PR 并审查 - prs = self.get_new_prs() - for pr in prs: - self.review_pr(pr) - - def automated_review(self): - """自动化审查""" - # 获取待审查的 PR - pending_prs = self.get_pending_prs() - - for pr in pending_prs: - review_result = self.analyze_pr(pr) - self.submit_review(pr, review_result) -``` - -## 最佳实践 - -### 1. 工作流选择 -- **项目创建**: 使用 workflow-repo-setup -- **日常维护**: 使用 workflow-issue-triage 和 workflow-pr-review -- **版本发布**: 使用 workflow-release-notes -- **团队管理**: 使用 workflow-sprint-report - -### 2. 执行顺序 -典型的执行顺序: -1. 项目初始化 → 2. Issue 管理 → 3. PR 审查 → 4. Release 发布 → 5. Sprint 报告 - -### 3. 错误处理 -```python -def safe_workflow_execution(workflow_func, *args, **kwargs): - """安全执行工作流""" - try: - return workflow_func(*args, **kwargs) - except Exception as e: - # 记录错误 - log_error(e) - # 尝试恢复 - return handle_workflow_error(e, workflow_func, *args, **kwargs) -``` - -### 4. 进度跟踪 -```python -class WorkflowProgress: - def __init__(self): - self.current_step = 0 - self.total_steps = 5 - self.completed_steps = [] - self.failed_steps = [] - - def update_progress(self, step_name, success=True): - if success: - self.completed_steps.append(step_name) - else: - self.failed_steps.append(step_name) - self.current_step += 1 - - def get_progress_report(self): - progress = self.current_step / self.total_steps * 100 - return { - 'progress': f'{progress:.1f}%', - 'completed': self.completed_steps, - 'failed': self.failed_steps - } -``` - -## 扩展工作流 - -### 自定义工作流 - -```python -# 创建自定义工作流 -def custom_workflow(owner, repo, custom_config): - """自定义工作流模板""" - - # 1. 预检查 - if not validate_environment(): - return False - - # 2. 执行自定义步骤 - for step in custom_config['steps']: - execute_step(step) - - # 3. 后处理 - cleanup_environment() - - return True -``` - -## 故障排除 - -### 常见问题 - -| 问题 | 解决方案 | -|------|----------| -| 权限不足 | 检查 Token 权限 | -| API 限制 | 添加重试机制 | -| 数据格式错误 | 验证输入数据 | -| 执行超时 | 增加超时时间 | - -## References - -- [workflow-issue-triage](../references/workflow-issue-triage.md) — Issue 分类 -- [workflow-pr-review](../references/workflow-pr-review.md) — PR 审查 -- [workflow-release-notes](../references/workflow-release-notes.md) — Release Notes -- [workflow-repo-setup](../references/workflow-repo-setup.md) — 仓库初始化 -- [workflow-sprint-report](../references/workflow-sprint-report.md) — Sprint 报告 -- [gitlink-workflow](../SKILL.md) — 工作流总览 diff --git a/skills/gitlink-workflow/references/workflow-community-ops.md b/skills/gitlink-workflow/references/workflow-community-ops.md new file mode 100644 index 00000000..75c88c64 --- /dev/null +++ b/skills/gitlink-workflow/references/workflow-community-ops.md @@ -0,0 +1,206 @@ +# Workflow: Community Ops(社区运营自动化) + +> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。 +> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于自动化社区运营。 + +AI Agent 自动完成社区运营任务,包括 Issue 分类、负责人分配、周报生成、Release Notes 发布。 + +## 工作流概述 + +Community Ops 工作流自动化社区运营的四个核心环节:Issue 分析 → 负责人分配 → 周报生成 → Release Notes 发布。 + +## 适用场景 + +- **Issue 管理**:自动分类和分配新 Issue +- **周报生成**:汇总本周数据生成社区周报 +- **发版管理**:生成 Release Notes 并发布 +- **定期运营**:每周/每月定期执行社区运营 + +## 触发词 + +- "社区运营" / "community ops" +- "周报" / "weekly report" +- "发版" / "release notes" +- "Issue 分类" / "issue triage" + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--owner` | 否 | 仓库所有者(自动从 git remote 解析) | +| `--repo` | 否 | 仓库名称(自动从 git remote 解析) | +| `--weeks-ago` | 否 | 生成 N 周前的周报(默认 0 = 本周) | +| `--dry-run` | 否 | 预览模式,不执行写入操作 | + +## 工作流步骤 + +### 阶段 1:Issue 自动分类 + +**步骤 1.1** — 获取 open issues: + +```bash +gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state open --limit 100 --format json +``` + +**步骤 1.2** — 分类规则: + +| 类型 | 标签 | 关键词 | +|------|------|--------| +| Bug | `bug` | bug, error, crash, fault, fix | +| Feature | `feature` | feature, enhancement, add, support, request | +| Question | `question` | how, question, help | +| Docs | `documentation` | doc, readme, guide, tutorial, example | + +**步骤 1.3** — 添加标签: + +```bash +gitlink-cli issue +label-add --owner {OWNER} --repo {REPO} --number {ISSUE_ID} --labels {LABEL} +``` + +### 阶段 2:负责人分配(基于贡献度 AHP 评分) + +**引用 [`workflow-contributor-growth.md`](workflow-contributor-growth.md) 的 AHP 评分模型** + +负责人分配基于贡献者的 AHP 评分,优先分配给贡献度高的成员。 + +**步骤 2.1** — 收集数据: + +```bash +# 获取仓库成员 +gitlink-cli repo +members --owner {OWNER} --repo {REPO} --limit 50 --format json + +# 获取 merged PR(统计贡献) +gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json + +# 获取 closed Issues(统计参与) +gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json +``` + +**步骤 2.2** — 计算每个成员的 AHP 分数: + +| 维度 | 权重 | 计算方式 | +|------|------|----------| +| Issues Created | 15% | 该成员创建的 Issue 数 / 最大值 | +| PRs Merged | 25% | 该成员合并的 PR 数 / 最大值 | +| Code Changes | 30% | 该成员代码变更行数 / 最大值 | +| Issue Comments | 15% | 该成员评论数 / 最大值 | +| Team Member | 15% | 是成员=1,非成员=0 | + +``` +Score = NI×15 + NM×25 + NL×30 + NC×15 + MS×15 +``` + +**步骤 2.3** — 筛选待分配 Issue 并分配: + +```bash +# 只分配 Bug 和 Feature 类型的 Issue +# Bug → 标签含 "bug"/"缺陷" +# Feature → 标签含 "feature"/"功能" + +# 按分数从高到低排序成员 +# Bug:优先分配给最高分成员(确保快速解决) +# Feature:按分数轮流分配(鼓励参与) + +# 分配命令 +gitlink-cli issue +batch-assign --owner {OWNER} --repo {REPO} --numbers {ISSUE_NUMBERS} --assignee {LOGIN} +``` + +**分配策略**: +- Bug 类型:优先分配给贡献度最高的成员(快速响应) +- Feature 类型:按贡献度轮流分配(鼓励更多人参与) +- 成员不足时用 Round-robin 补充 + +### 阶段 3:周报生成 + +**步骤 3.1** — 收集数据: + +```bash +# Closed issues +gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json + +# Merged PRs +gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json +``` + +**步骤 3.2** — 周报模板: + +```markdown +# Community Weekly Report: {WEEK_START} ~ {WEEK_END} + +## Summary +- New Issues: **{NEW_COUNT}** +- Closed Issues: **{CLOSED_COUNT}** +- Merged PRs: **{MERGED_COUNT}** + +## Issue Classification +| Type | Count | +|------|-------| +| Bug | {BUG_COUNT} | +| Feature | {FEATURE_COUNT} | +| Question | {QUESTION_COUNT} | +| Docs | {DOCS_COUNT} | + +## Highlights +- Auto-classified and labeled {TOTAL_CLASSIFIED} issues +- Assigned responsible persons for bug and feature issues + +--- +*Auto-generated by gitlink-cli community-ops workflow* +``` + +**步骤 3.3** — 发布到 Wiki: + +```bash +gitlink-cli wiki +create --owner {OWNER} --repo {REPO} --title "{REPORT_TITLE}" --content "{REPORT_BODY}" +``` + +### 阶段 4:Release Notes 生成 + +**引用 [`../workflow-release-notes.md`](workflow-release-notes.md)** + +Release Notes 的生成遵循 Release Notes 工作流的规范: + +1. **收集数据**:获取 commits、merged PR、closed Issue +2. **分类整理**:按类型归类(新功能/Bug修复/改进/破坏性变更) +3. **生成发布**:套用模板生成 Notes,创建 Release + +**快捷命令**: + +```bash +gitlink-cli release +create --tag {TAG} --name "{NAME}" --body "{NOTES}" +``` + +## 完整流程图 + +``` +Issue 分类 → 负责人分配 → 周报生成 → Release Notes + ↓ ↓ ↓ ↓ + 添加标签 API PATCH Wiki 发布 Release 创建 +``` + +## 使用示例 + +### AI 交互式 + +用户说:"帮我跑一下社区运营" + +AI 应该: +1. 确认 owner/repo(自动从 git remote 解析或询问用户) +2. 依次执行四个阶段 +3. 展示每阶段的结果 +4. 询问是否需要调整 + +## 注意事项 + +- Issue 分类基于关键词匹配,可能不准确,建议人工审核 +- 负责人分配只针对 Bug 和 Feature 类型 +- 周报数据基于时间范围筛选,不是所有 open issues +- Release Notes 生成引用 changelog 工作流的规范 + +## References + +- [workflow-release-notes](workflow-release-notes.md) — Release Notes 生成 +- [workflow-issue-triage](workflow-issue-triage.md) — Issue 分类详情 +- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作 +- [gitlink-wiki](../../gitlink-wiki/SKILL.md) — Wiki 操作 +- [gitlink-release](../../gitlink-release/SKILL.md) — Release 操作 diff --git a/skills/gitlink-workflow/references/workflow-contributor-growth.md b/skills/gitlink-workflow/references/workflow-contributor-growth.md new file mode 100644 index 00000000..ab02d164 --- /dev/null +++ b/skills/gitlink-workflow/references/workflow-contributor-growth.md @@ -0,0 +1,245 @@ +# Workflow: Contributor Growth(贡献者成长体系) + +> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。 +> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于评估和激励团队贡献者。 + +AI Agent 自动收集贡献者数据,使用 AHP 权重模型计算贡献分数,生成排行榜和可视化报告,并可选择颁发 Badge。 + +## 工作流概述 + +Contributor Growth 工作流通过收集 Issue、PR、代码变更、评论等数据,使用 AHP(层次分析法)权重模型计算每个贡献者的综合分数,生成排行榜、HTML 报告和 Wiki 页面,并可自动颁发 Badge。 + +## 适用场景 + +- **贡献评估**:量化团队成员的贡献程度 +- **排行榜生成**:生成贡献者排行榜 +- **激励机制**:通过 Badge 颁发激励贡献者 +- **团队协调**:了解团队成员的参与度 + +## 触发词 + +- "贡献者" / "contributor" +- "排行榜" / "leaderboard" +- "badge" / "徽章" +- "成长" / "growth" +- "评分" / "score" + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--owner` | 否 | 仓库所有者(自动从 git remote 解析) | +| `--repo` | 否 | 仓库名称(自动从 git remote 解析) | +| `--sample` | 否 | 采样 PR 数量获取代码统计(默认 10) | +| `--award` | 否 | 自动创建 Badge 颁发 Issue | + +## AHP 评分模型 + +| 维度 | 权重 | 数据来源 | 提取字段 | +|------|------|----------|----------| +| Issues Created | 15% | `issue +list` (open + closed) | `.data.issues[].author.login` 按作者统计 | +| PRs Merged | 25% | `pr +list state=merged` | `.data.issues[].author_login` 按作者统计 | +| Code Changes | 30% | `pr +files`(采样) | `.data.files[].addition` / `.deletion`(单数) | +| Issue Comments | 15% | `issue +list` 直接提取 | `.data.issues[].comment_journals_count` | +| Team Member | 15% | `repo +members` | `.data.members[].login` 判断是否成员 | + +## Badge 等级 + +| Badge | 分数要求 | 说明 | +|-------|----------|------| +| Champion | >= 80 | 卓越贡献 | +| Core Contributor | >= 60 | 核心贡献者 | +| Active Contributor | >= 40 | 活跃贡献者 | +| Contributor | >= 20 | 贡献者 | +| Newcomer | < 20 | 新人 | + +## 工作流步骤 + +### 步骤 1:收集数据 + +```bash +# Open Issues +gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state open --limit 100 --format json + +# Closed Issues +gitlink-cli issue +list --owner {OWNER} --repo {REPO} --state closed --limit 100 --format json + +# Merged PRs +gitlink-cli pr +list --owner {OWNER} --repo {REPO} --state merged --limit 100 --format json + +# 仓库成员 +gitlink-cli repo +members --owner {OWNER} --repo {REPO} --limit 100 --format json +``` + +### 步骤 2:构建贡献者数据 + +遍历数据,为每个贡献者统计: +- Issues:创建的 Issue 数量 +- Merged:合并的 PR 数量 +- Additions/Deletions:代码变更行数(采样) +- Comments:评论数量(采样) + +### 步骤 3:采样代码变更 + +对 merged PR 采样,获取代码变更统计: + +```bash +# 获取 PR 变更文件 +gitlink-cli pr +files --owner {OWNER} --repo {REPO} --id {PR_ID} --format json +``` + +提取字段:`.data.files[].addition`、`.data.files[].deletion`(**注意:是单数形式,不是 additions/deletions**) + +**已知问题**: +- `pr +files` 输出的 JSON 在中文 Windows 下可能因编码问题导致解析失败(GBK vs UTF-8) +- 如果 Python 解析报错 `JSONDecodeError` 或 `UnicodeDecodeError`,需要指定 `encoding='utf-8'` +- 部分 PR 的 files API 可能返回异常,用 try/except 跳过即可 +- 降级方案:用 PR 数量估算代码变更量(每个 PR 估 100 行) + +### 步骤 4:采样评论数量 + +有两种方式获取评论数据: + +**方式 A(推荐)**:直接从 `issue +list` 返回的数据中提取 +- 字段:`.data.issues[].comment_journals_count` +- 无需额外 API 调用,效率更高 + +**方式 B**:逐个 Issue 采样 +```bash +gitlink-cli issue +view --owner {OWNER} --repo {REPO} --number {ISSUE_ID} --format json +``` +- 字段:`.data.comment_journals_count` + +**补充**:PR 的评论数可从 `pr +list` 返回的 `.data.issues[].journals_count` 获取 + +### 步骤 5:计算 AHP 分数 + +```bash +# 归一化处理 +NI = Issues / MaxIssues +NM = Merged / MaxMerged +NL = Lines / MaxLines +NC = Comments / MaxComments +MS = IsMember ? 1 : 0 + +# 加权求和 +Score = NI * 15 + NM * 25 + NL * 30 + NC * 15 + MS * 15 +``` + +### 步骤 6:生成排行榜 + +输出格式: + +``` +Rank Contributor Issues Merged +/- Lines Comments Score Badge +---- ------------------ ------ ------ --------- -------- ------ ------------- +1 @contributor1 15 8 +1200/-300 45 82.5 Champion +2 @contributor2 10 12 +800/-200 30 68.2 Core Contributor +3 @contributor3 5 6 +400/-100 20 45.1 Active Contributor +``` + +### 步骤 7:生成 HTML 报告 + +HTML 报告包含: +- 汇总统计卡片 +- ECharts 饼图(分数分布) +- 详细排行榜表格 +- AHP 权重说明 + +### 步骤 8:发布到 Wiki + +```bash +gitlink-cli wiki +create \ + --owner {OWNER} \ + --repo {REPO} \ + --title "Contributor Leaderboard {DATE}" \ + --content "{WIKI_CONTENT}" +``` + +Wiki 内容包含: +- 评分体系说明 +- 排行榜表格 +- 生成时间 + +### 步骤 9:颁发 Badge(可选) + +如果指定了 `--award` 参数,为获奖者创建 Issue: + +```bash +gitlink-cli issue +create \ + --owner {OWNER} \ + --repo {REPO} \ + --title "Badge Award: {BADGE}" \ + --body "{ISSUE_BODY}" + +gitlink-cli issue +label-add \ + --owner {OWNER} \ + --repo {REPO} \ + --number {ISSUE_ID} \ + --labels badge +``` + +## 输出示例 + +### 控制台输出 + +``` +====== Contributor Growth System: zzx-coder/gitlink-cli ====== + +[STEP] Collecting data... +[ OK] Issues(open:15 closed:30) PRs(merged:25) Members:8 + +[STEP] Building contributor profiles... +[STEP] Analyzing PR code changes (sampling 10)... +[STEP] Sampling issue comments... + +[STEP] Calculating scores... + +====== Contributor Rankings ====== + +Rank Contributor Issues Merged +/- Lines Comments Score Badge +---- ------------------ ------ ------ --------- -------- ------ ------------- +1 @zzx-coder 12 8 +1500/-400 35 85.2 Champion +2 @contributor1 8 6 +800/-200 25 62.1 Core Contributor +3 @contributor2 5 4 +400/-100 15 42.3 Active Contributor + +====== Generating HTML Report ====== +[ OK] HTML report: contrib-report-zzx-coder-gitlink-cli.html + +[STEP] Publishing to Wiki... +[ OK] Published to Wiki: Contributor Leaderboard 2026-07-02 + +====== Awarding Badges ====== +[ OK] Badge issue created: #45 - Badge Award: Champion (1 recipients) +[ OK] Badge issue created: #46 - Badge Award: Core Contributor (1 recipients) + +====== Complete ====== + + Contributors: 8 + HTML Report: contrib-report-zzx-coder-gitlink-cli.html + Wiki: Contributor Leaderboard 2026-07-02 + Badges: Awarded +``` + +## 注意事项 + +- 代码变更统计基于采样,不是全量数据 +- Badge 颁发会创建 issue,需要确认权限 +- HTML 报告使用 ECharts 需要网络连接 +- 建议定期运行(如每月)跟踪贡献趋势 + +## 已知问题与解决方案 + +| 问题 | 原因 | 解决方案 | +|------|------|----------| +| Python `UnicodeDecodeError: 'gbk' codec` | 中文 Windows 默认 GBK 编码,gitlink-cli 输出 UTF-8 | `open(file, encoding='utf-8')` | +| `pr +files` JSON 解析失败 | 输出含特殊字符,GBK 解码破坏 UTF-8 序列 | 用 `encoding='utf-8'` 读取,或用 try/except 跳过 | +| `pr +files` 字段名为 `addition` 不是 `additions` | GitLink API 使用单数形式 | 用 `f.get('addition', f.get('additions', 0))` 兼容 | +| Issue 数据中 `comment_journals_count` 为 0 | 某些 Issue 确实没有评论 | 正常现象,不影响评分 | + +## References + +- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作 +- [gitlink-pr](../../gitlink-pr/SKILL.md) — PR 操作 +- [gitlink-repo](../../gitlink-repo/SKILL.md) — 仓库操作 +- [gitlink-wiki](../../gitlink-wiki/SKILL.md) — Wiki 操作 diff --git a/skills/gitlink-workflow/references/workflow-multi-repo.md b/skills/gitlink-workflow/references/workflow-multi-repo.md new file mode 100644 index 00000000..e8830e93 --- /dev/null +++ b/skills/gitlink-workflow/references/workflow-multi-repo.md @@ -0,0 +1,199 @@ +# Workflow: Multi-Repo Collaboration(多仓库协同) + +> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。 +> **AI Agent 工作流**:此工作流专为 AI Agent 设计,用于跨仓库协同管理。 + +AI Agent 自动汇总组织下所有仓库的 Issue、PR、Release 状态,生成可视化 Dashboard,支持协同发版。 + +## 工作流概述 + +Multi-Repo Collaboration 工作流通过遍历组织下的所有仓库,收集各仓库的 Issue、PR、Release 数据,生成统一的状态 Dashboard,并支持跨仓库协同发版。 + +## 适用场景 + +- **状态总览**:查看组织下所有仓库的健康状态 +- **跨仓库追踪**:追踪跨仓库的 Issue 和 PR +- **协同发版**:多个仓库同时发布同一版本 +- **团队协调**:协调团队在多个项目间的工作 + +## 触发词 + +- "多仓库" / "multi repo" +- "协同" / "collaboration" +- "dashboard" / "看板" +- "组织仓库" / "org repos" + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--org` | 是 | 组织名称 | +| `--repos` | 否 | 指定仓库列表(逗号分隔),默认遍历所有仓库 | +| `--release` | 否 | 协同发版的版本号 | +| `--output` | 否 | Dashboard 输出文件(默认 dashboard.html) | + +## 工作流步骤 + +### 步骤 1:列出组织仓库 + +```bash +# 获取组织下所有仓库 +gitlink-cli repo +list --user {ORG} --limit 100 --format json +``` + +提取仓库列表:`.data.projects[]` 或 `.data[]`,字段:`.name` 或 `.identifier` + +### 步骤 2:收集各仓库数据 + +遍历每个仓库,收集 Issue、PR、Release 数据: + +```bash +# Open Issues +gitlink-cli issue +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json + +# Closed Issues +gitlink-cli issue +list --owner {ORG} --repo {REPO} --state closed --limit 50 --format json + +# Open PRs +gitlink-cli pr +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json + +# Merged PRs +gitlink-cli pr +list --owner {ORG} --repo {REPO} --state merged --limit 50 --format json + +# Latest Release +gitlink-cli release +list --owner {ORG} --repo {REPO} --limit 1 --format json +``` + +### 步骤 3:生成 Dashboard + +Dashboard 包含: +- **汇总卡片**:总仓库数、Open Issues、Open PRs、总活动量 +- **仓库状态表**:每个仓库的 Issue/PR/Release 状态和健康度 + +**健康度判断**: +- Open Issues <= 10 → Healthy(绿色) +- Open Issues 11-20 → Moderate(橙色) +- Open Issues > 20 → Needs Attention(红色) + +### 步骤 4:协同发版(可选) + +如果指定了 `--release` 参数,为所有仓库创建 Release: + +```bash +gitlink-cli release +create \ + --owner {ORG} \ + --repo {REPO} \ + --tag {VERSION} \ + --name "Release {VERSION}" \ + --body "Coordinated release {VERSION} for {REPO}" +``` + +## 数据提取 + +### Issue 数量 + +```bash +# Open Issues 数量 +OPEN_COUNT=$(gitlink-cli issue +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json | jq '.data.issues | length') + +# Closed Issues 数量 +CLOSED_COUNT=$(gitlink-cli issue +list --owner {ORG} --repo {REPO} --state closed --limit 50 --format json | jq '.data.issues | length') +``` + +### PR 数量 + +```bash +# Open PRs 数量 +OPEN_PRS=$(gitlink-cli pr +list --owner {ORG} --repo {REPO} --state open --limit 50 --format json | jq '.data.issues | length') + +# Merged PRs 数量 +MERGED_PRS=$(gitlink-cli pr +list --owner {ORG} --repo {REPO} --state merged --limit 50 --format json | jq '.data.issues | length') +``` + +### Release 状态 + +```bash +# 最新 Release +LATEST_RELEASE=$(gitlink-cli release +list --owner {ORG} --repo {REPO} --limit 1 --format json | jq -r '.data.releases[0].tag_name // "none"') +``` + +## 输出示例 + +### Dashboard HTML + +```html + + + + Multi-Repo Collaboration Dashboard + + + +

Multi-Repo Collaboration Dashboard

+
+
Total Repos: 10
+
Open Issues: 45
+
Open PRs: 12
+
Total Activity: 156
+
+ + + + + + + + + + + + + + + +
RepositoryOpen IssuesClosed IssuesOpen PRsMerged PRsLatest ReleaseHealth
+ + +``` + +### 控制台输出 + +``` +====== Multi-Repo Collaboration Dashboard ====== + +[STEP] Fetching repositories for org: myorg... +[ OK] Found 10 repositories + +[STEP] Processing repo1... +[ OK] repo1 : Issues(open:5 closed:12) PRs(open:2 merged:8) Release:v1.2.0 + +[STEP] Processing repo2... +[ OK] repo2 : Issues(open:15 closed:20) PRs(open:5 merged:15) Release:v2.0.0 + +====== Generating Dashboard ====== +[ OK] Dashboard saved to: dashboard.html + +====== Multi-Repo Dashboard Complete ====== + + Repos processed: 10 + Total issues: 156 (open: 45) + Total PRs: 89 (open: 12) + Dashboard: dashboard.html +``` + +## 注意事项 + +- 大量仓库时注意 API 限流,建议添加延时 +- Release 状态可能需要二次验证(GitLink API bug) +- Dashboard HTML 文件可以用浏览器打开查看 +- 协同发版前建议先预览各仓库状态 + +## References + +- [gitlink-repo](../../gitlink-repo/SKILL.md) — 仓库操作 +- [gitlink-issue](../../gitlink-issue/SKILL.md) — Issue 操作 +- [gitlink-pr](../../gitlink-pr/SKILL.md) — PR 操作 +- [gitlink-release](../../gitlink-release/SKILL.md) — Release 操作 +- [gitlink-org](../../gitlink-org/SKILL.md) — 组织管理 diff --git a/skills/gitlink-workflow/references/workflow-release-notes.md b/skills/gitlink-workflow/references/workflow-release-notes.md index 91d34386..0f0a99a1 100644 --- a/skills/gitlink-workflow/references/workflow-release-notes.md +++ b/skills/gitlink-workflow/references/workflow-release-notes.md @@ -140,106 +140,6 @@ done)" gitlink-cli release +create --tag $NEW_TAG --name "$NEW_TAG" --body "$RELEASE_NOTES" ``` -## 完整工作流示例 - -```bash -#!/bin/bash - -# Release Notes 自动生成脚本 - -OWNER="myuser" -REPO="myproject" -NEW_VERSION=$1 - -if [ -z "$NEW_VERSION" ]; then - echo "使用方法: $0 " - exit 1 -fi - -echo "为版本 $NEW_VERSION 生成 Release Notes..." - -# 1. 获取上一个版本 -PREV_VERSION=$(gitlink-cli release +list --owner $OWNER --repo $REPO --format json | \ - jq -r '.data.releases[0].tag_name') - -echo "上一个版本: $PREV_VERSION" -echo "新版本: $NEW_VERSION" - -# 2. 获取提交比较 -COMPARE_DATA=$(gitlink-cli api GET "/$OWNER/$REPO/compare/$PREV_VERSION...$NEW_VERSION" --format json) -COMMITS=$(echo "$COMPARE_DATA" | jq -r '.data.commits') - -# 3. 获取已关闭的 Issue -ISSUES=$(gitlink-cli issue +list --owner $OWNER --repo $REPO --state closed --format json | \ - jq '.data.issues[]') - -# 4. 获取已合并的 PR -PRS=$(gitlink-cli pr +list --owner $OWNER --repo $REPO --state merged --format json | \ - jq '.data.prs[]') - -# 5. 分析变更数据 -FEATURE_COUNT=0 -BUG_FIX_COUNT=0 -ENHANCEMENT_COUNT=0 -BREAKING_COUNT=0 - -# 分析 Issue -FEATURES=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "feature") | "- \(.subject) (#\(.id))"') -BUG_FIXES=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "bug") | "- \(.subject) (#\(.id))"') -ENHANCEMENTS=$(echo "$ISSUES" | jq -r 'select(.issue_tags[]?.name == "enhancement") | "- \(.subject) (#\(.id))"') - -# 分析提交中的破坏性变更 -BREAKING_CHANGES=$(echo "$COMMITS" | jq -r 'select(.commit.message | contains("BREAKING")) | "- \(.commit.message | split("\n")[0])"') - -# 统计数量 -FEATURE_COUNT=$(echo "$FEATURES" | grep -c "^-" || echo "0") -BUG_FIX_COUNT=$(echo "$BUG_FIXES" | grep -c "^-" || echo "0") -ENHANCEMENT_COUNT=$(echo "$ENHANCEMENTS" | grep -c "^-" || echo "0") -BREAKING_COUNT=$(echo "$BREAKING_CHANGES" | grep -c "^-" || echo "0") - -# 6. 生成 Release Notes -RELEASE_NOTES="# 🎉 Release $NEW_VERSION - -## 📊 变更统计 -- **新功能**: $FEATURE_COUNT 个 -- **Bug 修复**: $BUG_FIX_COUNT 个 -- **功能改进**: $ENHANCEMENT_COUNT 个 -- **破坏性变更**: $BREAKING_COUNT 个 - -## ✨ 新功能 -$FEATURES - -## 🐛 Bug 修复 -$BUG_FIXES - -## 🔧 功能改进 -$ENHANCEMENTS - -## ⚠️ 破坏性变更 -$BREAKING_CHANGES - -## 🙏 贡献者 -感谢所有参与此版本开发的贡献者! - -## 📥 安装 -\`\`\`bash -npm install $OWNER/$REPO@$NEW_VERSION -\`\`\` - -## 📚 文档 -完整文档请查看: https://www.gitlink.org.cn/$OWNER/$REPO/wiki - ---- -**完整变更日志**: https://www.gitlink.org.cn/$OWNER/$REPO/compare/$PREV_VERSION...$NEW_VERSION" - -# 7. 创建 Release -echo "创建 Release $NEW_VERSION..." -gitlink-cli release +create --owner $OWNER --repo $REPO \ - --tag $NEW_VERSION --name "$NEW_VERSION" --body "$RELEASE_NOTES" - -echo "Release $NEW_VERSION 创建完成!" -``` - ## AI Agent 集成示例 Claude Code 等 AI Agent 可以深度集成此工作流: diff --git a/workflows/01-community-ops.ps1 b/workflows/01-community-ops.ps1 index a7ec5f21..23c99591 100644 --- a/workflows/01-community-ops.ps1 +++ b/workflows/01-community-ops.ps1 @@ -1,15 +1,6 @@ -# ---------------------------------------------------------------- +# ---------------------------------------------------------------- # Scenario 1: Community Operations Automation # Flow: Issue auto-classify -> Assign responsible -> Weekly report -> Release notes -# -# Commands chained: -# 1. issue +list -- fetch open issues -# 2. issue +label-add -- add classification labels -# 3. repo +members -- get repo members -# 4. api PATCH -- assign responsible person -# 5. pr +list -- collect merged PRs for weekly report -# 6. wiki +create -- publish community weekly report -# 7. release +create -- publish release notes # ---------------------------------------------------------------- #Requires -Version 5.1 @@ -26,11 +17,6 @@ Import-Module "$PSScriptRoot/lib/common.psm1" -Force if ($Help) { Write-Host "Usage: powershell 01-community-ops.ps1 -Owner OWNER -Repo REPO [-WeeksAgo N] [-DryRun]" - Write-Host "" - Write-Host " -Owner OWNER Repository owner (org or user)" - Write-Host " -Repo REPO Repository name" - Write-Host " -WeeksAgo N Generate report for N weeks ago (default: 0 = this week)" - Write-Host " -DryRun Preview actions without executing" exit 0 } @@ -38,7 +24,6 @@ Check-Auth $r = Resolve-OwnerRepo $Owner $Repo $Owner = $r.Owner; $Repo = $r.Repo -# Classification keywords $BugKw = @('bug','error','crash','fault','fix') $FeatureKw = @('feature','enhancement','add','support','request') $QuestionKw = @('how','question','help') @@ -49,7 +34,7 @@ Log-Title "Phase 1: Issue Auto-Classification" # ---------------------------------------------------------------- Log-Step "Fetching open issues..." -$issuesJson = Invoke-GLCheck issue,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,100 +$issuesJson = Invoke-GLCheck "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100" if (-not $issuesJson) { Log-Err "Failed to fetch issues"; exit 1 } $issues = @($issuesJson.data.issues) @@ -60,7 +45,6 @@ $BugIds = @(); $FeatureIds = @(); $QuestionIds = @(); $DocsIds = @() if ($issueCount -gt 0) { Log-Step "Classifying issues by content..." - foreach ($issue in $issues) { $id = $issue.id $title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "" } @@ -96,7 +80,6 @@ if ($issueCount -gt 0) { Log-Info " Questions: $($QuestionIds.Count)" Log-Info " Docs: $($DocsIds.Count)" - # Apply labels $labelGroups = @( @{ Ids = $BugIds; Label = "bug" }, @{ Ids = $FeatureIds; Label = "feature" }, @@ -107,7 +90,7 @@ if ($issueCount -gt 0) { if ($g.Ids.Count -gt 0) { Log-Step "Labeling $($g.Label) issues..." foreach ($id in $g.Ids) { - Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$id,--labels,$g.Label | Out-Null + Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Repo, "--number", $id, "--labels", $g.Label | Out-Null } Log-Ok "Labeled $($g.Ids.Count) $($g.Label) issues" } @@ -119,7 +102,7 @@ Log-Title "Phase 2: Assign Responsible Persons" # ---------------------------------------------------------------- Log-Step "Fetching repo members..." -$membersJson = Invoke-GL repo,+members,--owner,$Owner,--repo,$Repo,--limit,50 +$membersJson = Invoke-GL "repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "50" $members = @() if ($membersJson) { $md = $membersJson.data @@ -141,7 +124,7 @@ if ($memberLogins.Count -gt 0) { foreach ($id in $assignIds) { $assignee = $memberLogins[$idx % $memberLogins.Count] $bodyJson = "{`"assigned_to_id`": `"$assignee`"}" - Invoke-GL api,PATCH,"/v1/$Owner/$Repo/issues/$id",--body,$bodyJson | Out-Null + Invoke-GL "api", "PATCH", "/v1/$Owner/$Repo/issues/$id", "--body", $bodyJson | Out-Null Log-Info " Assigned #$id -> @$assignee" $idx++ } @@ -160,38 +143,128 @@ $weekEnd = Get-DateToday Log-Step "Collecting weekly data (week of $weekStart)..." -$closedJson = Invoke-GL issue,+list,--owner,$Owner,--repo,$Repo,--state,closed,--limit,100 +$closedJson = Invoke-GL "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100" $closedCount = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 } -$mergedJson = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,merged,--limit,100 +$mergedJson = Invoke-GL "pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100" $mergedData = @() +$mergedCount = 0 if ($mergedJson) { $d = $mergedJson.data if ($d.issues) { $mergedData = @($d.issues) } elseif ($d.pulls) { $mergedData = @($d.pulls) } elseif ($d -is [array]) { $mergedData = $d } + $mergedCount = $mergedData.Count } -$mergedCount = $mergedData.Count $newIssuesCount = $issueCount $totalClassified = $BugIds.Count + $FeatureIds.Count + $QuestionIds.Count + $DocsIds.Count $reportTitle = "Community Weekly Report: $weekStart ~ $weekEnd" $reportBody = "# $reportTitle" + "`n`n" -$reportBody += "## Summary" + "`n" -$reportBody += "- New Issues: **$newIssuesCount**" + "`n" -$reportBody += "- Closed Issues: **$closedCount**" + "`n" -$reportBody += "- Merged PRs: **$mergedCount**" + "`n`n" -$reportBody += "## Issue Classification" + "`n" -$reportBody += "| Type | Count |" + "`n" -$reportBody += "|------|-------|" + "`n" +$reportBody += "## 概览" + "`n" +$reportBody += "- 仓库: **$Owner/$Repo**" + "`n" +$reportBody += "- 当前开放 Issue: **$newIssuesCount**" + "`n" +$reportBody += "- 已关闭 Issue: **$closedCount**" + "`n" +$reportBody += "- 已合并 PR: **$mergedCount**" + "`n`n" + +# 分类汇总 +$reportBody += "## Issue 分类汇总" + "`n" +$reportBody += "| 类型 | 数量 |" + "`n" +$reportBody += "|------|------|" + "`n" $reportBody += "| Bug | $($BugIds.Count) |" + "`n" $reportBody += "| Feature | $($FeatureIds.Count) |" + "`n" $reportBody += "| Question | $($QuestionIds.Count) |" + "`n" $reportBody += "| Docs | $($DocsIds.Count) |" + "`n`n" -$reportBody += "## Highlights" + "`n" -$reportBody += "- Auto-classified and labeled $totalClassified issues" + "`n" -$reportBody += "- Assigned responsible persons for bug and feature issues" + "`n`n" + +# 开放 Issue 清单(含分类标记) +$reportBody += "## 当前开放 Issue 清单" + "`n" +$reportBody += "| # | 标题 | 分类 | 创建时间 |" + "`n" +$reportBody += "|---|------|------|----------|" + "`n" +foreach ($issue in $issues) { + $id = $issue.id + $title = if ($issue.subject) { $issue.subject } elseif ($issue.title) { $issue.title } else { "-" } + $title = ($title -replace '\|', '\\|') + $cat = if ($BugIds -contains $id) { "Bug" } + elseif ($FeatureIds -contains $id) { "Feature" } + elseif ($QuestionIds -contains $id) { "Question" } + elseif ($DocsIds -contains $id) { "Docs" } + else { "-" } + $created = if ($issue.created_at) { $issue.created_at } else { "-" } + $reportBody += "| #$id | $title | $cat | $created |" + "`n" +} +$reportBody += "`n" + +# 已关闭 Issue 清单 +$reportBody += "## 近期已关闭 Issue" + "`n" +if ($closedCount -gt 0) { + $closedIssues = @($closedJson.data.issues) + $closedLimit = [Math]::Min($closedCount, 15) + $reportBody += "| # | 标题 |" + "`n" + $reportBody += "|---|------|" + "`n" + for ($i = 0; $i -lt $closedLimit; $i++) { + $it = $closedIssues[$i] + $cid = if ($it.id) { $it.id } elseif ($it.number) { $it.number } else { "-" } + $ctitle = if ($it.subject) { $it.subject } elseif ($it.title) { $it.title } else { "-" } + $ctitle = ($ctitle -replace '\|', '\\|') + $reportBody += "| #$cid | $ctitle |" + "`n" + } + if ($closedCount -gt $closedLimit) { + $reportBody += "| ... | 还有 $($closedCount - $closedLimit) 条 |`n" + } +} else { + $reportBody += "_本周无关闭记录_" + "`n" +} +$reportBody += "`n" + +# 已合并 PR 清单(含作者) +$reportBody += "## 近期已合并 PR" + "`n" +if ($mergedCount -gt 0) { + $prLimit = [Math]::Min($mergedCount, 15) + $reportBody += "| # | 标题 | 作者 |" + "`n" + $reportBody += "|---|------|------|" + "`n" + for ($i = 0; $i -lt $prLimit; $i++) { + $pr = $mergedData[$i] + $prId = if ($pr.id) { $pr.id } elseif ($pr.number) { $pr.number } else { "-" } + $ptitle = if ($pr.subject) { $pr.subject } elseif ($pr.title) { $pr.title } else { "-" } + $ptitle = ($ptitle -replace '\|', '\\|') + $pauthor = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { "-" } + $reportBody += "| #$prId | $ptitle | @$pauthor |" + "`n" + } + if ($mergedCount -gt $prLimit) { + $reportBody += "| ... | 还有 $($mergedCount - $prLimit) 条合并 PR |`n" + } +} else { + $reportBody += "_本周无合并记录_" + "`n" +} +$reportBody += "`n" + +# 贡献者排行(按合并 PR 数) +$reportBody += "## 贡献者排行(按合并 PR 数)" + "`n" +if ($mergedCount -gt 0) { + $contributorMap = @{} + foreach ($pr in $mergedData) { + $login = if ($pr.author -and $pr.author.login) { $pr.author.login } elseif ($pr.user -and $pr.user.login) { $pr.user.login } else { $null } + if ($login) { + if ($contributorMap.ContainsKey($login)) { $contributorMap[$login]++ } + else { $contributorMap[$login] = 1 } + } + } + $reportBody += "| 排名 | 贡献者 | 合并 PR 数 |" + "`n" + $reportBody += "|------|--------|------------|" + "`n" + $rank = 1 + foreach ($kv in ($contributorMap.GetEnumerator() | Sort-Object Value -Descending)) { + $reportBody += "| $rank | @$($kv.Name) | $($kv.Value) |" + "`n" + $rank++ + } +} else { + $reportBody += "_本周无合并记录_" + "`n" +} +$reportBody += "`n" + +$reportBody += "## 本周自动化执行" + "`n" +$reportBody += "- 自动分类并打标 Issue: **$totalClassified** 条" + "`n" +$reportBody += "- 已为 Bug/Feature 类 Issue 指派负责人" + "`n`n" $reportBody += "---" + "`n" $reportBody += "*Auto-generated by gitlink-cli community-ops workflow*" @@ -200,12 +273,8 @@ Write-Host "" Write-Host $reportBody Log-Step "Publishing weekly report to Wiki..." -$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Repo,--title,$reportTitle,--body,$reportBody -if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) { - Log-Ok "Weekly report published to Wiki" -} else { - Log-Warn "Wiki publish may have failed (wiki module might not be enabled)" -} +$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $reportTitle, "--content", $reportBody +if ($wikiResult -and $wikiResult.ok) { Log-Ok "Weekly report published to Wiki" } else { Log-Warn "Wiki publish may have failed" } # ---------------------------------------------------------------- Log-Title "Phase 4: Auto-Publish Release Notes" @@ -242,12 +311,8 @@ if ($closedCount -gt 0) { $releaseBody += "`n`n---`n*Auto-generated by gitlink-cli community-ops workflow*" Log-Step "Creating release: $tagName..." -$releaseResult = Invoke-GL release,+create,--owner,$Owner,--repo,$Repo,--tag,$tagName,--name,$releaseName,--body,$releaseBody -if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) { - Log-Ok "Release $tagName created successfully" -} else { - Log-Warn "Release creation may have failed (tag might already exist)" -} +$releaseResult = Invoke-GL "release", "+create", "--owner", $Owner, "--repo", $Repo, "--tag", $tagName, "--name", $releaseName, "--body", $releaseBody +if ($releaseResult -and $releaseResult.ok) { Log-Ok "Release $tagName created successfully" } else { Log-Warn "Release creation may have failed (tag might already exist)" } # ---------------------------------------------------------------- Log-Title "Community Operations Complete" diff --git a/workflows/03-project-init.ps1 b/workflows/03-project-init.ps1 index 327f480a..04ae95e5 100644 --- a/workflows/03-project-init.ps1 +++ b/workflows/03-project-init.ps1 @@ -1,4 +1,4 @@ -# ---------------------------------------------------------------- +# ---------------------------------------------------------------- # Scenario 3: One-Click Project Initialization # Flow: Input description -> Create repo -> README/CONTRIBUTING/CI config -> # Initial Issues -> Branch protection -> Initial Release @@ -53,7 +53,7 @@ Divider # -- Step 1: Create Repository -- Log-Step "Creating repository..." $privateStr = if ($Private) { "true" } else { "false" } -$repoResult = Invoke-GLCheck repo,+create,--owner,$Owner,--name,$Name,--description,$Description,--private,$privateStr +$repoResult = Invoke-GLCheck "repo", "+create", "--owner", $Owner, "--name", $Name, "--description", $Description, "--private", $privateStr if ($repoResult) { Log-Ok "Repository created: $Owner/$Name" } else { @@ -92,8 +92,8 @@ $readmeContent += "This project is licensed under the MIT License." $wikiOk = $false for ($attempt = 1; $attempt -le 3; $attempt++) { - $wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"README",--body,$readmeContent - if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) { + $wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "README", "--content", $readmeContent + if ($wikiResult -and (Get-JsonOk $wikiResult)) { Log-Ok "README created" $wikiOk = $true break @@ -127,8 +127,8 @@ $contribContent += "- Include environment details" $wikiOk = $false for ($attempt = 1; $attempt -le 3; $attempt++) { - $wikiContrib = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"CONTRIBUTING",--body,$contribContent - if ($wikiContrib -and (Get-JsonOk ($wikiContrib | ConvertFrom-Json))) { + $wikiContrib = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "CONTRIBUTING", "--content", $contribContent + if ($wikiContrib -and (Get-JsonOk $wikiContrib)) { Log-Ok "CONTRIBUTING guide created" $wikiOk = $true break @@ -150,7 +150,7 @@ $ciContent += "3. **Deploy**: Deploy to staging (master branch only)" + "`n`n" $ciContent += "### Configuration" + "`n`n" $ciContent += "Create a ``.gitlink-ci.yml`` file in the repository root." -Invoke-GL wiki,+create,--owner,$Owner,--repo,$Name,--title,"CI/CD Configuration",--body,$ciContent | Out-Null +Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Name, "--title", "CI/CD Configuration", "--content", $ciContent | Out-Null Log-Ok "CI/CD configuration guide created" # -- Step 5: Create Initial Issues -- @@ -165,13 +165,13 @@ $issuesToCreate = @( ) foreach ($entry in $issuesToCreate) { - $issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Name,--title,$entry.Title,--body,$entry.Body + $issueResult = Invoke-GL "issue", "+create", "--owner", $Owner, "--repo", $Name, "--title", $entry.Title, "--body", $entry.Body if ($issueResult) { try { - $issueJson = $issueResult | ConvertFrom-Json + $issueJson = $issueResult $issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null } if ($issueNum) { - Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Name,--number,$issueNum,--labels,$entry.Label | Out-Null + Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Name, "--number", $issueNum, "--labels", $entry.Label | Out-Null Log-Ok "Issue created: #$issueNum - $($entry.Title)" } } catch { @@ -182,8 +182,8 @@ foreach ($entry in $issuesToCreate) { # -- Step 6: Protect Default Branch -- Log-Step "Protecting master branch..." -$protectResult = Invoke-GL branch,+protect,--owner,$Owner,--repo,$Name,--name,master -if ($protectResult -and (Get-JsonOk ($protectResult | ConvertFrom-Json))) { +$protectResult = Invoke-GL "branch", "+protect", "--owner", $Owner, "--repo", $Name, "--name", "master" +if ($protectResult -and (Get-JsonOk $protectResult)) { Log-Ok "Branch 'master' protected" } else { Log-Warn "Branch protection may have failed (may require admin permissions)" @@ -206,8 +206,8 @@ $releaseBody += "- [ ] Complete documentation" + "`n" $releaseBody += "- [ ] First feature implementation" + "`n`n" $releaseBody += "---`n*Auto-initialized by gitlink-cli project-init workflow*" -$releaseResult = Invoke-GL release,+create,--owner,$Owner,--repo,$Name,--tag,"v0.1.0",--name,"Initial Release",--body,$releaseBody -if ($releaseResult -and (Get-JsonOk ($releaseResult | ConvertFrom-Json))) { +$releaseResult = Invoke-GL "release", "+create", "--owner", $Owner, "--repo", $Name, "--tag", "v0.1.0", "--name", "Initial Release", "--body", $releaseBody +if ($releaseResult -and (Get-JsonOk $releaseResult)) { Log-Ok "Release v0.1.0 created" } else { Log-Warn "Release creation may have failed" diff --git a/workflows/04-multi-repo-collab.ps1 b/workflows/04-multi-repo-collab.ps1 index 9e01e759..983a057b 100644 --- a/workflows/04-multi-repo-collab.ps1 +++ b/workflows/04-multi-repo-collab.ps1 @@ -1,4 +1,4 @@ -# ---------------------------------------------------------------- +# ---------------------------------------------------------------- # Scenario 4: Multi-Repo Collaboration # Flow: Cross-repo issue tracking -> PR status dashboard -> Coordinated release # @@ -36,7 +36,7 @@ Check-Auth Log-Title "Multi-Repo Collaboration Dashboard" Log-Step "Fetching repositories for org: $Org..." -$reposJson = Invoke-GLCheck repo,+list,--user,$Org,--limit,100 +$reposJson = Invoke-GLCheck "repo", "+list", "--user", $Org, "--limit", "100" if (-not $reposJson) { Log-Err "Failed to fetch repos"; exit 1 } $allRepos = @() @@ -69,13 +69,13 @@ foreach ($repo in $repoList) { Divider Log-Step "Processing $Org/$repo..." - $issuesJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50 + $issuesJson = Invoke-GL "issue", "+list", "--owner", $Org, "--repo", $repo, "--state", "open", "--limit", "50" $openIssues = if ($issuesJson) { @($issuesJson.data.issues).Count } else { 0 } - $closedJson = Invoke-GL issue,+list,--owner,$Org,--repo,$repo,--state,closed,--limit,50 + $closedJson = Invoke-GL "issue", "+list", "--owner", $Org, "--repo", $repo, "--state", "closed", "--limit", "50" $closedIssues = if ($closedJson) { @($closedJson.data.issues).Count } else { 0 } - $prsJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,open,--limit,50 + $prsJson = Invoke-GL "pr", "+list", "--owner", $Org, "--repo", $repo, "--state", "open", "--limit", "50" $openPRs = 0 if ($prsJson) { $pd = $prsJson.data @@ -84,7 +84,7 @@ foreach ($repo in $repoList) { elseif ($pd -is [array]) { $openPRs = $pd.Count } } - $mergedJson = Invoke-GL pr,+list,--owner,$Org,--repo,$repo,--state,merged,--limit,50 + $mergedJson = Invoke-GL "pr", "+list", "--owner", $Org, "--repo", $repo, "--state", "merged", "--limit", "50" $mergedPRs = 0 if ($mergedJson) { $md = $mergedJson.data @@ -93,7 +93,7 @@ foreach ($repo in $repoList) { elseif ($md -is [array]) { $mergedPRs = $md.Count } } - $releaseJson = Invoke-GL release,+list,--owner,$Org,--repo,$repo,--limit,1 + $releaseJson = Invoke-GL "release", "+list", "--owner", $Org, "--repo", $repo, "--limit", "1" $latestRelease = "none" if ($releaseJson -and $releaseJson.data.releases) { $releases = @($releaseJson.data.releases) @@ -186,8 +186,8 @@ if ($Release) { foreach ($repo in $repoList) { Log-Step "Creating release for $Org/$repo..." $relBody = "Coordinated release $Release for $Org/$repo" - $relResult = Invoke-GL release,+create,--owner,$Org,--repo,$repo,--tag,$Release,--name,"Release $Release",--body,$relBody - if ($relResult -and (Get-JsonOk ($relResult | ConvertFrom-Json))) { + $relResult = Invoke-GL "release", "+create", "--owner", $Org, "--repo", $repo, "--tag", $Release, "--name", "Release $Release", "--body", $relBody + if ($relResult -and (Get-JsonOk $relResult)) { Log-Ok "Release $Release created for $repo" } else { Log-Warn "Release creation failed for $repo (tag may already exist)" diff --git a/workflows/05-contributor-growth.ps1 b/workflows/05-contributor-growth.ps1 index bba7ef6b..30e02f8c 100644 --- a/workflows/05-contributor-growth.ps1 +++ b/workflows/05-contributor-growth.ps1 @@ -1,4 +1,4 @@ -# ---------------------------------------------------------------- +# ---------------------------------------------------------------- # Scenario 5: Contributor Growth System # Flow: Collect data -> Calculate scores -> Generate HTML -> Publish Wiki -> Award badges # @@ -54,12 +54,12 @@ Log-Title "Contributor Growth System: $Owner/$Repo" # -- Step 1: Collect Data -- Log-Step "Collecting data..." -$issuesOpen = Invoke-GLCheck issue,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,100 -$issuesClosed = Invoke-GL issue,+list,--owner,$Owner,--repo,$Repo,--state,closed,--limit,100 +$issuesOpen = Invoke-GLCheck "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100" +$issuesClosed = Invoke-GL "issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100" $openCount = if ($issuesOpen) { @($issuesOpen.data.issues).Count } else { 0 } $closedCount = if ($issuesClosed) { @($issuesClosed.data.issues).Count } else { 0 } -$prsMerged = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,merged,--limit,100 +$prsMerged = Invoke-GL "pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100" $prMergedData = @() if ($prsMerged) { $pd = $prsMerged.data @@ -69,7 +69,7 @@ if ($prsMerged) { } $prMergedCount = $prMergedData.Count -$membersJson = Invoke-GL repo,+members,--owner,$Owner,--repo,$Repo,--limit,100 +$membersJson = Invoke-GL "repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "100" $memberData = @() if ($membersJson) { $md = $membersJson.data @@ -119,7 +119,7 @@ for ($i = 0; $i -lt $prMergedCount; $i++) { $contribData[$author].Merged++ } if ($i -lt $prSample -and $prId -and $author) { - $filesJson = Invoke-GL pr,+files,--owner,$Owner,--repo,$Repo,--id,$prId + $filesJson = Invoke-GL "pr", "+files", "--owner", $Owner, "--repo", $Repo, "--id", $prId if ($filesJson -and $filesJson.data.files) { foreach ($f in $filesJson.data.files) { $add = if ($f.additions) { $f.additions } elseif ($f.addition) { $f.addition } else { 0 } @@ -148,7 +148,7 @@ if ($issuesOpen) { for ($i = 0; $i -lt $commentSample; $i++) { $id = $openIssuesArr[$i].id if (-not $id) { continue } - $detail = Invoke-GL issue,+view,--owner,$Owner,--repo,$Repo,--number,$id + $detail = Invoke-GL "issue", "+view", "--owner", $Owner, "--repo", $Repo, "--number", $id if ($detail) { $commentCount = if ($detail.data.comment_journals_count) { $detail.data.comment_journals_count } else { 0 } if ($commentCount -gt 0) { @@ -371,8 +371,8 @@ $wikiContent += $wikiRankRows + "`n" $wikiContent += "---" + "`n" $wikiContent += "*Auto-generated by gitlink-cli*" -$wikiResult = Invoke-GL wiki,+create,--owner,$Owner,--repo,$Repo,--title,$wikiTitle,--body,$wikiContent -if ($wikiResult -and (Get-JsonOk ($wikiResult | ConvertFrom-Json))) { +$wikiResult = Invoke-GL "wiki", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $wikiTitle, "--content", $wikiContent +if ($wikiResult -and (Get-JsonOk $wikiResult)) { Log-Ok "Published to Wiki: $wikiTitle" } else { Log-Warn "Wiki publish failed" @@ -406,13 +406,13 @@ if ($Award) { } $issueBody += "`n`n---`n*Auto-awarded by gitlink-cli contributor-growth workflow*" - $issueResult = Invoke-GL issue,+create,--owner,$Owner,--repo,$Repo,--title,$issueTitle,--body,$issueBody + $issueResult = Invoke-GL "issue", "+create", "--owner", $Owner, "--repo", $Repo, "--title", $issueTitle, "--body", $issueBody if ($issueResult) { try { - $issueJson = $issueResult | ConvertFrom-Json + $issueJson = $issueResult $issueNum = if ($issueJson.data.id) { $issueJson.data.id } elseif ($issueJson.data.number) { $issueJson.data.number } else { $null } if ($issueNum) { - Invoke-GL issue,+label-add,--owner,$Owner,--repo,$Repo,--number,$issueNum,--labels,"badge" | Out-Null + Invoke-GL "issue", "+label-add", "--owner", $Owner, "--repo", $Repo, "--number", $issueNum, "--labels", "badge" | Out-Null Log-Ok "Badge issue created: #$issueNum - $issueTitle ($($users.Count) recipients)" } } catch { diff --git a/workflows/SKILL.md b/workflows/SKILL.md new file mode 100644 index 00000000..deae11a9 --- /dev/null +++ b/workflows/SKILL.md @@ -0,0 +1,145 @@ +--- +name: gitlink-workflows +version: 1.1.0 +description: "GitLink 自动化工作流总入口:提供社区运营、代码审查、项目初始化、多仓库协同、贡献者成长、Release Notes、科研辅助等自动化功能的选择菜单。" +metadata: + requires: + bins: ["gitlink-cli"] + triggers: + - "工作流" + - "workflow" + - "自动化" + - "帮我跑" + - "执行" + - "科研" + - "research" + - "论文" + - "citation" + - "知识图谱" + - "knowledge graph" + - "热点追踪" + - "合规检查" + - "复现性" + - "reproducibility" + - "协作匹配" + - "进度跟踪" + - "项目洞察" + - "引用格式" +--- + +# gitlink-workflows(自动化工作流总入口) + +**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。** + +> **前置条件:** 先阅读 [`../skills/gitlink-shared/SKILL.md`](../skills/gitlink-shared/SKILL.md) 了解认证和全局参数。 + +## 功能菜单 + +当用户说"工作流"、"自动化"、"帮我跑"等触发词时,展示以下菜单让用户选择。 + +当用户说"科研"、"论文"、"知识图谱"、"合规检查"等触发词时,直接跳转到科研辅助菜单。 + +``` +====== GitLink 自动化工作流 ====== + +请选择要执行的工作流: + + 1. 社区运营自动化 + → Issue 自动分类、负责人分配、周报生成、Release Notes + + 2. 代码质量审查 + → PR Review、AI 四维度评分、自动合并 + + 3. 项目一键初始化 + → 创建仓库、README、CI 配置、初始 Issues、分支保护 + + 4. 多仓库协同 + → 跨仓库 Issue/PR 追踪、状态 Dashboard、协同发版 + + 5. 贡献者成长体系 + → 数据收集、AHP 评分、排行榜、Wiki 发布、Badge 颁发 + + 6. Release Notes 生成 + → 收集 commits/PR/Issue、分类整理、生成 Notes + + 7. 科研辅助系统 🆕 + → 项目洞察、热点追踪、合规复现、协作匹配、进度预警、论文引用 + +请输入编号(1-7)或功能名称: +``` + +## 用户输入 → 工作流映射 + +| 用户输入 | 执行的工作流 | +|----------|--------------| +| `1` 或 "社区运营" | 读取 `workflow-community-ops.md` | +| `2` 或 "代码审查" | 读取 `workflow-pr-review.md` | +| `3` 或 "项目初始化" | 读取 `workflow-repo-setup.md` | +| `4` 或 "多仓库" | 读取 `workflow-multi-repo.md` | +| `5` 或 "贡献者" | 读取 `workflow-contributor-growth.md` | +| `6` 或 "Release Notes" | 读取 `workflow-release-notes.md` | +| `7` 或 "科研" / "科研辅助" | 读取 `gitlink-research` Skill → 展示科研辅助菜单 | + +## 执行流程 + +1. **展示菜单** — 列出所有可用工作流 +2. **获取用户选择** — 用户输入编号或功能名称 +3. **读取 Reference** — 根据选择读取对应的 reference 文件 +4. **确认参数** — 询问必要的参数(owner/repo 等) +5. **执行工作流** — 按照 reference 文件的步骤执行 +6. **展示结果** — 输出执行结果和摘要 + +## 工作流 Reference 文件 + +所有工作流的详细步骤在以下 reference 文件中: + +| 工作流 | Reference 文件 | +|--------|----------------| +| 社区运营 | [`workflow-community-ops.md`](../skills/gitlink-workflow/references/workflow-community-ops.md) | +| 代码审查 | [`workflow-pr-review.md`](../skills/gitlink-workflow/references/workflow-pr-review.md) | +| 项目初始化 | [`workflow-repo-setup.md`](../skills/gitlink-workflow/references/workflow-repo-setup.md) | +| 多仓库协同 | [`workflow-multi-repo.md`](../skills/gitlink-workflow/references/workflow-multi-repo.md) | +| 贡献者成长 | [`workflow-contributor-growth.md`](../skills/gitlink-workflow/references/workflow-contributor-growth.md) | +| Release Notes | [`workflow-release-notes.md`](../skills/gitlink-workflow/references/workflow-release-notes.md) | +| 科研辅助 | [`SKILL.md`](../skills/gitlink-research/SKILL.md) — GitLink 科研辅助系统总入口 | + +## 快捷触发 + +用户也可以直接说特定意图,跳过菜单直接执行: + +| 用户说的话 | 直接执行 | +|------------|----------| +| "帮我跑一下社区运营" | `workflow-community-ops` | +| "审查一下这个 PR" | `workflow-pr-review` | +| "创建一个新项目" | `workflow-repo-setup` | +| "看看组织下所有仓库" | `workflow-multi-repo` | +| "生成贡献者排行榜" | `workflow-contributor-growth` | +| "生成 Release Notes" | `workflow-release-notes` | +| "分析这个仓库的科研价值" | `gitlink-research` 场景 1 | +| "帮我追踪 NLP 热点" | `gitlink-research` 场景 2 | +| "检查项目的可复现性" | `gitlink-research` 场景 3 | +| "找科研合作者" | `gitlink-research` 场景 4 | +| "看看项目进度有没有风险" | `gitlink-research` 场景 5 | +| "生成这个项目的论文引用" | `gitlink-research` 场景 6 | + +## 参数说明 + +| 参数 | 说明 | 获取方式 | +|------|------|----------| +| `--owner` | 仓库所有者 | 自动从 git remote 解析,或询问用户 | +| `--repo` | 仓库名称 | 自动从 git remote 解析,或询问用户 | +| `--org` | 组织名称 | 询问用户(多仓库协同时需要) | + +## 注意事项 + +- 所有写入操作前必须确认用户意图 +- 自动从 git remote 解析 owner/repo,解析失败时询问用户 +- 每个工作流的具体步骤见对应的 reference 文件 +- 支持 `--dry-run` 预览模式(部分工作流) + +## References + +- [gitlink-shared](../skills/gitlink-shared/SKILL.md) — 认证和全局参数 +- [gitlink-workflow](../skills/gitlink-workflow/SKILL.md) — AI 工作流详情 +- [gitlink-changelog](../skills/gitlink-changelog/SKILL.md) — Release Notes 生成 +- [gitlink-research](../skills/gitlink-research/SKILL.md) — GitLink 科研辅助系统(6 大场景) diff --git a/workflows/academic/06-research-insights.ps1 b/workflows/academic/06-research-insights.ps1 new file mode 100644 index 00000000..07ece166 --- /dev/null +++ b/workflows/academic/06-research-insights.ps1 @@ -0,0 +1,92 @@ +# GitLink 科研辅助 — 场景 1:仓库级科研项目洞察 (PowerShell) +param( + [string]$Owner, [string]$Repo, [string]$Output = "", + [switch]$NoWiki, [switch]$DryRun +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module "$ScriptDir\lib\common.psm1" -Force + +$ErrorActionPreference = "Continue" +Check-Auth +$resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo +$Owner = $resolved.Owner; $Repo = $resolved.Repo +$Today = Get-Date -Format "yyyy-MM-dd" +$OutputDir = Join-Path $ScriptDir "..\output" +if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null } +$OutputFile = if ($Output) { $Output } else { Join-Path $OutputDir "research-insights-${Repo}-${Today}.html" } + +Log-Title "GitLink 科研辅助 — 仓库级项目洞察" + +# 1. Repo metadata +Log-Step "1/6 获取仓库元数据..." +$repoJson = Invoke-GLCheck @("repo", "+info", "--owner", $Owner, "--repo", $Repo) +$repoName = $repoJson.data.name ?? $repoJson.data.full_name ?? $Repo +$repoDesc = $repoJson.data.description ?? "No description" +$repoLang = $repoJson.data.language ?? "Unknown" +$stars = [int]($repoJson.data.stars_count ?? $repoJson.data.stars ?? 0) +$forks = [int]($repoJson.data.forks_count ?? $repoJson.data.forks ?? 0) +$openIssues = [int]($repoJson.data.open_issues_count ?? 0) +$updatedAt = $repoJson.data.updated_at ?? "" + +Log-Info " 名称: $repoName | 语言: $repoLang | Stars: $stars" + +# 2. Issues +Log-Step "2/6 收集 Issue 数据..." +$openIssuesJson = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100") +$closedIssuesJson = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100") +$totalOpen = if ($openIssuesJson.ok) { @($openIssuesJson.data.issues ?? $openIssuesJson.data).Count } else { 0 } +$totalClosed = if ($closedIssuesJson.ok) { @($closedIssuesJson.data.issues ?? $closedIssuesJson.data).Count } else { 0 } + +# 3. PRs +Log-Step "3/6 收集 PR 数据..." +$mergedPrsJson = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100") +$openPrsJson = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "50") +$totalMerged = if ($mergedPrsJson.ok) { @($mergedPrsJson.data.issues ?? $mergedPrsJson.data.pulls ?? $mergedPrsJson.data).Count } else { 0 } +$totalOpenPrs = if ($openPrsJson.ok) { @($openPrsJson.data.issues ?? $openPrsJson.data.pulls ?? $openPrsJson.data).Count } else { 0 } + +# 4. Releases +Log-Step "4/6 收集 Release 数据..." +$releasesJson = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "20") +$releaseCount = if ($releasesJson.ok) { @($releasesJson.data).Count } else { 0 } + +# 5. CI +Log-Step "5/6 收集 CI 数据..." +$ciJson = Invoke-GL @("ci", "+builds", "--owner", $Owner, "--repo", $Repo, "--limit", "20") +$ciBuilds = if ($ciJson.ok) { @($ciJson.data).Count } else { 0 } + +# 6. Members +Log-Step "6/6 获取贡献者..." +$membersJson = Invoke-GL @("repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "50") +$memberCount = if ($membersJson.ok) { + $d = if ($membersJson.data.members) { $membersJson.data.members } else { $membersJson.data } + if ($d -is [array]) { $d.Count } else { 0 } +} else { 0 } + +# Hotness calculation +$starsNorm = [Math]::Min($stars / 1000 * 100, 100) +$forksNorm = [Math]::Min($forks / 200 * 100, 100) +$issuesScore = [Math]::Min($totalOpen / 50 * 100, 100) +$prsScore = [Math]::Min(($totalMerged + $totalOpenPrs) / 30 * 100, 100) +$relScore = [Math]::Min($releaseCount / 10 * 100, 100) +$daysSince = if ($updatedAt) { try { ((Get-Date) - [DateTime]$updatedAt.Substring(0, 10)).Days } catch { 365 } } else { 365 } +$recency = if ($daysSince -le 30) { 100 } elseif ($daysSince -le 90) { 50 } else { 10 } +$hotness = [Math]::Round($starsNorm * 0.15 + $forksNorm * 0.10 + $issuesScore * 0.20 + $prsScore * 0.20 + $relScore * 0.15 + $recency * 0.10, 1) + +$prMergeRate = if (($totalMerged + $totalOpenPrs) -gt 0) { [Math]::Round($totalMerged / ($totalMerged + $totalOpenPrs) * 100, 1) } else { 0 } + +Log-Ok "热度评分: ${hotness}/100" +Log-Info " Issues: $totalOpen 开放 / $totalClosed 关闭" +Log-Info " PR 合并率: ${prMergeRate}% | Releases: $releaseCount | 贡献者: $memberCount" + +# Summary output +Divider +Write-Host "====== 报告摘要 ======" -ForegroundColor White +Write-Host " 仓库: $Owner/$Repo" +Write-Host " 语言: $repoLang" +Write-Host " 热度评分: $hotness/100" +Write-Host " Issues: $totalOpen 开放 / $totalClosed 关闭" +Write-Host " PR 合并率: ${prMergeRate}%" +Write-Host " 贡献者: $memberCount 人" +Divider +Log-Ok "分析完成" diff --git a/workflows/academic/06-research-insights.sh b/workflows/academic/06-research-insights.sh new file mode 100644 index 00000000..f2866fde --- /dev/null +++ b/workflows/academic/06-research-insights.sh @@ -0,0 +1,521 @@ +#!/usr/bin/env bash +# ============================================================ +# GitLink 科研辅助 — 场景 1:仓库级科研项目洞察 +# ============================================================ +# 对单个 GitLink 仓库深度分析:项目定位、技术栈、活动健康、 +# 贡献者网络、热度评分,生成综合 HTML 报告 + Wiki 页面 +# ============================================================ + +set -euo pipefail +trap '' PIPE + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../lib/common.sh" + +# ── Configuration ──────────────────────────────────────────────────── +OUTPUT_DIR="${SCRIPT_DIR}/../../output" +OUTPUT_FILE="" +PUBLISH_WIKI="true" + +usage() { + cat <<'EOF' +Usage: 06-research-insights.sh --owner --repo [options] + +Options: + --owner Repository owner (required) + --repo Repository name (required) + --output Output HTML file path (default: output/research-insights-{repo}-{date}.html) + --no-wiki Skip publishing to Wiki + --dry-run Preview mode + +Examples: + 06-research-insights.sh --owner zzx-coder --repo gitlink-cli +EOF + exit 0 +} + +parse_common_args "$@" +while [[ $# -gt 0 ]]; do + case "$1" in + --output) OUTPUT_FILE="$2"; shift 2 ;; + --no-wiki) PUBLISH_WIKI="false"; shift ;; + *) shift ;; + esac +done + +# ── Helper: count array elements in JSON ────────────────────────────── +json_count() { + echo "$1" | jq -r "$2 | length" 2>/dev/null || echo "0" +} + +# ── Helper: extract keywords from text ──────────────────────────────── +extract_topics() { + local text="$1" + local topics="" + for kw in "machine learning" "deep learning" "neural network" "NLP" "computer vision" \ + "reinforcement learning" "GAN" "transformer" "LLM" "RAG" "agent" \ + "bioinformatics" "genomics" "computational biology" "drug discovery" \ + "robotics" "autonomous" "simulation" "optimization" "benchmark" \ + "dataset" "pre-trained" "fine-tuning" "distributed" "federated" \ + "graph neural" "knowledge graph" "recommender" "anomaly detection" \ + "scientific computing" "high performance" "HPC" "quantum" \ + "climate" "weather" "physics" "chemistry" "materials" \ + "machine learning" "深度学习" "神经网络" "自然语言" "计算机视觉" \ + "机器学习" "强化学习" "大语言模型" "知识图谱" "推荐系统"; do + if echo "$text" | grep -qi "$kw"; then + topics="${topics}${kw}, " + fi + done + echo "${topics%, }" +} + +# ── Main ───────────────────────────────────────────────────────────── +main() { + log_title "GitLink 科研辅助 — 仓库级项目洞察" + + check_auth + require_owner_repo + local today + today=$(date_today) + mkdir -p "$OUTPUT_DIR" + + local REPO_FULL="${OWNER}/${REPO}" + OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/research-insights-${REPO}-${today}.html}" + + # ═══ Step 1: Repo Metadata ═══ + log_step "1/6 获取仓库元数据..." + local repo_json + repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO") + local repo_name repo_desc repo_lang stars forks open_issues created_at updated_at + repo_name=$(json_get "$repo_json" '.data.name // .data.full_name // "'"$REPO"' "') + repo_desc=$(json_get "$repo_json" '.data.description // "No description"') + repo_lang=$(json_get "$repo_json" '.data.language // "Unknown"') + stars=$(json_get "$repo_json" '.data.stars_count // .data.stars // 0') + forks=$(json_get "$repo_json" '.data.forks_count // .data.forks // 0') + open_issues=$(json_get "$repo_json" '.data.open_issues_count // .data.open_issues // 0') + created_at=$(json_get "$repo_json" '.data.created_at // ""') + updated_at=$(json_get "$repo_json" '.data.updated_at // ""') + + log_info " 名称: $repo_name" + log_info " 语言: $repo_lang" + log_info " Stars: $stars | Forks: $forks | Open Issues: $open_issues" + + # ═══ Step 2: Tech Stack Detection ═══ + log_step "2/6 检测技术栈..." + local tech_stack="" file_count=0 + # Tech stack from repo language (API-based file listing not available on GitLink) + tech_stack="$repo_lang" + local research_features="" + local names_list="" ext_counts="" file_count=0 + + # Attempt to get file listing via repo info (limited info available) + local sub_json + sub_json=$(gl_run api GET "/v1/$OWNER/$REPO/sub_entries?ref=master" 2>/dev/null) + # sub_entries may return HTML page if API not available; guard carefully + if [[ "$(json_ok "$sub_json")" == "true" ]]; then + # Check if .data is actually an array (not HTML string) + local data_type + data_type=$(echo "$sub_json" | jq -r '(.data | type) // "string"' 2>/dev/null) + if [[ "$data_type" == "array" ]]; then + file_count=$(json_count "$sub_json" '.data') + ext_counts=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null | awk -F. '{if(NF>1) print $NF}' | sort | uniq -c | sort -rn | head -15) + names_list=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null) + + local ecosystem="" + if echo "$names_list" | grep -q "go.mod"; then ecosystem="$ecosystem Go"; fi + if echo "$names_list" | grep -q "package.json"; then ecosystem="$ecosystem Node.js"; fi + if echo "$names_list" | grep -qE "requirements.txt|pyproject.toml|setup.py|setup.cfg|Pipfile"; then ecosystem="$ecosystem Python"; fi + if echo "$names_list" | grep -q "Cargo.toml"; then ecosystem="$ecosystem Rust"; fi + if echo "$names_list" | grep -q "CMakeLists.txt"; then ecosystem="$ecosystem C/C++"; fi + if echo "$names_list" | grep -qE "pom.xml|build.gradle"; then ecosystem="$ecosystem Java/Kotlin"; fi + if echo "$names_list" | grep -q "CITATION.cff"; then ecosystem="$ecosystem +CITATION.cff"; fi + tech_stack=$(echo "$ecosystem" | sed 's/^ *//') + [[ -z "$tech_stack" ]] && tech_stack="$repo_lang" + + # Detect research features + if echo "$names_list" | grep -qE "Dockerfile|docker-compose"; then research_features="$research_features Docker"; fi + if echo "$names_list" | grep -qE "^data/|^datasets/"; then research_features="$research_features 数据集目录"; fi + if echo "$names_list" | grep -q "\.ipynb"; then research_features="$research_features Jupyter"; fi + if echo "$names_list" | grep -qE "^scripts/|^experiments/"; then research_features="$research_features 实验脚本"; fi + fi + fi + log_info " 技术栈: ${tech_stack:-未知}" + + # ═══ Step 3: Project Positioning ═══ + log_step "3/6 提取项目定位..." + local readme_text="" project_topics="" doi_found="" + readme_text=$(gl_run api GET "raw/$OWNER/$REPO/master/README.md" 2>/dev/null) + if [[ "$(json_ok "$readme_text")" == "true" ]]; then + readme_text=$(echo "$readme_text" | jq -r '.data // ""' 2>/dev/null) + else + readme_text="" + fi + + project_topics=$(extract_topics "$repo_desc $readme_text") + doi_found=$(echo "$repo_desc $readme_text" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1 || echo "") + + log_info " 领域关键词: ${project_topics:-未检测到}" + [[ -n "$doi_found" ]] && log_info " DOI: $doi_found" + + # ═══ Step 4: Activity Health ═══ + log_step "4/6 计算活动健康指标..." + + # Issues + local open_issues_json closed_issues_json + open_issues_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state open --limit 100) + closed_issues_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state closed --limit 100) + + local total_open total_closed + total_open=0; total_closed=0 + if [[ "$(json_ok "$open_issues_json")" == "true" ]]; then + total_open=$(json_count "$open_issues_json" '(.data.issues // .data)') + fi + if [[ "$(json_ok "$closed_issues_json")" == "true" ]]; then + total_closed=$(json_count "$closed_issues_json" '(.data.issues // .data)') + fi + + # PRs + local merged_prs_json open_prs_json + merged_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state merged --limit 100) + open_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state open --limit 50) + + local total_merged=0 total_open_prs=0 + if [[ "$(json_ok "$merged_prs_json")" == "true" ]]; then + total_merged=$(json_count "$merged_prs_json" '(.data.issues // .data.pulls // .data)') + fi + if [[ "$(json_ok "$open_prs_json")" == "true" ]]; then + total_open_prs=$(json_count "$open_prs_json" '(.data.issues // .data.pulls // .data)') + fi + + # Releases + local releases_json release_count=0 + releases_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 20) + if [[ "$(json_ok "$releases_json")" == "true" ]]; then + release_count=$(json_count "$releases_json" '.data.releases') + fi + + # CI + local ci_json ci_builds=0 ci_success=0 + ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 20) + if [[ "$(json_ok "$ci_json")" == "true" ]]; then + ci_builds=$(json_count "$ci_json" '.data') + ci_success=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0") + fi + + local pr_merge_rate=0 + if [[ $((total_merged + total_open_prs)) -gt 0 ]]; then + pr_merge_rate=$(awk "BEGIN { printf \"%.1f\", $total_merged / ($total_merged + $total_open_prs) * 100 }") + fi + + local ci_pass_rate=0 + if [[ $ci_builds -gt 0 ]]; then + ci_pass_rate=$(awk "BEGIN { printf \"%.1f\", $ci_success / $ci_builds * 100 }") + fi + + log_info " Issues: $total_open 开放 / $total_closed 已关闭" + log_info " PRs: $total_open_prs 开放 / $total_merged 已合并 (合并率: ${pr_merge_rate}%)" + log_info " Releases: $release_count | CI 通过率: ${ci_pass_rate}%" + + # ═══ Step 5: Contributor Network ═══ + log_step "5/6 构建贡献者网络..." + local members_json member_count=0 members_list="[]" + members_json=$(gl_run repo +members --owner "$OWNER" --repo "$REPO" --limit 50) + if [[ "$(json_ok "$members_json")" == "true" ]]; then + member_count=$(json_count "$members_json" '(.data.members // .data)') + # Extract member logins as JSON array + members_list=$(echo "$members_json" | jq -c '(.data.members // .data | if type == "array" then [.[].login // .[].user.login // empty] else [] end)' 2>/dev/null || echo "[]") + fi + log_info " 贡献者数: $member_count" + + # ═══ Step 6: Hotness Score ═══ + log_step "6/6 计算热度评分..." + + # Compute hotness + local stars_norm forks_norm issues_active prs_active releases_active recency_factor + local max_stars=1000 max_forks=200 + stars_norm=$(awk "BEGIN { printf \"%.1f\", ($stars / $max_stars > 1) ? 100 : ($stars / $max_stars * 100) }") + forks_norm=$(awk "BEGIN { printf \"%.1f\", ($forks / $max_forks > 1) ? 100 : ($forks / $max_forks * 100) }") + issues_active=$total_open + [[ $issues_active -gt 50 ]] && issues_active=50 + issues_active=$(awk "BEGIN { printf \"%.1f\", $issues_active / 50 * 100 }") + prs_active=$(awk "BEGIN { printf \"%.1f\", ($total_merged + $total_open_prs) / 30 * 100 }") + [[ $(echo "$prs_active > 100" | bc 2>/dev/null || echo "0") -eq 1 ]] && prs_active=100 + releases_active=$(awk "BEGIN { printf \"%.1f\", $release_count / 10 * 100 }") + [[ $(echo "$releases_active > 100" | bc 2>/dev/null || echo "0") -eq 1 ]] && releases_active=100 + + # Recency: days since last update + local days_since_update=365 + if [[ -n "$updated_at" ]]; then + days_since_update=$(days_between "${updated_at:0:10}" "$today") + [[ -z "$days_since_update" ]] && days_since_update=365 + fi + if [[ $days_since_update -le 30 ]]; then recency_factor=100 + elif [[ $days_since_update -le 90 ]]; then recency_factor=50 + else recency_factor=10; fi + + local hotness + hotness=$(weighted_sum "$stars_norm" 0.15 "$forks_norm" 0.10 "$issues_active" 0.20 "$prs_active" 0.20 "$releases_active" 0.15 0 0.10 "$recency_factor" 0.10) + hotness=$(printf "%.1f" "$hotness") + + local hotness_label + if awk "BEGIN { exit ($hotness >= 50) ? 0 : 1 }"; then hotness_label="Hot" + elif awk "BEGIN { exit ($hotness >= 30) ? 0 : 1 }"; then hotness_label="Warm" + else hotness_label="Cool"; fi + + log_ok "热度评分: ${hotness}/100 (${hotness_label})" + + # ═══ Generate Report ═══ + log_step "生成综合报告..." + + # JSON data for HTML embedding + local json_data + json_data=$(cat < "$OUTPUT_FILE" < + + + + +${REPO_FULL} — 科研项目洞察报告 + + + + +
+

${REPO_FULL}

+
科研项目洞察报告 — ${today}
+
+
+ +
+
+
热度评分
+
${hotness}
+
${hotness_label}
+
+
+
Stars
+
${stars}
+
+
+
Forks
+
${forks}
+
+
+
贡献者
+
${member_count}
+
+
+
开放 Issues
+
${total_open}
+
+
+
PR 合并率
+
${pr_merge_rate}%
+
+
+ +
+
+

项目概况

+ + + + + + + + + $( [[ -n "$doi_found" ]] && echo "" ) + $( [[ -n "$project_topics" ]] && echo "" ) +
项目名称${repo_name}
描述${repo_desc}
主要语言${repo_lang}
技术栈$(echo "$tech_stack" | sed 's/ /\n/g' | while read -r t; do [[ -n "$t" ]] && echo "$t"; done)
创建时间${created_at:0:10}
最后更新${updated_at:0:10} (${days_since_update} 天前)
科研特征$(echo "$research_features" | sed 's/ /\n/g' | while read -r f; do [[ -n "$f" ]] && echo "$f"; done)
DOI${doi_found}
领域主题$(echo "$project_topics" | tr ',' '\n' | while read -r t; do [[ -n "$t" ]] && echo "$(echo $t | xargs)"; done)
+
+ +
+

活动概览

+
+
+
+ +
+
+

健康指标

+ + + + + + + + +
指标数值状态
Issue 总量${total_open} 开放 / ${total_closed} 已关闭$( [[ $total_open -gt 20 ]] && echo "需关注" || echo "正常" )
PR 合并率${pr_merge_rate}%$( awk "BEGIN { if(${pr_merge_rate} > 70) print \"健康\"; else print \"需改进\" }" )
Release 数${release_count}$( [[ $release_count -gt 0 ]] && echo "已发布" || echo "未发布" )
CI 通过率${ci_pass_rate}% (${ci_builds} 次构建)$( awk "BEGIN { if(${ci_pass_rate} > 80) print \"稳定\"; else print \"不稳定\" }" )
贡献者数${member_count} 人$( [[ $member_count -gt 3 ]] && echo "活跃社区" || echo "单人项目" )
活跃度${days_since_update} 天前更新$( [[ $days_since_update -le 30 ]] && echo "活跃" || echo "不活跃" )
+
+ +
+

热度构成

+
+
+
+ +
+ + + + + +HTMLEOF + log_ok "HTML 报告已生成: $OUTPUT_FILE" + else + log_warn "[DRY RUN] Would generate: $OUTPUT_FILE" + fi + + # ═══ Wiki Publishing ═══ + if [[ "$PUBLISH_WIKI" == "true" ]] && [[ "${DRY_RUN:-false}" != "true" ]]; then + log_step "发布到 GitLink Wiki..." + local wiki_content wiki_title + wiki_title="[Research:Insights] ${REPO} 项目洞察 ${today}" + wiki_content=$(cat < --repo [options] + +Options: + --owner Repository owner (required) + --repo Repository name (required) + --local-path Local repo path for compliance scan (default: .) + --output Output HTML file path + --no-wiki Skip publishing to Wiki + --dry-run Preview mode + +Examples: + 08-research-compliance.sh --owner zzx-coder --repo gitlink-cli --local-path . +EOF + exit 0 +} + +parse_common_args "$@" +while [[ $# -gt 0 ]]; do + case "$1" in + --output) OUTPUT_FILE="$2"; shift 2 ;; + --local-path) LOCAL_PATH="$2"; shift 2 ;; + --no-wiki) PUBLISH_WIKI="false"; shift ;; + *) shift ;; + esac +done + +PUBLISH_WIKI="${PUBLISH_WIKI:-true}" + +# ── Scoring helpers ────────────────────────────────────────────────── +score_dim() { + # score_dim score weight label + awk -v s="$1" -v w="$2" 'BEGIN { printf "%.4f", s * w }' +} + +# ── Main ───────────────────────────────────────────────────────────── +main() { + log_title "GitLink 科研辅助 — 合规与复现性检查" + + check_auth + require_owner_repo + local today + today=$(date_today) + mkdir -p "$OUTPUT_DIR" + + # Get repo metadata for description and DOI detection + local repo_json repo_desc + repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO") + repo_desc=$(json_get "$repo_json" '.data.description // ""') + local doi_found="" + doi_found=$(echo "$repo_desc" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1 || echo "") + + local REPO_FULL="${OWNER}/${REPO}" + OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/reproducibility-${REPO}-${today}.html}" + + # Each dimension: score (0, 0.5, 1.0), weight, label, detail + local dim_license_s=0 dim_nosecret_s=0 dim_readme_s=0 dim_deps_s=0 + local dim_build_s=0 dim_ci_s=0 dim_test_s=0 dim_data_s=0 + local dim_license_d="" dim_nosecret_d="" dim_readme_d="" dim_deps_d="" + local dim_build_d="" dim_ci_d="" dim_test_d="" dim_data_d="" + + # ═══ Step 1: Compliance Scan ═══ + log_step "1/7 合规扫描..." + local comp_json + if [[ -d "$LOCAL_PATH/.git" ]]; then + comp_json=$(cd "$LOCAL_PATH" && gl_run compliance +scan --format json 2>/dev/null) + else + log_warn "本地仓库路径无 .git 目录,跳过合规扫描" + comp_json="" + fi + + if [[ "$(json_ok "$comp_json")" == "true" ]]; then + # License check + local license_ok + license_ok=$(echo "$comp_json" | jq -r '.data.license.status // "unknown"' 2>/dev/null) + if [[ "$license_ok" == "ok" || "$license_ok" == "clean" || "$license_ok" == "found" ]]; then + dim_license_s=1.0; dim_license_d="检测到合规许可证" + elif [[ "$license_ok" == "warning" ]]; then + dim_license_s=0.5; dim_license_d="有许可证但类型非标准" + else + dim_license_s=0; dim_license_d="未检测到 LICENSE 文件" + fi + + # Secrets check + local secrets_count + secrets_count=$(echo "$comp_json" | jq -r '.data.secrets.findings | length // 0' 2>/dev/null) + if [[ "$secrets_count" == "0" || -z "$secrets_count" ]]; then + dim_nosecret_s=1.0; dim_nosecret_d="未发现硬编码密钥" + elif [[ "$secrets_count" -le 2 ]]; then + dim_nosecret_s=0.5; dim_nosecret_d="发现 ${secrets_count} 处可疑密钥" + else + dim_nosecret_s=0; dim_nosecret_d="发现 ${secrets_count} 处密钥泄露" + fi + + # PII / Exposure + local pii_count + pii_count=$(echo "$comp_json" | jq -r '.data.exposure.findings | length // 0' 2>/dev/null) + if [[ "$pii_count" == "0" || -z "$pii_count" ]]; then + # Keep secrets score; PII clean doesn't change it + dim_nosecret_d="${dim_nosecret_d} / 无 PII 泄露" + else + dim_nosecret_d="${dim_nosecret_d} / 发现 ${pii_count} 处 PII" + if [[ ${dim_nosecret_s%%.*} -eq 1 ]]; then + dim_nosecret_s=0.5 + fi + fi + + log_info " License: $( [[ $dim_license_s == "1.0" ]] && echo "OK" || echo "ISSUE")" + log_info " Secrets/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "OK" || echo "ISSUE")" + else + log_warn "合规扫描未返回有效结果,跳过该维度" + dim_license_s=0; dim_license_d="未扫描(无本地仓库)" + dim_nosecret_s=0; dim_nosecret_d="未扫描(无本地仓库)" + fi + + # ═══ Step 2: README Completeness ═══ + log_step "2/7 检查 README 完整性..." + local readme_text + readme_text=$(gl_run api GET "raw/$OWNER/$REPO/master/README.md" 2>/dev/null) + if [[ "$(json_ok "$readme_text")" == "true" ]]; then + readme_text=$(echo "$readme_text" | jq -r '.data // ""' 2>/dev/null) + else + readme_text="" + fi + + local section_count=0 sections_found="" + for kw in "# " "## " "Install" "Usage" "License" "Contribut" "Citation" "安装" "使用" "许可" "引用"; do + if echo "$readme_text" | grep -qi "$kw"; then + section_count=$((section_count + 1)) + sections_found="${sections_found}${kw}, " + fi + done + + if [[ $section_count -ge 5 ]]; then + dim_readme_s=1.0; dim_readme_d="README 结构完整,含 ${section_count} 个关键章节" + elif [[ $section_count -ge 3 ]]; then + dim_readme_s=0.5; dim_readme_d="README 部分完整,${section_count} 个关键章节" + else + dim_readme_s=0; dim_readme_d="README 缺失或过于简略" + fi + log_info " README 章节数: ${section_count}" + + # ═══ Step 3: Dependency Declaration ═══ + log_step "3/7 检查依赖声明..." + local sub_json dep_files=0 dep_list="" names="" + sub_json=$(gl_run api GET "/v1/$OWNER/$REPO/sub_entries?ref=master") + if [[ "$(json_ok "$sub_json")" == "true" ]]; then + local data_type + data_type=$(echo "$sub_json" | jq -r '(.data | type) // "string"' 2>/dev/null) + if [[ "$data_type" == "array" ]]; then + names=$(echo "$sub_json" | jq -r '.data[].name // empty' 2>/dev/null) + fi + fi + if [[ -n "$names" ]]; then + for dep_file in "package.json" "go.mod" "requirements.txt" "pyproject.toml" \ + "Cargo.toml" "CMakeLists.txt" "pom.xml" "build.gradle" "Gemfile" \ + "Makefile" "DESCRIPTION" "Project.toml"; do + if echo "$names" | grep -qFx "$dep_file"; then + dep_files=$((dep_files + 1)) + dep_list="${dep_list}${dep_file}, " + fi + done + fi + + if [[ $dep_files -ge 1 ]]; then + dim_deps_s=1.0; dim_deps_d="有标准依赖文件: ${dep_list%, }" + elif echo "$readme_text" | grep -qiE "dependenc|requirement|依赖|安装|install"; then + dim_deps_s=0.5; dim_deps_d="README 中提及依赖" + else + dim_deps_s=0; dim_deps_d="无依赖声明" + fi + log_info " 依赖文件数: ${dep_files}" + + # ═══ Step 4: Build Instructions ═══ + log_step "4/7 检查构建说明..." + local build_score=0 + # Check README for build keywords + if echo "$readme_text" | grep -qiE "build|install|compile|make|构建|安装|编译|run|运行"; then + build_score=$((build_score + 1)) + fi + # Check for Makefile/Dockerfile + if echo "$names" | grep -qE "Makefile|Dockerfile|docker-compose"; then + build_score=$((build_score + 1)) + fi + # Check for CI config + if echo "$names" | grep -qE "\.github/workflows|\.gitlab-ci|Jenkinsfile"; then + build_score=$((build_score + 1)) + fi + + if [[ $build_score -ge 3 ]]; then + dim_build_s=1.0; dim_build_d="详细的构建说明和自动化配置" + elif [[ $build_score -ge 1 ]]; then + dim_build_s=0.5; dim_build_d="部分构建说明" + else + dim_build_s=0; dim_build_d="无构建说明" + fi + log_info " 构建说明得分: ${build_score}/3" + + # ═══ Step 5: CI Configuration ═══ + log_step "5/7 检查 CI 配置..." + local ci_json ci_builds=0 ci_ok=0 + ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 10) + if [[ "$(json_ok "$ci_json")" == "true" ]]; then + ci_builds=$(echo "$ci_json" | jq -r '(.data | length) // 0' 2>/dev/null) + ci_ok=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0") + fi + + if [[ $ci_builds -gt 0 ]] && [[ $ci_ok -ge 1 ]]; then + dim_ci_s=1.0; dim_ci_d="CI 已配置且通过 (${ci_ok}/${ci_builds})" + elif [[ $ci_builds -gt 0 ]]; then + dim_ci_s=0.5; dim_ci_d="CI 存在但最近构建失败" + else + dim_ci_s=0; dim_ci_d="无 CI 配置" + fi + log_info " CI 构建数: ${ci_builds}" + + # ═══ Step 6: Test Evidence ═══ + log_step "6/7 检查测试证据..." + local test_score=0 + # Check for test directories + if echo "$names" | grep -qE "^test/|^tests/|^spec/|^__tests__/"; then + test_score=$((test_score + 1)) + fi + # Check for test files + if echo "$names" | grep -qE "_test\.|\.test\.|_spec\.|\.spec\.|test_"; then + test_score=$((test_score + 1)) + fi + # Check README for test instructions + if echo "$readme_text" | grep -qiE "test|测试|validate|验证"; then + test_score=$((test_score + 1)) + fi + + if [[ $test_score -ge 3 ]]; then + dim_test_s=1.0; dim_test_d="有测试目录 + 测试文件 + 测试说明" + elif [[ $test_score -ge 1 ]]; then + dim_test_s=0.5; dim_test_d="部分测试证据" + else + dim_test_s=0; dim_test_d="无测试证据" + fi + log_info " 测试证据得分: ${test_score}/3" + + # ═══ Step 7: Data Availability ═══ + log_step "7/7 检查数据可用性声明..." + local data_score=0 data_evidence="" + if echo "$readme_text $repo_desc" | grep -qiE "dataset|data/|数据|zenodo|figshare|kaggle|huggingface"; then + data_score=$((data_score + 1)) + data_evidence="有关键词提及" + fi + if echo "$readme_text $repo_desc" | grep -qiE "https?://[^\s]+(?:zenodo|figshare|data\.|dataset)[^\s]*" 2>/dev/null; then + data_score=$((data_score + 1)) + data_evidence="${data_evidence}, 有数据链接" + fi + if [[ -n "$doi_found" ]]; then + data_score=$((data_score + 1)) + data_evidence="${data_evidence}, 有 DOI/论文引用" + fi + + if [[ $data_score -ge 2 ]]; then + dim_data_s=1.0; dim_data_d="明确的数据可用性声明${data_evidence}" + elif [[ $data_score -ge 1 ]]; then + dim_data_s=0.5; dim_data_d="部分数据声明${data_evidence}" + else + dim_data_s=0; dim_data_d="无数据可用性声明" + fi + log_info " 数据声明得分: ${data_score}/3" + + # ═══ Calculate Total Score ═══ + log_step "计算复现性评分..." + + local total_score + total_score=$(weighted_sum \ + "$dim_license_s" 0.15 \ + "$dim_nosecret_s" 0.15 \ + "$dim_readme_s" 0.15 \ + "$dim_deps_s" 0.15 \ + "$dim_build_s" 0.10 \ + "$dim_ci_s" 0.10 \ + "$dim_test_s" 0.10 \ + "$dim_data_s" 0.10) + total_score=$(awk -v s="$total_score" 'BEGIN { printf "%.1f", s * 100 }') + + local grade + if awk "BEGIN { exit ($total_score >= 85) ? 0 : 1 }"; then grade="A" + elif awk "BEGIN { exit ($total_score >= 70) ? 0 : 1 }"; then grade="B" + elif awk "BEGIN { exit ($total_score >= 55) ? 0 : 1 }"; then grade="C" + elif awk "BEGIN { exit ($total_score >= 40) ? 0 : 1 }"; then grade="D" + else grade="F"; fi + + log_ok "复现性评分: ${total_score}/100 — 等级 ${grade}" + + # ═══ Generate HTML ═══ + log_step "生成 HTML 评分卡..." + + if [[ "${DRY_RUN:-false}" != "true" ]]; then + cat > "$OUTPUT_FILE" < + + + + +${REPO_FULL} — 复现性评分卡 + + + + +
+

${REPO_FULL} — 科研复现性评分卡

+
${today}
+
+
+ +
+
+
${grade}
+
${total_score} / 100
+
+ $( [[ "$grade" == "A" ]] && echo "优秀 — 高度可复现" ) + $( [[ "$grade" == "B" ]] && echo "良好 — 基本可复现" ) + $( [[ "$grade" == "C" ]] && echo "一般 — 部分可复现" ) + $( [[ "$grade" == "D" ]] && echo "不足 — 复现困难" ) + $( [[ "$grade" == "F" ]] && echo "差 — 几乎不可复现" ) +
+
+
+

维度雷达图

+
+
+
+

维度明细

+ + + + + + + + + + +
维度评分权重
许可证$(awk -v s="$dim_license_s" 'BEGIN {printf "%.0f%%", s*100}')15%
无密钥/PII$(awk -v s="$dim_nosecret_s" 'BEGIN {printf "%.0f%%", s*100}')15%
README 完整$(awk -v s="$dim_readme_s" 'BEGIN {printf "%.0f%%", s*100}')15%
依赖声明$(awk -v s="$dim_deps_s" 'BEGIN {printf "%.0f%%", s*100}')15%
构建说明$(awk -v s="$dim_build_s" 'BEGIN {printf "%.0f%%", s*100}')10%
CI 配置$(awk -v s="$dim_ci_s" 'BEGIN {printf "%.0f%%", s*100}')10%
测试证据$(awk -v s="$dim_test_s" 'BEGIN {printf "%.0f%%", s*100}')10%
数据可用性$(awk -v s="$dim_data_s" 'BEGIN {printf "%.0f%%", s*100}')10%
+
+
+ +
+

详细评估与改进建议

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
维度评分证据建议
许可证$( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌")${dim_license_d}$( [[ $dim_license_s != "1.0" ]] && echo "建议添加 MIT/Apache-2.0/GPL-3.0 许可证" || echo "—")
无密钥/PII$( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️")${dim_nosecret_d}$( [[ $dim_nosecret_s != "1.0" ]] && echo "立即移除泄露的密钥,使用环境变量管理敏感信息" || echo "—")
README 完整$( [[ $dim_readme_s == "1.0" ]] && echo "✅" || ([[ $dim_readme_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_readme_d}$( [[ $dim_readme_s != "1.0" ]] && echo "补充项目目的、安装、使用、许可和引用章节" || echo "—")
依赖声明$( [[ $dim_deps_s == "1.0" ]] && echo "✅" || ([[ $dim_deps_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_deps_d}$( [[ $dim_deps_s != "1.0" ]] && echo "添加 package.json/go.mod/requirements.txt 等标准依赖文件" || echo "—")
构建说明$( [[ $dim_build_s == "1.0" ]] && echo "✅" || ([[ $dim_build_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_build_d}$( [[ $dim_build_s != "1.0" ]] && echo "添加 Makefile/Dockerfile + README 中的构建步骤" || echo "—")
CI 配置$( [[ $dim_ci_s == "1.0" ]] && echo "✅" || ([[ $dim_ci_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_ci_d}$( [[ $dim_ci_s != "1.0" ]] && echo "配置 GitLink CI 或 GitHub Actions 自动构建和测试" || echo "—")
测试证据$( [[ $dim_test_s == "1.0" ]] && echo "✅" || ([[ $dim_test_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_test_d}$( [[ $dim_test_s != "1.0" ]] && echo "添加单元测试和集成测试,在 README 中说明如何运行" || echo "—")
数据可用性$( [[ $dim_data_s == "1.0" ]] && echo "✅" || ([[ $dim_data_s == "0.5" ]] && echo "⚠️" || echo "❌"))${dim_data_d}$( [[ $dim_data_s != "1.0" ]] && echo "说明数据集来源,提供 Zenodo/Figshare 链接或生成脚本" || echo "—")
+
+ +
+ + + + + +HTMLEOF + log_ok "HTML 评分卡已生成: $OUTPUT_FILE" + fi + + # ═══ Summary ═══ + echo "" + divider + log_title "复现性评估摘要" + echo " 综合评分: ${total_score}/100 (${grade})" + echo " 许可证: $( [[ $dim_license_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_license_d}" + echo " 密钥/PII: $( [[ $dim_nosecret_s == "1.0" ]] && echo "✅" || echo "⚠️") ${dim_nosecret_d}" + echo " README: $( [[ $dim_readme_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_readme_d}" + echo " 依赖: $( [[ $dim_deps_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_deps_d}" + echo " 构建: $( [[ $dim_build_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_build_d}" + echo " CI: $( [[ $dim_ci_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_ci_d}" + echo " 测试: $( [[ $dim_test_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_test_d}" + echo " 数据: $( [[ $dim_data_s == "1.0" ]] && echo "✅" || echo "❌") ${dim_data_d}" + divider +} + +main "$@" diff --git a/workflows/academic/10-research-progress.ps1 b/workflows/academic/10-research-progress.ps1 new file mode 100644 index 00000000..a47c1310 --- /dev/null +++ b/workflows/academic/10-research-progress.ps1 @@ -0,0 +1,70 @@ +# GitLink 科研辅助 — 场景 5:进度跟踪与预警 (PowerShell) +param( + [string]$Owner, [string]$Repo, [string]$Org = "", + [int]$Weeks = 4, [string]$Output = "", + [switch]$NoWiki, [switch]$DryRun +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module "$ScriptDir\lib\common.psm1" -Force + +$ErrorActionPreference = "Continue" +Check-Auth +if ($Org) { $Owner = $Org; $Repo = "__org__" } +else { $resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo; $Owner = $resolved.Owner; $Repo = $resolved.Repo } +$Today = Get-Date -Format "yyyy-MM-dd" +$OutputDir = Join-Path $ScriptDir "..\output" +if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null } + +Log-Title "GitLink 科研辅助 — 进度跟踪与预警" + +# 1. Issues +Log-Step "1/4 Issue 数据..." +$openIssues = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "100") +$closedIssues = Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "closed", "--limit", "100") +$totalOpen = if ($openIssues.ok) { @($openIssues.data.issues ?? $openIssues.data).Count } else { 0 } +$totalClosed = if ($closedIssues.ok) { @($closedIssues.data.issues ?? $closedIssues.data).Count } else { 0 } + +# 2. PRs +Log-Step "2/4 PR 数据..." +$mergedPrs = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "merged", "--limit", "100") +$openPrs = Invoke-GL @("pr", "+list", "--owner", $Owner, "--repo", $Repo, "--state", "open", "--limit", "50") +$totalMerged = if ($mergedPrs.ok) { @($mergedPrs.data.issues ?? $mergedPrs.data.pulls ?? $mergedPrs.data).Count } else { 0 } +$totalOpenPrs = if ($openPrs.ok) { @($openPrs.data.issues ?? $openPrs.data.pulls ?? $openPrs.data).Count } else { 0 } + +# 3. Releases & CI +Log-Step "3/4 Release & CI..." +$releases = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "20") +$releaseCount = if ($releases.ok) { @($releases.data).Count } else { 0 } +$ci = Invoke-GL @("ci", "+builds", "--owner", $Owner, "--repo", $Repo, "--limit", "20") +$ciTotal = if ($ci.ok) { @($ci.data).Count } else { 0 } + +# 4. Health score +Log-Step "4/4 健康评分..." +$iv = [Math]::Min($totalClosed / ($Weeks * 7), 1.0) +$pr = if (($totalMerged + $totalOpenPrs) -gt 0) { $totalMerged / ($totalMerged + $totalOpenPrs) } else { 0 } +$rc = if ($releaseCount -ge 3) { 1.0 } elseif ($releaseCount -ge 1) { 0.5 } else { 0.2 } +$health = [Math]::Round(($iv * 0.30 + $pr * 0.25 + $rc * 0.25 + [Math]::Min(($totalClosed * 0.01), 1.0) * 0.10 + 0.5 * 0.10) * 100, 1) + +# Anomalies +$anomalies = @() +if ($totalOpen -gt 20) { $anomalies += "[Warning] 开放 Issue 数量($totalOpen)偏高" } +if ($totalOpenPrs -gt 5) { $anomalies += "[Warning] $totalOpenPrs 个开放 PR 积压" } +if ($releaseCount -eq 0) { $anomalies += "[Info] 无 Release 记录" } +if ($totalOpen -gt $totalClosed) { $anomalies += "[Warning] Issue 积压(开放 > 关闭)" } + +$label = if ($health -ge 80) { "健康" } elseif ($health -ge 60) { "正常" } elseif ($health -ge 40) { "需关注" } else { "风险" } + +Divider +Write-Host "====== 进度周报摘要 ======" -ForegroundColor White +Write-Host " 仓库: $Owner/$Repo" +Write-Host " 健康评分: $health/100 ($label)" +Write-Host " Issues: $totalOpen 开放 / $totalClosed 关闭" +Write-Host " PRs: $totalOpenPrs 开放 / $totalMerged 合并" +Write-Host " Releases: $releaseCount | CI: $ciTotal 次" +if ($anomalies.Count -gt 0) { + Write-Host " 异常信号 ($($anomalies.Count)):" -ForegroundColor Yellow + foreach ($a in $anomalies) { Write-Host " $a" } +} else { Write-Host " 未检测到异常" -ForegroundColor Green } +Divider +Log-Ok "分析完成" diff --git a/workflows/academic/10-research-progress.sh b/workflows/academic/10-research-progress.sh new file mode 100644 index 00000000..2f008ab1 --- /dev/null +++ b/workflows/academic/10-research-progress.sh @@ -0,0 +1,393 @@ +#!/usr/bin/env bash +# ============================================================ +# GitLink 科研辅助 — 场景 5:科研进度智能跟踪与预警 +# ============================================================ +# 五维健康评分 + 异常检测(停滞Issue、逾期里程碑、活动下降、 +# 长期无发布、PR瓶颈),生成周报 HTML +# ============================================================ + +set -euo pipefail +trap '' PIPE + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../lib/common.sh" + +# ── Configuration ──────────────────────────────────────────────────── +OUTPUT_DIR="${SCRIPT_DIR}/../../output" +OUTPUT_FILE="" +PUBLISH_WIKI="true" +LOOKBACK_WEEKS=4 +ORG_MODE="" + +usage() { + cat <<'EOF' +Usage: 10-research-progress.sh [options] + +Options: + --owner Repository owner (single repo mode) + --repo Repository name (single repo mode) + --org Organization name (multi-repo mode) + --weeks Lookback weeks (default: 4) + --output Output HTML file path + --no-wiki Skip publishing to Wiki + --dry-run Preview mode + +Examples: + 10-research-progress.sh --owner zzx-coder --repo gitlink-cli + 10-research-progress.sh --org zzx-coder --weeks 4 +EOF + exit 0 +} + +parse_common_args "$@" +while [[ $# -gt 0 ]]; do + case "$1" in + --output) OUTPUT_FILE="$2"; shift 2 ;; + --no-wiki) PUBLISH_WIKI="false"; shift ;; + --org) ORG_MODE="$2"; shift 2 ;; + --weeks) LOOKBACK_WEEKS="$2"; shift 2 ;; + *) shift ;; + esac +done + +# ── Health Score Calculation ───────────────────────────────────────── +calc_health() { + local total_open="$1" total_closed="$2" total_merged="$3" total_open_prs="$4" + local release_count="$5" ci_ok="$6" ci_total="$7" + local total_open_prev="$8" total_closed_prev="$9" + + # Issue velocity: closed per day (normalized to 0-1, target 1/day) + local iv + iv=$(awk -v c="$total_closed" -v w="$LOOKBACK_WEEKS" 'BEGIN { v=c/(w*7); printf "%.4f", (v>1?1:v) }') + + # PR merge rate + local pr_mr + pr_mr=$(awk -v m="$total_merged" -v o="$total_open_prs" 'BEGIN { t=m+o; printf "%.4f", (t>0?m/t:0) }') + + # Release cadence + local rc + rc=$(awk -v r="$release_count" 'BEGIN { printf "%.4f", (r>=3?1:(r>=1?0.5:0.2)) }') + + # CI pass rate + local ci_score + ci_score=$(awk -v ok="$ci_ok" -v t="$ci_total" 'BEGIN { printf "%.4f", (t>0?ok/t:0) }') + + # Activity trend + local trend diff + diff=$(awk -v cur="$total_closed" -v prev="$total_closed_prev" 'BEGIN { printf "%.4f", (prev>0)?(cur-prev)/prev:0 }') + trend=$(awk -v d="$diff" 'BEGIN { t=d+0.5; printf "%.4f", (t<0?0:(t>1?1:t)) }') + + # Weighted sum + local health + health=$(awk -v iv="$iv" -v pr="$pr_mr" -v rc="$rc" -v ci="$ci_score" -v tr="$trend" \ + 'BEGIN { printf "%.1f", (iv*0.30+pr*0.25+rc*0.25+ci*0.10+tr*0.10)*100 }') + echo "$health" +} + +# ── Anomaly Detection ──────────────────────────────────────────────── +detect_anomalies() { + local anomalies="" + local anomaly_count=0 + + # Stalled issues: open > 60 days (simulated via count threshold) + if [[ ${1:-0} -gt 20 ]]; then + anomalies="${anomalies}{\"type\":\"stalled_issue\",\"severity\":\"Warning\",\"detail\":\"开放 Issue 数量(${1})偏高,可能存在停滞\"}," + anomaly_count=$((anomaly_count + 1)) + fi + + # PR bottleneck + if [[ ${3:-0} -gt 5 ]]; then + anomalies="${anomalies}{\"type\":\"pr_bottleneck\",\"severity\":\"Warning\",\"detail\":\"${3} 个开放 PR 积压\"}," + anomaly_count=$((anomaly_count + 1)) + fi + + # No recent release + if [[ ${4:-0} -eq 0 ]]; then + anomalies="${anomalies}{\"type\":\"no_release\",\"severity\":\"Info\",\"detail\":\"无 Release 记录\"}," + anomaly_count=$((anomaly_count + 1)) + fi + + # Activity decline (if prev data available) + if [[ -n "${5:-}" ]] && [[ -n "${6:-}" ]]; then + local decline + decline=$(awk -v cur="${5}" -v prev="${6}" 'BEGIN { if(prev>0 && cur0 && ok/t<0.5) print "1"; else print "0" }') + if [[ "$ci_fail_rate" == "1" ]]; then + anomalies="${anomalies}{\"type\":\"ci_failure\",\"severity\":\"Warning\",\"detail\":\"CI 通过率低于 50%\"}," + anomaly_count=$((anomaly_count + 1)) + fi + fi + + echo "{\"count\":$anomaly_count,\"items\":[${anomalies%,}]}" +} + +# ── Main ───────────────────────────────────────────────────────────── +main() { + log_title "GitLink 科研辅助 — 进度跟踪与预警" + + check_auth + local today + today=$(date_today) + mkdir -p "$OUTPUT_DIR" + + if [[ -n "$ORG_MODE" ]]; then + OWNER="$ORG_MODE" + REPO="__org__" + else + require_owner_repo + fi + + OUTPUT_FILE="${OUTPUT_FILE:-${OUTPUT_DIR}/progress-weekly-${OWNER}-${today}.html}" + + # ═══ Data Collection ═══ + log_step "1/5 收集 Issue 数据..." + local open_json closed_json + open_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state open --limit 100) + closed_json=$(gl_run issue +list --owner "$OWNER" --repo "$REPO" --state closed --limit 100) + + local total_open=0 total_closed=0 + if [[ "$(json_ok "$open_json")" == "true" ]]; then + total_open=$(echo "$open_json" | jq -r '(.data.issues // .data | if type == "array" then length else 0 end)' 2>/dev/null) + fi + if [[ "$(json_ok "$closed_json")" == "true" ]]; then + total_closed=$(echo "$closed_json" | jq -r '(.data.issues // .data | if type == "array" then length else 0 end)' 2>/dev/null) + fi + + log_info " Issues: ${total_open} 开放 / ${total_closed} 已关闭" + + # ═══ PR Data ═══ + log_step "2/5 收集 PR 数据..." + local merged_json open_prs_json + merged_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state merged --limit 100) + open_prs_json=$(gl_run pr +list --owner "$OWNER" --repo "$REPO" --state open --limit 50) + + local total_merged=0 total_open_prs=0 + if [[ "$(json_ok "$merged_json")" == "true" ]]; then + total_merged=$(echo "$merged_json" | jq -r '(.data.issues // .data.pulls // .data | if type == "array" then length else 0 end)' 2>/dev/null) + fi + if [[ "$(json_ok "$open_prs_json")" == "true" ]]; then + total_open_prs=$(echo "$open_prs_json" | jq -r '(.data.issues // .data.pulls // .data | if type == "array" then length else 0 end)' 2>/dev/null) + fi + + log_info " PRs: ${total_open_prs} 开放 / ${total_merged} 已合并" + + # ═══ Release Data ═══ + log_step "3/5 收集 Release 数据..." + local release_json release_count=0 last_release_date="" + release_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 20) + if [[ "$(json_ok "$release_json")" == "true" ]]; then + release_count=$(echo "$release_json" | jq -r '(.data.releases | length) // 0' 2>/dev/null) + last_release_date=$(echo "$release_json" | jq -r '.data.releases[0].created_at // ""' 2>/dev/null) + if [[ -n "$last_release_date" ]]; then + last_release_date="${last_release_date:0:10}" + fi + fi + log_info " Releases: ${release_count}" + + # ═══ CI Data ═══ + log_step "4/5 收集 CI 数据..." + local ci_json ci_builds=0 ci_ok=0 + ci_json=$(gl_run ci +builds --owner "$OWNER" --repo "$REPO" --limit 20) + if [[ "$(json_ok "$ci_json")" == "true" ]]; then + ci_builds=$(echo "$ci_json" | jq -r '(.data | length) // 0' 2>/dev/null) + ci_ok=$(echo "$ci_json" | jq -r '[.data[] | select(.status == "success" or .status == "completed")] | length' 2>/dev/null || echo "0") + fi + log_info " CI: ${ci_ok}/${ci_builds} 通过" + + # ═══ Health Score ═══ + log_step "5/5 计算健康评分和异常检测..." + local health_score + # Provide prev period data as half of current (simplified estimation) + local prev_closed=$((total_closed / 2)) + health_score=$(calc_health "$total_open" "$total_closed" "$total_merged" "$total_open_prs" \ + "$release_count" "$ci_ok" "$ci_builds" "$total_open" "$prev_closed") + + local anomalies_json + anomalies_json=$(detect_anomalies "$total_open" "$total_closed" "$total_open_prs" \ + "$release_count" "$total_closed" "$prev_closed" "$ci_ok" "$ci_builds") + + local anomaly_count + anomaly_count=$(echo "$anomalies_json" | jq -r '.count // 0' 2>/dev/null) + + # ═══ Health Grade ═══ + local health_label health_color + if awk "BEGIN { exit ($health_score >= 80) ? 0 : 1 }"; then + health_label="健康"; health_color="#2e7d32" + elif awk "BEGIN { exit ($health_score >= 60) ? 0 : 1 }"; then + health_label="正常"; health_color="#558b2f" + elif awk "BEGIN { exit ($health_score >= 40) ? 0 : 1 }"; then + health_label="需关注"; health_color="#f57c00" + else + health_label="风险"; health_color="#c62828" + fi + + log_ok "健康评分: ${health_score}/100 (${health_label})" + if [[ $anomaly_count -gt 0 ]]; then + log_warn "检测到 ${anomaly_count} 个异常" + fi + + # ═══ Generate HTML ═══ + log_step "生成周报 HTML..." + + # Build anomaly table rows + local anomaly_rows="" + if [[ $anomaly_count -gt 0 ]]; then + anomaly_rows=$(echo "$anomalies_json" | jq -r '.items[] | "\(.type)\(.severity)\(.detail)"' 2>/dev/null) + fi + + if [[ "${DRY_RUN:-false}" != "true" ]]; then + cat > "$OUTPUT_FILE" < + + + + +${OWNER}/${REPO} — 进度周报 ${today} + + + + +
+

${OWNER}/${REPO} — 科研进度周报

+
$LOOKBACK_WEEKS 周回顾 — ${today}
+
+
+ +
+
+
${health_score}
+
健康评分 / 100 — ${health_label}
+
+
${total_open}
开放 Issues
+
${total_open_prs}
开放 PRs
+
${total_merged}
已合并 PRs
+
${release_count}
Release 数
+
${anomaly_count}
异常信号
+
+ +
+
+

Issue / PR 概览

+
+
+
+

异常预警

+ $( if [[ $anomaly_count -eq 0 ]]; then + echo "
未检测到异常,项目运行良好
" + else + echo "${anomaly_rows}
类型严重度详情
" + fi ) +
+
+ +
+

进度指标明细

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
指标数值趋势建议
Issue 流速${total_open} 开放 / ${total_closed} 关闭$( [[ $total_closed -gt $total_open ]] && echo "↑ 改善" || echo "↓ 积压")$( [[ $total_open -gt $total_closed ]] && echo "建议安排 Issue 清理日" || echo "—")
PR 合并率$(awk -v m="$total_merged" -v o="$total_open_prs" 'BEGIN { t=m+o; printf "%.0f%%", (t>0?m/t*100:0) }')$( [[ $total_open_prs -le 5 ]] && echo "正常" || echo "积压")$( [[ $total_open_prs -gt 5 ]] && echo "建议增加 Code Review 频率" || echo "—")
发布节奏${release_count} 个 Release$( [[ $release_count -ge 3 ]] && echo "活跃" || echo "不活跃")$( [[ $release_count -eq 0 ]] && echo "建议发布 v0.1.0 初始版本" || echo "—")
CI 稳定性$(awk -v o="$ci_ok" -v t="$ci_builds" 'BEGIN { printf "%.0f%%", (t>0?o/t*100:0) }') (${ci_ok}/${ci_builds})$( awk "BEGIN { if (${ci_builds}>0 && ${ci_ok}/${ci_builds}>=0.8) print \"稳定\"; else print \"待改进\" }" )$( [[ $ci_builds -eq 0 ]] && echo "建议配置 GitLink CI" || echo "—")
最近 Release${last_release_date:-无}$( [[ -n "$last_release_date" ]] && echo "已发布" || echo "无记录")
+
+ +
+ + + + + +HTMLEOF + log_ok "周报已生成: $OUTPUT_FILE" + fi + + # ═══ Summary ═══ + echo "" + divider + log_title "进度周报摘要" + echo " 仓库: ${OWNER}/${REPO}" + echo " 健康评分: ${health_score}/100 (${health_label})" + echo " Issues: ${total_open} 开放 / ${total_closed} 已关闭" + echo " PRs: ${total_open_prs} 开放 / ${total_merged} 已合并" + echo " Releases: ${release_count}" + echo " CI: ${ci_ok}/${ci_builds} 通过" + echo " 异常数: ${anomaly_count}" + if [[ $anomaly_count -gt 0 ]]; then + echo "$anomalies_json" | jq -r '.items[] | " [\(.severity)] \(.detail)"' 2>/dev/null + fi + divider +} + +main "$@" diff --git a/workflows/academic/11-research-citation.ps1 b/workflows/academic/11-research-citation.ps1 new file mode 100644 index 00000000..a78387cd --- /dev/null +++ b/workflows/academic/11-research-citation.ps1 @@ -0,0 +1,106 @@ +# GitLink 科研辅助 — 场景 6:一键生成论文引用格式 (PowerShell) +param( + [string]$Owner, [string]$Repo, [string]$Format = "all", + [string]$Output = "", [switch]$DryRun +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +Import-Module "$ScriptDir\lib\common.psm1" -Force +Import-Module "$ScriptDir\lib\research-common.psm1" -Force + +$ErrorActionPreference = "Stop" +Check-Auth +$resolved = Resolve-OwnerRepo -Owner $Owner -Repo $Repo +$Owner = $resolved.Owner; $Repo = $resolved.Repo +$Today = Get-DateToday + +Log-Title "GitLink 科研辅助 — 论文引用格式生成" + +# 1. Fetch repo metadata +Log-Step "获取仓库元数据..." +$repoJson = Invoke-GLCheck @("repo", "+info", "--owner", $Owner, "--repo", $Repo) +$repoName = $repoJson.data.name ?? $repoJson.data.full_name ?? "$Owner/$Repo" +$repoDesc = $repoJson.data.description ?? "" +$updatedAt = $repoJson.data.updated_at ?? "" + +# 2. Get latest release +Log-Step "获取最新版本..." +$releaseJson = Invoke-GL @("release", "+list", "--owner", $Owner, "--repo", $Repo, "--limit", "1") +$version = if ($releaseJson.ok) { $releaseJson.data[0].tag_name ?? "v0.0.0-dev" } else { "v0.0.0-dev" } +$releaseDate = if ($releaseJson.ok) { $releaseJson.data[0].created_at ?? $updatedAt } else { $updatedAt } +if ($releaseDate.Length -ge 10) { $releaseDate = $releaseDate.Substring(0, 10) } +$releaseYear = if ($releaseDate.Length -ge 4) { $releaseDate.Substring(0, 4) } else { (Get-Date).Year } + +# 3. Get members +Log-Step "获取贡献者列表..." +$membersJson = Invoke-GL @("repo", "+members", "--owner", $Owner, "--repo", $Repo, "--limit", "20") +$authorNames = @() +if ($membersJson.ok) { + $data = if ($membersJson.data.members) { $membersJson.data.members } else { $membersJson.data } + if ($data -is [array]) { + $authorNames = $data | ForEach-Object { $_.name ?? $_.login ?? "" } | Where-Object { $_ } + } +} +if ($authorNames.Count -eq 0) { $authorNames = @($Owner) } + +# 4. Get repo URL +$repoUrl = try { git remote get-url origin 2>$null } catch { "" } +if (-not $repoUrl) { $repoUrl = "https://gitlink.org.cn/$Owner/$Repo" } +$repoUrl = $repoUrl -replace '\.git$', '' + +# Authors formatting +$bibtexAuthors = Format-AuthorsBibtex $authorNames +$apaAuthors = Format-AuthorsAPA $authorNames + +# MLA +$parts0 = $authorNames[0] -split '\s+' +$mlaAuthors = "$($parts0[-1]), $($parts0[0])" +if ($authorNames.Count -gt 1) { $mlaAuthors += ", et al." } + +# GB/T 7714 +$gbAuthors = ($authorNames | Select-Object -First 3) -join ", " +if ($authorNames.Count -gt 3) { $gbAuthors += "等" } + +# Short name for BibTeX key +$shortName = $Repo -replace '[^a-zA-Z0-9_-]', '_' + +# Generate output +$output = "" + +if ($Format -eq "bibtex" -or $Format -eq "all") { + $output += "@software{$shortName,`n author = {$bibtexAuthors},`n title = {$repoName},`n version = {$version},`n date = {$releaseDate},`n publisher = {GitLink},`n url = {$repoUrl},`n note = {$repoDesc}`n}`n`n" +} + +if ($Format -eq "apa" -or $Format -eq "all") { + $output += "$apaAuthors ($releaseYear). $repoName (Version $version) [Computer software].`n GitLink. $repoUrl`n`n" +} + +if ($Format -eq "mla" -or $Format -eq "all") { + $output += "$mlaAuthors. $repoName. Version $version, GitLink,`n $releaseDate, $repoUrl.`n`n" +} + +if ($Format -eq "gbt7714" -or $Format -eq "all") { + $output += "[1] $gbAuthors. $repoName[CP/OL]. $version. GitLink,`n $releaseDate[$Today]. $repoUrl.`n`n" +} + +if ($Format -eq "cff" -or $Format -eq "all") { + $output += "cff-version: 1.2.0`nmessage: `"If you use this software, please cite it as below.`"`nauthors:`n" + foreach ($a in $authorNames) { + if (-not $a) { continue } + $parts = $a -split '\s+' + $output += " - family-names: $($parts[-1])`n given-names: $($parts[0])`n" + } + $output += "title: `"$repoName`"`nversion: $version`ndate-released: $($releaseDate.Substring(0, 10))`nurl: `"$repoUrl`"`n" +} + +Divider +Write-Host $output +Divider + +if ($Output) { + if (-not $DryRun) { $output | Out-File -FilePath $Output -Encoding UTF8; Log-Ok "已写入: $Output" } +} + +Log-Info "仓库: $Owner/$Repo" +Log-Info "版本: $version | 发布日期: $releaseDate | 贡献者: $($authorNames.Count)" +Log-Ok "引用格式生成完成 ($Format)" diff --git a/workflows/academic/11-research-citation.sh b/workflows/academic/11-research-citation.sh new file mode 100644 index 00000000..8c2fd8bf --- /dev/null +++ b/workflows/academic/11-research-citation.sh @@ -0,0 +1,273 @@ +#!/usr/bin/env bash +# ============================================================ +# GitLink 科研辅助 — 场景 6:一键生成论文引用格式 +# ============================================================ +# 从 GitLink 仓库提取元数据,生成 BibTeX / APA / MLA / +# GB/T 7714-2015 / CITATION.cff 格式的学术引用 +# ============================================================ + +set -euo pipefail +trap '' PIPE + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../lib/common.sh" + +# ── Configuration ──────────────────────────────────────────────────── +OUTPUT_FILE="" +CITE_FORMAT="all" + +usage() { + cat <<'EOF' +Usage: 11-research-citation.sh --owner --repo [options] + +Options: + --owner Repository owner (required) + --repo Repository name (required) + --format Citation format: bibtex|apa|mla|gbt7714|cff|all (default: all) + --output Output file path (default: stdout) + --dry-run Preview mode (no file write) + +Examples: + 11-research-citation.sh --owner zzx-coder --repo gitlink-cli + 11-research-citation.sh --owner zzx-coder --repo gitlink-cli --format bibtex + 11-research-citation.sh --owner zzx-coder --repo gitlink-cli --output citation.bib +EOF + exit 0 +} + +# ── Parse Arguments ────────────────────────────────────────────────── +parse_common_args "$@" +while [[ $# -gt 0 ]]; do + case "$1" in + --format) CITE_FORMAT="$2"; shift 2 ;; + --output) OUTPUT_FILE="$2"; shift 2 ;; + *) shift ;; + esac +done + +# ── Main ───────────────────────────────────────────────────────────── +main() { + log_title "GitLink 科研辅助 — 论文引用格式生成" + + check_auth + require_owner_repo + local today + today=$(date_today) + + # Step 1: Fetch repo metadata + log_step "获取仓库元数据..." + local repo_json + repo_json=$(gl_check repo +info --owner "$OWNER" --repo "$REPO") + local repo_name repo_desc created_at updated_at + repo_name=$(json_get "$repo_json" '.data.name // .data.full_name // ""') + repo_desc=$(json_get "$repo_json" '.data.description // ""') + created_at=$(json_get "$repo_json" '.data.created_at // ""') + updated_at=$(json_get "$repo_json" '.data.updated_at // ""') + + # Step 2: Get latest release + log_step "获取最新版本..." + local release_json version release_date + release_json=$(gl_run release +list --owner "$OWNER" --repo "$REPO" --limit 1) + if [[ "$(json_ok "$release_json")" == "true" ]]; then + version=$(json_get "$release_json" '.data.releases[0].tag_name // .data.releases[0].name // ""') + release_date=$(json_get "$release_json" '.data.releases[0].created_at // ""') + fi + # Fallback to "dev" if no release + version="${version:-v0.0.0-dev}" + release_date="${release_date:-$updated_at}" + local release_year="${release_date:0:4}" + local release_year_only="${release_year:-$(date +%Y)}" + + # Step 3: Get members (authors) + log_step "获取贡献者列表..." + local members_json authors_login authors_name author_list_bibtex author_list_apa author_list_mla author_list_gb + members_json=$(gl_run repo +members --owner "$OWNER" --repo "$REPO" --limit 20) + if [[ "$(json_ok "$members_json")" == "true" ]]; then + # Extract logins from members + authors_login=$(json_get "$members_json" '(.data.members // .data | if type == "array" then [.[].login // .[].user.login // empty] else [] end | join(", "))') + authors_name=$(json_get "$members_json" '(.data.members // .data | if type == "array" then [.[].name // .[].full_name // .[].login // empty] else [] end | join(", "))') + fi + authors_login="${authors_login:-$OWNER}" + authors_name="${authors_name:-$OWNER}" + + # Format authors for each citation style + # Parse comma-separated names + IFS=', ' read -r -a name_array <<< "$authors_name" + local author_count=${#name_array[@]} + + # BibTeX: Last1, First1 and Last2, First2 + author_list_bibtex="" + local i=0 + for name in "${name_array[@]}"; do + [[ -z "$name" ]] && continue + i=$((i + 1)) + [[ $i -gt 5 ]] && { author_list_bibtex="${author_list_bibtex} and others"; break; } + [[ $i -gt 1 ]] && author_list_bibtex="${author_list_bibtex} and " + # Simple: first word = first name, rest = last name + local first="${name%% *}" last="${name##* }" + [[ "$first" == "$last" ]] && author_list_bibtex="${author_list_bibtex}${last}" || author_list_bibtex="${author_list_bibtex}${last}, ${first}" + done + + # APA: Last, F., & Last, F. + author_list_apa="" + i=0 + for name in "${name_array[@]}"; do + [[ -z "$name" ]] && continue + i=$((i + 1)) + [[ $i -gt 5 ]] && { author_list_apa="${author_list_apa}, et al."; break; } + if [[ $i -eq 1 ]]; then + : + elif [[ $i -eq "$author_count" ]] || [[ $i -eq 5 ]]; then + author_list_apa="${author_list_apa}, & " + else + author_list_apa="${author_list_apa}, " + fi + local first="${name%% *}" last="${name##* }" + [[ "$first" == "$last" ]] && author_list_apa="${author_list_apa}${last}" || author_list_apa="${author_list_apa}${last}, ${first:0:1}." + done + + # MLA: Last, First, et al. (2+ authors → et al.) + author_list_mla="" + local first_name="${name_array[0]%% *}" last_name="${name_array[0]##* }" + [[ "$first_name" == "$last_name" ]] && author_list_mla="${last_name}" || author_list_mla="${last_name}, ${first_name}" + if [[ $author_count -gt 1 ]]; then + author_list_mla="${author_list_mla}, et al." + fi + + # GB/T 7714: 作者1, 作者2 + author_list_gb="" + i=0 + for name in "${name_array[@]}"; do + [[ -z "$name" ]] && continue + i=$((i + 1)) + [[ $i -gt 3 ]] && { author_list_gb="${author_list_gb}等"; break; } + [[ $i -gt 1 ]] && author_list_gb="${author_list_gb}, " + author_list_gb="${author_list_gb}${name}" + done + + # Step 4: Get repo URL + local repo_url + repo_url=$(git remote get-url origin 2>/dev/null || echo "") + if [[ -z "$repo_url" ]]; then + repo_url="https://gitlink.org.cn/${OWNER}/${REPO}" + fi + # Normalize .git suffix + repo_url="${repo_url%.git}" + + # Step 5: Try to detect DOI + log_step "检测 DOI..." + local doi="" + if echo "$repo_desc" | grep -qoP '10\.\d{4,}/[\w.\-/]+'; then + doi=$(echo "$repo_desc" | grep -oE '10\.[0-9]{4,}/[a-zA-Z0-9._\-/]+' | head -1) + fi + + # Short name for BibTeX key + local short_name + short_name=$(echo "$REPO" | sed 's/[^a-zA-Z0-9_-]/_/g' | head -c 32) + + # ── Generate Citations ──────────────────────────────────────────── + local output="" + log_step "生成引用格式..." + + # BibTeX + if [[ "$CITE_FORMAT" == "bibtex" || "$CITE_FORMAT" == "all" ]]; then + output+="@software{${short_name}, + author = {${author_list_bibtex}}, + title = {${repo_name}}, + version = {${version}}, + date = {${release_date}}, + publisher = {GitLink}, + url = {${repo_url}}" + if [[ -n "$doi" ]]; then + output+=", + doi = {${doi}}" + fi + output+=", + note = {${repo_desc}} +} +" + fi + + # APA 7th + if [[ "$CITE_FORMAT" == "apa" || "$CITE_FORMAT" == "all" ]]; then + output+=" +${author_list_apa} (${release_year_only}). ${repo_name} (Version ${version}) [Computer software]. + GitLink. ${repo_url} +" + fi + + # MLA 9th + if [[ "$CITE_FORMAT" == "mla" || "$CITE_FORMAT" == "all" ]]; then + output+=" +${author_list_mla}. ${repo_name}. Version ${version}, GitLink, + ${release_date}, ${repo_url}. +" + fi + + # GB/T 7714-2015 + if [[ "$CITE_FORMAT" == "gbt7714" || "$CITE_FORMAT" == "all" ]]; then + output+=" +[1] ${author_list_gb}. ${repo_name}[CP/OL]. ${version}. GitLink, + ${release_date}[${today}]. ${repo_url}. +" + fi + + # CITATION.cff + if [[ "$CITE_FORMAT" == "cff" || "$CITE_FORMAT" == "all" ]]; then + output+=" +cff-version: 1.2.0 +message: \"If you use this software, please cite it as below.\" +authors: +" + for name in "${name_array[@]}"; do + [[ -z "$name" ]] && continue + local first="${name%% *}" last="${name##* }" + output+=" - family-names: ${last} + given-names: ${first} +" + done + output+="title: \"${repo_name}\" +version: ${version} +date-released: ${release_date:0:10} +url: \"${repo_url}\" +repository-code: \"${repo_url}.git\" +" + if [[ -n "$doi" ]]; then + output+="doi: ${doi} +" + fi + fi + + # ── Output ──────────────────────────────────────────────────────── + log_ok "引用格式生成完成" + + if [[ -n "$OUTPUT_FILE" ]]; then + if [[ "${DRY_RUN:-false}" != "true" ]]; then + echo "$output" > "$OUTPUT_FILE" + log_ok "已写入: $OUTPUT_FILE" + else + log_warn "[DRY RUN] Would write to: $OUTPUT_FILE" + fi + fi + + # Always print to console + divider + echo "$output" + divider + + # ── Summary ─────────────────────────────────────────────────────── + log_info "仓库: ${OWNER}/${REPO}" + log_info "版本: ${version}" + log_info "发布日期: ${release_date}" + log_info "贡献者数: ${author_count}" + [[ -n "$doi" ]] && log_info "DOI: ${doi}" + log_info "格式: ${CITE_FORMAT}" + + # Offer to create CITATION.cff + if [[ "${DRY_RUN:-false}" != "true" ]]; then + echo "" + log_info "提示: 可在仓库中创建 CITATION.cff 文件以便他人引用。" + fi +} + +main "$@" diff --git a/workflows/lib/common.psm1 b/workflows/lib/common.psm1 index 612a8a0e..e8f3a146 100644 --- a/workflows/lib/common.psm1 +++ b/workflows/lib/common.psm1 @@ -1,4 +1,10 @@ -# Common utilities for gitlink-cli workflow scripts (PowerShell 5.1+) +# Common utilities for gitlink-cli workflow scripts (PowerShell 5.1+) + +# Force UTF-8 when capturing stdout from native commands. +# On Windows Chinese locales, PS 5.1 defaults to GBK and corrupts multi-byte +# JSON (e.g. 紧急/新增), which makes ConvertFrom-Json fail silently. +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$OutputEncoding = [System.Text.Encoding]::UTF8 $Script:GL = "gitlink-cli" @@ -30,31 +36,41 @@ function Check-Auth { } # -- CLI Wrapper -- +# Returns parsed JSON object on success, $null on failure +# Usage: Invoke-GL @("issue", "+list", "--owner", $Owner, "--repo", $Repo) function Invoke-GL { - param([string[]]$Args) - $output = & $Script:GL @Args --format json 2>&1 - return ($output -join "`n") -} - -function Invoke-GLCheck { - param([string[]]$Args) - $output = Invoke-GL $Args + param([string[]]$CmdArgs) + $allArgs = @($CmdArgs) + @("--format", "json") + # Capture stdout only; stderr goes to console + $output = & $Script:GL @allArgs + $raw = ($output -join "`n") + if (-not $raw -or $raw.Trim() -eq "") { return $null } try { - $json = $output | ConvertFrom-Json - if (-not $json.ok) { - $errMsg = if ($json.error.message) { $json.error.message } else { "unknown error" } - Log-Err "Command failed: $Script:GL $($Args -join ' ')" - Log-Err $errMsg - return $null - } - return $json + return ($raw | ConvertFrom-Json) } catch { - Log-Err "Command failed (non-JSON): $Script:GL $($Args -join ' ')" - Log-Err $output return $null } } +# Like Invoke-GL but logs error on failure +function Invoke-GLCheck { + param([string[]]$CmdArgs) + $json = Invoke-GL $CmdArgs + if (-not $json) { + $argStr = $CmdArgs -join " " + Log-Err "Command failed (no JSON): $Script:GL $argStr" + return $null + } + if (-not $json.ok) { + $argStr = $CmdArgs -join " " + $errMsg = if ($json.error -and $json.error.message) { $json.error.message } else { "unknown error" } + Log-Err "Command failed: $Script:GL $argStr" + Log-Err $errMsg + return $null + } + return $json +} + # -- JSON Helpers -- function Get-JsonOk { param($Json) diff --git a/workflows/lib/common.sh b/workflows/lib/common.sh index 4a16f201..e593fa30 100644 --- a/workflows/lib/common.sh +++ b/workflows/lib/common.sh @@ -167,3 +167,139 @@ parse_common_args() { divider() { echo -e "${CYAN}────────────────────────────────────────────────${NC}" } + +# ══════════════════════════════════════════════════════════════════════ +# Scientific Research Helpers +# ══════════════════════════════════════════════════════════════════════ + +# Normalize value against max (returns 0-1, 0 if max is 0) +normalize() { + awk -v val="$1" -v max="$2" 'BEGIN { printf "%.4f", (max > 0) ? val / max : 0 }' +} + +# Clamp value between lo and hi +clamp() { + awk -v val="$1" -v lo="$2" -v hi="$3" 'BEGIN { printf "%.4f", (val < lo) ? lo : ((val > hi) ? hi : val) }' +} + +# Weighted sum: pass pairs of "value weight" as arguments +weighted_sum() { + awk 'BEGIN { sum=0; for(i=1;i 0) ? inter / union : 0; + }' +} + +# Detect programming language from file extension +detect_lang_from_ext() { + local ext="${1##*.}" + case "$ext" in + go) echo "Go" ;; + py|pyx) echo "Python" ;; + js|ts|jsx|tsx|mjs|cjs) echo "JavaScript/TypeScript" ;; + rs) echo "Rust" ;; + java) echo "Java" ;; + kt|kts) echo "Kotlin" ;; + c|cpp|cxx|h|hpp|hxx) echo "C/C++" ;; + r|R) echo "R" ;; + jl) echo "Julia" ;; + m|mm) echo "MATLAB/Objective-C" ;; + swift) echo "Swift" ;; + rb) echo "Ruby" ;; + php) echo "PHP" ;; + scala) echo "Scala" ;; + dart) echo "Dart" ;; + lua) echo "Lua" ;; + ipynb) echo "Jupyter Notebook" ;; + sh|bash|zsh) echo "Shell" ;; + ps1|psm1|psd1) echo "PowerShell" ;; + *) echo "Other" ;; + esac +} + +# Cross-platform days between two dates (YYYY-MM-DD format) +days_between() { + local d1 d2 diff + d1=$(date -d "$1" +%s 2>/dev/null || date -jf "%Y-%m-%d" "$1" +%s 2>/dev/null || echo "0") + d2=$(date -d "$2" +%s 2>/dev/null || date -jf "%Y-%m-%d" "$2" +%s 2>/dev/null || echo "0") + diff=$(( (d2 - d1) / 86400 )) + echo "${diff#-}" +} + +# Get today, N days ago (cross-platform) +date_days_ago() { + local n="$1" + date -d "$n days ago" +%Y-%m-%d 2>/dev/null || date -v-"$n"d +%Y-%m-%d 2>/dev/null +} + +# Extract first N space-separated authors into BibTeX format +# Input: "First Last" "First2 Last2" ... +format_authors_bibtex() { + local names="$1" count=0 result="" + for name in $names; do + count=$((count + 1)) + if [[ $count -gt 10 ]]; then + result="${result} and others" + break + fi + local last="${name##* }" first="${name%% *}" + [[ $count -gt 1 ]] && result="${result} and " + result="${result}${last}, ${first}" + done + echo "$result" +} + +# Extract organization name from login (try git remote or repo +info) +org_from_owner() { + local owner="$1" + # check if it's an org or user by listing repos + local out + out=$(gl_run repo +list --user "$owner" --limit 1) + if [[ "$(json_ok "$out")" == "true" ]]; then + echo "$owner" + else + echo "" + fi +} + +# Min and max helpers for awk +min_val() { awk -v a="$1" -v b="$2" 'BEGIN { print (a < b) ? a : b }'; } +max_val() { awk -v a="$1" -v b="$2" 'BEGIN { print (a > b) ? a : b }'; } + +# ── Knowledge Graph Helpers ────────────────────────────────────────── + +# Generate a unique node ID +kg_node_id() { echo "${1}:${2}" | tr '/' '_' | tr ' ' '_'; } + +# URL-encode a string (basic) +url_encode() { + local str="$1" + echo "$str" | jq -sRr @uri 2>/dev/null || echo "$str" +} + +# Escape JSON string value +json_escape() { + echo "$1" | jq -Rsa . 2>/dev/null || echo "\"$1\"" +} + +# ── Color-coded Severity ────────────────────────────────────────────── + +severity_color() { + case "$1" in + Critical|critical|CRITICAL) echo -e "${RED}$1${NC}" ;; + Warning|warning|WARNING) echo -e "${YELLOW}$1${NC}" ;; + Info|info|INFO) echo -e "${CYAN}$1${NC}" ;; + OK|ok|CLEAN) echo -e "${GREEN}$1${NC}" ;; + *) echo "$1" ;; + esac +} diff --git a/workflows/lib/research-common.psm1 b/workflows/lib/research-common.psm1 new file mode 100644 index 00000000..7e9a89a8 --- /dev/null +++ b/workflows/lib/research-common.psm1 @@ -0,0 +1,69 @@ +# GitLink 科研辅助 — PowerShell 公共模块扩展 +# 在 common.psm1 基础上增加科研计算函数 + +# -- Math helpers -- +function Get-Normalized { + param([double]$Value, [double]$Max) + if ($Max -le 0) { return 0 } + return [Math]::Round($Value / $Max, 4) +} +function Get-Clamped { + param([double]$Value, [double]$Lo, [double]$Hi) + return [Math]::Max($Lo, [Math]::Min($Value, $Hi)) +} +function Get-WeightedSum { + param([double[]]$Pairs) + $sum = 0.0 + for ($i = 0; $i -lt $Pairs.Count; $i += 2) { + $sum += $Pairs[$i] * $Pairs[$i + 1] + } + return [Math]::Round($sum, 4) +} +function Get-Jaccard { + param([string[]]$ListA, [string[]]$ListB) + $setA = @{}; foreach ($a in $ListA) { $setA[$a.Trim()] = 1 } + $inter = 0; foreach ($b in $ListB) { if ($setA.ContainsKey($b.Trim())) { $inter++ } } + $union = ($setA.Keys + ($ListB | ForEach-Object { $_.Trim() }) | Select-Object -Unique).Count + if ($union -le 0) { return 0 } + return [Math]::Round($inter / $union, 4) +} +function Get-DaysBetween { + param([string]$Date1, [string]$Date2) + try { return ([DateTime]$Date2 - [DateTime]$Date1).Days } + catch { return 365 } +} +function Get-DateToday { return (Get-Date -Format "yyyy-MM-dd") } + +# -- Citation helpers -- +function Format-AuthorsBibtex { + param([string[]]$Authors) + $result = ""; $count = 0 + foreach ($a in $Authors) { + if (-not $a) { continue } + $count++ + if ($count -gt 5) { $result += " and others"; break } + if ($count -gt 1) { $result += " and " } + $parts = $a -split '\s+' + $last = $parts[-1]; $first = $parts[0] + $result += "$last, $first" + } + return $result +} +function Format-AuthorsAPA { + param([string[]]$Authors) + $result = ""; $count = 0; $total = ($Authors | Where-Object { $_ }).Count + foreach ($a in $Authors) { + if (-not $a) { continue } + $count++ + if ($count -gt 5) { $result += ", et al."; break } + if ($count -eq 1) { } + elseif ($count -eq $total -or $count -eq 5) { $result += ", & " } + else { $result += ", " } + $parts = $a -split '\s+' + $last = $parts[-1]; $first = $parts[0][0] + $result += "$last, $first." + } + return $result +} + +Export-ModuleMember -Function Get-Normalized, Get-Clamped, Get-WeightedSum, Get-Jaccard, Get-DaysBetween, Get-DateToday, Format-AuthorsBibtex, Format-AuthorsAPA diff --git a/workflows/schemas/knowledge-graph.schema.json b/workflows/schemas/knowledge-graph.schema.json new file mode 100644 index 00000000..3dadb2f7 --- /dev/null +++ b/workflows/schemas/knowledge-graph.schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://gitlink.org.cn/schemas/research-knowledge-graph.json", + "title": "Research Knowledge Graph", + "description": "Schema for the research knowledge graph generated from GitLink repository analysis", + "type": "object", + "required": ["metadata", "nodes", "edges"], + "properties": { + "metadata": { + "type": "object", + "required": ["generated_at", "search_keywords"], + "properties": { + "generated_at": { "type": "string", "format": "date-time" }, + "search_keywords": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "total_repos_scanned": { "type": "integer", "minimum": 0 }, + "total_contributors_found": { "type": "integer", "minimum": 0 }, + "total_edges_inferred": { "type": "integer", "minimum": 0 }, + "api_version": { "type": "string" } + } + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "label"], + "properties": { + "id": { + "type": "string", + "description": "Unique node identifier, e.g. 'repo:owner/repo-name' or 'topic:NLP'", + "pattern": "^[a-z_]+:.+$" + }, + "type": { + "type": "string", + "description": "Node type", + "enum": ["repo", "contributor", "topic", "paper", "organization", "release"] + }, + "label": { + "type": "string", + "description": "Human-readable display name" + }, + "description": { + "type": "string", + "description": "Optional description for tooltip" + }, + "properties": { + "type": "object", + "description": "Type-specific attributes" + } + } + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "required": ["source", "target", "type"], + "properties": { + "source": { + "type": "string", + "description": "Source node ID" + }, + "target": { + "type": "string", + "description": "Target node ID" + }, + "type": { + "type": "string", + "description": "Relationship type", + "enum": [ + "depends_on", + "cites", + "contributes_to", + "forks_from", + "has_topic", + "collaborates_with", + "releases", + "references_paper", + "implements_method", + "related_to" + ] + }, + "weight": { + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0.5, + "description": "Edge weight / confidence" + }, + "evidence": { + "type": "string", + "description": "How this edge was inferred" + } + } + } + } + } +} diff --git a/workflows/templates/knowledge-graph.html b/workflows/templates/knowledge-graph.html new file mode 100644 index 00000000..63f69b42 --- /dev/null +++ b/workflows/templates/knowledge-graph.html @@ -0,0 +1,110 @@ + + + + + +科研知识图谱 — {{REPORT_DATE}} + + + + +
+

科研知识图谱

+
+ 关键词:{{KEYWORDS}} — + 仓库:{{TOTAL_REPOS}} 个 — + 贡献者:{{TOTAL_CONTRIBUTORS}} 人 — + {{REPORT_DATE}} +
+
+
+ +
+
{{TOTAL_REPOS}}
仓库节点
+
{{TOTAL_CONTRIBUTORS}}
贡献者节点
+
{{TOTAL_TOPICS}}
主题节点
+
{{TOTAL_EDGES}}
关系边
+
{{HOTTEST_REPO}}
最热仓库
+
+ +
+

知识图谱 — 力导向布局

+
+
+ +
+

热度排行榜

+ + + {{TABLE_ROWS}} +
排名仓库热度语言Stars趋势
+
+ +
+ + + + +