forked from Gitlink/gitlink-cli
feat(子任务四): gitlink-spark + fair v2 科研软件 X 光 + 真科研图谱修复 #14
|
|
@ -0,0 +1,846 @@
|
|||
# gitlink-spark Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build a `gitlink-spark` Skill + runnable `spark.py` that mines "literature↔code semantic gaps" across arXiv × GitLink × GitHub and outputs an opportunity report with optional fork+issue kickoff.
|
||||
|
||||
**Architecture:** `scripts/spark.py` is a standalone data-fusion script (fetches real data → JSON). `SKILL.md` orchestrates an LLM that reads the JSON, semantically matches two gap types (theory-no-impl / demand-no-solution) with evidence triples, renders an opportunity report, and optionally forks+issues to kickoff. Pure functions are unit-tested; the live GNN run is integration validation.
|
||||
|
||||
**Tech Stack:** Python 3.11 stdlib only (urllib, subprocess, xml.etree, json, argparse) — no pip deps. gitlink-cli. arXiv Atom API + GitHub Search REST.
|
||||
|
||||
**Source of truth:** `docs/superpowers/specs/2026-07-01-gitlink-spark-design.md` (read it first).
|
||||
|
||||
---
|
||||
|
||||
## Environment & Gotchas (engineer must know)
|
||||
|
||||
- **Branch:** on `feat/gitlink-research-fair` (spark is 子任务四第二部分, shares PR #5). Commit only your own files; leave pre-existing `D README_TASKB.md` / `D gitlink-cli.exe` / `?? dist/` / `?? _edge_prescription/` untouched.
|
||||
- **Encoding:** all python that touches Chinese gitlink-cli output MUST run with `PYTHONUTF8=1 PYTHONIOENCODING=utf-8`. Never inline Chinese in `python -c` — write a `.py` file.
|
||||
- **arXiv must be HTTPS** (`https://export.arxiv.org`); plain HTTP is sandbox-blocked (returns 0 bytes).
|
||||
- **GitHub unauthenticated = 10 req/min** → `fetch_github_count` sleeps ~7s between calls. Set `GITHUB_TOKEN` env to raise to 5000/h. Cache by query key.
|
||||
- **`gitlink-cli search +issues` returns HTML (broken)** — never use it. Use per-repo `gitlink-cli issue +list --owner X --repo Y --state open` (returns JSON).
|
||||
- **`--repo` uses identifier** (ASCII slug), not Chinese display name.
|
||||
- **No `pip install`** — spark.py uses stdlib only. Tests run via `python test_spark.py` (assert-based, no pytest).
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility |
|
||||
|------|----------------|
|
||||
| `skills/gitlink-spark/scripts/spark.py` | Standalone data fusion: arXiv + gitlink-cli + GitHub → JSON on stdout. Pure parsers + network fetchers + main() |
|
||||
| `skills/gitlink-spark/scripts/test_spark.py` | Assert-based unit tests for pure parsers (parse_arxiv_atom, parse_github_search, extract_method_keywords) |
|
||||
| `skills/gitlink-spark/SKILL.md` | 4-stage orchestration, gap taxonomy, GitHub threshold rule, report template, kickoff guardrails, error table |
|
||||
| `skills/gitlink-spark/REFERENCE.md` | Gap taxonomy detail, GitHub tiers, LLM prompt template, data-source findings, honesty caveats |
|
||||
| `skills/gitlink-spark/examples/spark-图神经网络.md` | Real GNN run: 2-3 gap cards + kickoff screenshot |
|
||||
| `skills/README.md` | Add gitlink-spark row |
|
||||
| `skills/gitlink-workflow/SKILL.md` | Optional cross-link |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Scaffold SKILL.md (frontmatter + CRITICAL + pipeline + command interface)
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-spark/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Create SKILL.md with frontmatter, CRITICAL headers, 概述, 命令接口, 4-stage pipeline**
|
||||
|
||||
Content (exact):
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: gitlink-spark
|
||||
version: 1.0.0
|
||||
description: "文献-代码语义缺口挖掘机:给一个研究领域,跨 arXiv × GitLink × GitHub 三源挖'有理论无实现/有需求无解答'语义缺口,输出空白学术机会报告,可一键 fork+issue 起跑。当用户需要找研究点、发现论文-代码空白、科研选题启发时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "python skills/gitlink-spark/scripts/spark.py --help"
|
||||
---
|
||||
|
||||
# gitlink-spark(文献-代码语义缺口挖掘机)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 缺口由 LLM 揨断,但每条必须带实证三件套(论文 id / GitLink 查询+命中数 / GitHub total_count);无实证的缺口必须丢弃。**
|
||||
**CRITICAL — 起跑(fork+issue)默认预览确认;绝不自动 merge、绝不 force-push、绝不碰原仓库。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md);缺口分类法、GitHub 阈值、LLM prompt 模板见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
## 概述
|
||||
|
||||
给一个研究领域,跨 **arXiv(学术)× GitLink(中文生态)× GitHub(全球)** 三源挖两类语义缺口,输出**空白学术机会报告**。`scripts/spark.py` 抓真实数据(JSON),LLM 做语义匹配并附实证三件套。与 `gitlink-research-fair`(评估已有)组成"科研辅助双联装"——本 skill 负责**发现空白**。
|
||||
|
||||
## 命令接口
|
||||
|
||||
数据融合脚本(可独立运行):
|
||||
|
||||
```bash
|
||||
python skills/gitlink-spark/scripts/spark.py --field "图神经网络" [--max-papers 10] [--gap-type both|theory|demand] [--github-token $GITHUB_TOKEN]
|
||||
# → stdout: 融合 JSON {papers, gitlink_repos, gitlink_issues, github_counts}
|
||||
```
|
||||
|
||||
skill 约定参数(非 CLI flag):
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--field` | 必填 | 研究领域 |
|
||||
| `--gap-type` | `both` | `theory` / `demand` / `both` |
|
||||
| `--max-papers` | 10 | arXiv 抓取上限(控 GitHub 调用) |
|
||||
| `--auto` | 关 | 跳过预览直接起跑(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出报告,不起跑 |
|
||||
|
||||
## 管道(4 阶段)
|
||||
|
||||
### ① 学术采
|
||||
`spark.py` 调 arXiv HTTPS API 抓领域近 90 天论文(标题/摘要/arxiv id/方法关键词)
|
||||
|
||||
### ② GitLink 采
|
||||
`spark.py` 调 `gitlink-cli search +repos` 抓领域仓库;对每个仓库 `issue +list --state open` 抓 open issue(**不用 search +issues**,它返回 HTML)
|
||||
|
||||
### ③ 全球对照
|
||||
`spark.py` 调 GitHub Search API 对每个论文方法查 `total_count` + Top3 仓库(限流+缓存)
|
||||
|
||||
### ④ 缺口匹配(LLM)+ 报告 + 起跑
|
||||
读 spark.py 的 JSON → 语义匹配两类缺口(每张带实证三件套)→ 渲染机会报告 → 可选 fork+issue 起跑
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run: `head -30 skills/gitlink-spark/SKILL.md | grep -c -e "name: gitlink-spark" -e "description:" -e "CRITICAL" -e "管道(4 阶段)"`
|
||||
Expected: `6`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/SKILL.md
|
||||
git commit -m "feat(spark): scaffold SKILL.md(frontmatter+CRITICAL+4阶段管道)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: SKILL.md — gap taxonomy + evidence triples + GitHub thresholds + report + guardrails
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-spark/SKILL.md` (append after 管道)
|
||||
|
||||
- [ ] **Step 1: Append gap taxonomy, thresholds, report template, kickoff, error table**
|
||||
|
||||
Append (exact):
|
||||
|
||||
```markdown
|
||||
## 两类缺口 + 实证三件套(信服核心)
|
||||
|
||||
每张缺口卡**必须**带齐三件套,否则丢弃(防 LLM 编造):
|
||||
|
||||
### 类型 A:有理论无实现(paper → code gap)
|
||||
- **三件套**:① 论文 arxiv id + 标题 + 发表日期 ② GitLink 搜索查询串 + 命中数(0/极少) ③ GitHub total_count + Top 仓库
|
||||
- LLM 判定:论文提出方法 M;GitLink 实现 0/极少;GitHub 按下方阈值分级
|
||||
|
||||
### 类型 B:有需求无解答(open issue → applied gap)
|
||||
- **三件套**:① issue URL + 主题 + 讨论人数/状态 ② GitLink 无现成实现解此痛点 ③ GitHub 是否有成熟开源解
|
||||
- 降噪:LLM 只挑"研究性痛点"(性能/可扩展/新场景),排除"安装报错"等使用问题
|
||||
|
||||
## GitHub 全球对照阈值(诚实核心,硬需求)
|
||||
|
||||
防止"GitLink 0 ≠ 全球空白"误导。对每个"理论无实现"候选按 GitHub total_count 分级:
|
||||
|
||||
| GitHub total_count | 分级 | 报告行为 |
|
||||
|--------------------|------|----------|
|
||||
| `< 10` | 全球稀缺(真空白) | 报为高价值缺口 |
|
||||
| `10–50` | 新兴(部分空白) | 报为中等缺口("GitLink 空白,全球新兴") |
|
||||
| `≥ 50` | 全球已成熟 | **不报为空白**,列入"✅ 已诚实排除" |
|
||||
|
||||
宁可少报,不误报机会。
|
||||
|
||||
## 机会报告格式(hero)
|
||||
|
||||
````markdown
|
||||
⚡ **gitlink-spark 机会报告:<field>**
|
||||
|
||||
学术采:arXiv 近 90 天 N 篇 | GitLink 仓库 M 个 | GitHub 全球基线已对照
|
||||
生成时间:YYYY-MM-DD
|
||||
|
||||
### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值]
|
||||
**论文**:[arxiv:<id>] "<title>" (<date>)
|
||||
**方法关键词**:<...>
|
||||
**GitLink**:search "<query>" → **0 命中**(查询串留底)
|
||||
**GitHub 全球**:total_count = **N**(Top: <repo> <stars>⭐)→ 稀缺
|
||||
**机会建议**:<LLM 一句话>
|
||||
**起跑**:[按钮] fork 基准 <repo> → 创建 issue 粘论文伪代码
|
||||
|
||||
### 🧩 缺口 2 · 有需求无解答 [应用机会]
|
||||
**Issue**:<repo>#<n> "<subject>"(N 人讨论, open)
|
||||
**痛点**:<LLM 归纳>
|
||||
**GitLink / GitHub**:均无成熟解
|
||||
**机会建议**:<LLM 一句话>
|
||||
|
||||
### ✅ 已诚实排除(非空白)
|
||||
- 论文 Y:GitLink 虽 0,但 GitHub 已 N 个 → 全球已成熟,不报
|
||||
|
||||
---
|
||||
<!-- gitlink-spark v1 | field:<field> | gaps:<N> | date:<YYYY-MM-DD> -->
|
||||
*由 gitlink-spark skill 生成。*
|
||||
````
|
||||
|
||||
## 起跑动作 + 护栏
|
||||
|
||||
选定一张"理论无实现"缺口卡 → 确认 →
|
||||
1. `gitlink-cli repo +fork` 最近基准(GitHub Top 仓库或 GitLink 最近实现)
|
||||
2. LLM 从 arXiv 论文抓 Algorithm/Pseudocode 节
|
||||
3. `gitlink-cli issue +create` 在 fork 建复现 todo issue(body 粘伪代码 + 报告卡摘要)
|
||||
|
||||
**护栏**:默认预览;`--auto` 跳过但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 只出报告。
|
||||
|
||||
## 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| arXiv 空/超时 | HTTPS 重试;仍空降级用既有论文 |
|
||||
| `search +issues` 返回 HTML | 不用,改逐仓库 `issue +list` |
|
||||
| GitHub 未认证限流(10/min) | spark.py sleep ~7s;建议设 `GITHUB_TOKEN` |
|
||||
| GitHub 查询失败 | 该论文标"对照失败",不进缺口判定 |
|
||||
| OpenAlex 503 | 跳过引用富集 |
|
||||
| LLM 缺口无三件套 | 置信度门控丢弃 |
|
||||
| fork/issue 起跑失败 | 输出 fork 目标 + 伪代码文本供手动起跑 |
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run: `grep -c -e "三件套" -e "GitHub total_count" -e "已诚实排除" -e "起跑动作" skills/gitlink-spark/SKILL.md`
|
||||
Expected: `4`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/SKILL.md
|
||||
git commit -m "feat(spark): SKILL 缺口分类法+GitHub阈值+报告模板+起跑护栏+降级"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: spark.py — parse_arxiv_atom + test (TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-spark/scripts/spark.py`
|
||||
- Create: `skills/gitlink-spark/scripts/test_spark.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
`test_spark.py` (exact):
|
||||
|
||||
```python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Assert-based unit tests for spark.py pure parsers. Run: python test_spark.py"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from spark import parse_arxiv_atom, parse_github_search, extract_method_keywords
|
||||
|
||||
SAMPLE_ARXIV = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<id>http://arxiv.org/abs/2403.12345v1</id>
|
||||
<title>Graph Attention Networks with Sparse Transformers</title>
|
||||
<summary>We propose a new graph attention mechanism using sparse attention.</summary>
|
||||
<published>2024-03-15T00:00:00Z</published>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>http://arxiv.org/abs/2404.99999v2</id>
|
||||
<title>Federated Learning on Heterogeneous Graphs</title>
|
||||
<summary>A federated approach for heterogeneous graph neural networks.</summary>
|
||||
<published>2024-04-20T00:00:00Z</published>
|
||||
</entry>
|
||||
</feed>"""
|
||||
|
||||
def test_parse_arxiv_atom():
|
||||
papers = parse_arxiv_atom(SAMPLE_ARXIV)
|
||||
assert len(papers) == 2, f"expected 2 papers, got {len(papers)}"
|
||||
assert papers[0]["arxiv_id"] == "2403.12345v1", papers[0]["arxiv_id"]
|
||||
assert "Graph Attention" in papers[0]["title"]
|
||||
assert papers[0]["published"] == "2024-03-15"
|
||||
assert "sparse" in papers[0]["abstract"].lower()
|
||||
print("test_parse_arxiv_atom OK")
|
||||
|
||||
def test_parse_github_search():
|
||||
import json as _j
|
||||
sample = _j.dumps({"total_count": 1543, "items": [{"full_name": "a/b", "stargazers_count": 3534}]})
|
||||
res = parse_github_search(sample)
|
||||
assert res["total_count"] == 1543
|
||||
assert res["top"][0]["full_name"] == "a/b"
|
||||
assert res["top"][0]["stars"] == 3534
|
||||
print("test_parse_github_search OK")
|
||||
|
||||
def test_extract_method_keywords():
|
||||
kws = extract_method_keywords("Graph Attention Networks", "We propose a sparse attention mechanism for graphs.", max_k=5)
|
||||
assert "graph" in kws and "attention" in kws
|
||||
assert "propose" not in kws # 'propose' is in the stop set, filtered out
|
||||
print("test_extract_method_keywords OK")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parse_arxiv_atom()
|
||||
test_parse_github_search()
|
||||
test_extract_method_keywords()
|
||||
print("ALL TESTS PASSED")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd skills/gitlink-spark/scripts && python test_spark.py`
|
||||
Expected: FAIL with `ModuleNotFoundError: No module named 'spark'`
|
||||
|
||||
- [ ] **Step 3: Write minimal spark.py with parse_arxiv_atom (+ stubs for the other two so import works)**
|
||||
|
||||
`spark.py` (exact):
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gitlink-spark data fusion: arXiv x GitLink x GitHub -> JSON on stdout. Stdlib only."""
|
||||
import argparse, json, os, sys, time, subprocess, urllib.request, urllib.parse, re
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
ARXIV_ENDPOINT = "https://export.arxiv.org/api/query"
|
||||
GITHUB_ENDPOINT = "https://api.github.com/search/repositories"
|
||||
|
||||
_NS = {"a": "http://www.w3.org/2005/Atom"}
|
||||
|
||||
def parse_arxiv_atom(xml_text):
|
||||
"""Parse arXiv Atom feed -> list of {arxiv_id, title, abstract, published}."""
|
||||
root = ET.fromstring(xml_text)
|
||||
papers = []
|
||||
for e in root.findall("a:entry", _NS):
|
||||
aid = (e.find("a:id", _NS).text or "").strip().split("/")[-1]
|
||||
title = re.sub(r"\s+", " ", (e.find("a:title", _NS).text or "").strip())
|
||||
summary = re.sub(r"\s+", " ", (e.find("a:summary", _NS).text or "").strip())
|
||||
pub = (e.find("a:published", _NS).text or "")[:10]
|
||||
papers.append({"arxiv_id": aid, "title": title, "abstract": summary, "published": pub})
|
||||
return papers
|
||||
|
||||
def parse_github_search(json_text):
|
||||
return {"total_count": 0, "top": []} # stub — implemented in Task 4
|
||||
|
||||
def extract_method_keywords(title, abstract, max_k=5):
|
||||
return [] # stub — implemented in Task 4
|
||||
|
||||
def main():
|
||||
pass # implemented in Task 7
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test — arxiv test passes, others fail on stubs**
|
||||
|
||||
Run: `cd skills/gitlink-spark/scripts && python test_spark.py`
|
||||
Expected: `test_parse_arxiv_atom OK`, then FAIL on `test_parse_github_search` (top[0] index error on empty). This confirms arxiv parser works; stubs next.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/scripts/spark.py skills/gitlink-spark/scripts/test_spark.py
|
||||
git commit -m "feat(spark): spark.py parse_arxiv_atom + test(TDD)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: spark.py — parse_github_search + extract_method_keywords (real impl)
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-spark/scripts/spark.py` (replace the two stubs)
|
||||
|
||||
- [ ] **Step 1: Replace the two stub functions with real implementations**
|
||||
|
||||
Replace `def parse_github_search(json_text): ...` and `def extract_method_keywords(...): ...` with:
|
||||
|
||||
```python
|
||||
def parse_github_search(json_text):
|
||||
"""Parse GitHub search JSON -> {total_count, top:[{full_name, stars}]}."""
|
||||
d = json.loads(json_text)
|
||||
return {
|
||||
"total_count": d.get("total_count", 0),
|
||||
"top": [{"full_name": r.get("full_name"), "stars": r.get("stargazers_count")}
|
||||
for r in (d.get("items") or [])[:3]],
|
||||
}
|
||||
|
||||
def extract_method_keywords(title, abstract, max_k=5):
|
||||
"""Crude keyword extraction for GitHub/arXiv query."""
|
||||
text = (title + " " + abstract).lower()
|
||||
stop = {"the", "a", "an", "of", "for", "and", "to", "in", "on", "with", "via",
|
||||
"based", "using", "by", "from", "as", "is", "are", "we", "our", "this",
|
||||
"that", "propose", "proposed", "paper", "method", "approach", "novel", "new"}
|
||||
tokens = re.findall(r"[a-z][a-z0-9-]+", text)
|
||||
seen = set(); out = []
|
||||
for t in tokens:
|
||||
if t in stop or len(t) < 3 or t in seen:
|
||||
continue
|
||||
seen.add(t); out.append(t)
|
||||
if len(out) >= max_k:
|
||||
break
|
||||
return out
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run all tests — expect ALL PASS**
|
||||
|
||||
Run: `cd skills/gitlink-spark/scripts && python test_spark.py`
|
||||
Expected: `ALL TESTS PASSED` (all 3 tests)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/scripts/spark.py
|
||||
git commit -m "feat(spark): parse_github_search + extract_method_keywords 实现(3 测试全过)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: spark.py — fetch_arxiv (network)
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-spark/scripts/spark.py` (add fetch_arxiv before main)
|
||||
|
||||
- [ ] **Step 1: Add fetch_arxiv**
|
||||
|
||||
Insert before `def main()`:
|
||||
|
||||
```python
|
||||
def fetch_arxiv(field, max_papers=10):
|
||||
"""Search arXiv (HTTPS) for recent papers in field. Returns list of paper dicts."""
|
||||
q = urllib.parse.quote(f'abs:"{field}"')
|
||||
url = (f"{ARXIV_ENDPOINT}?search_query={q}&max_results={max_papers}"
|
||||
f"&sortBy=submittedDate&sortOrder=descending")
|
||||
with urllib.request.urlopen(url, timeout=30) as r:
|
||||
papers = parse_arxiv_atom(r.read().decode("utf-8", "replace"))
|
||||
for p in papers:
|
||||
p["method_keywords"] = extract_method_keywords(p["title"], p["abstract"])
|
||||
return papers
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Smoke test fetch_arxiv (live network)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd skills/gitlink-spark/scripts && PYTHONUTF8=1 python -c "
|
||||
from spark import fetch_arxiv
|
||||
ps = fetch_arxiv('graph neural network', max_papers=2)
|
||||
assert len(ps) >= 1, 'no papers'
|
||||
p = ps[0]
|
||||
assert p['arxiv_id'] and p['title'] and p['published']
|
||||
assert isinstance(p['method_keywords'], list)
|
||||
print('OK', p['arxiv_id'], '|', p['title'][:50])
|
||||
"
|
||||
```
|
||||
Expected: `OK 2504.xxxxx | <recent GNN paper title>` (a real recent arxiv id). If 0 bytes, confirm HTTPS (not HTTP).
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/scripts/spark.py
|
||||
git commit -m "feat(spark): fetch_arxiv(arXiv HTTPS 网络层)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: spark.py — fetch_gitlink_repos + fetch_gitlink_issues
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-spark/scripts/spark.py`
|
||||
|
||||
- [ ] **Step 1: Add _gitlink helper + the two fetchers**
|
||||
|
||||
Insert before `def main()`:
|
||||
|
||||
```python
|
||||
def _gitlink(*args):
|
||||
"""Run gitlink-cli with json output; return parsed dict (UTF-8 safe)."""
|
||||
r = subprocess.run(["gitlink-cli"] + list(args) + ["--format", "json"],
|
||||
capture_output=True, text=True, encoding="utf-8",
|
||||
errors="replace", timeout=60)
|
||||
raw = r.stdout
|
||||
i = raw.find("{")
|
||||
return json.loads(raw[i:]) if i >= 0 else {}
|
||||
|
||||
def fetch_gitlink_repos(field):
|
||||
"""gitlink-cli search +repos -> list of {owner, repo(identifier), name, desc, topics}."""
|
||||
d = _gitlink("search", "+repos", "-k", field)
|
||||
projs = d.get("data", {}).get("projects", []) or []
|
||||
out = []
|
||||
for p in projs:
|
||||
out.append({
|
||||
"owner": (p.get("author") or {}).get("login"),
|
||||
"repo": p.get("identifier"),
|
||||
"name": p.get("name"),
|
||||
"desc": p.get("description"),
|
||||
"topics": [t.get("name") if isinstance(t, dict) else t for t in (p.get("topics") or [])],
|
||||
})
|
||||
return out
|
||||
|
||||
def fetch_gitlink_issues(repos, max_per_repo=10):
|
||||
"""Per-repo issue +list (open) -> list of {repo, number, subject, status, participants}.
|
||||
Works around search +issues returning HTML."""
|
||||
out = []
|
||||
for r in repos:
|
||||
if not (r.get("owner") and r.get("repo")):
|
||||
continue
|
||||
d = _gitlink("issue", "+list", "--owner", r["owner"], "--repo", r["repo"], "--state", "open")
|
||||
data = d.get("data", {}) or {}
|
||||
issues = data.get("issues") or []
|
||||
for it in issues[:max_per_repo]:
|
||||
st = (it.get("status") or {})
|
||||
if st.get("name") == "关闭":
|
||||
continue
|
||||
out.append({
|
||||
"repo": f'{r["owner"]}/{r["repo"]}',
|
||||
"number": it.get("project_issues_index") or it.get("number"),
|
||||
"subject": it.get("subject"),
|
||||
"status": st.get("name"),
|
||||
"participants": it.get("participants_count") or 0,
|
||||
})
|
||||
return out
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Smoke test (live)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd skills/gitlink-spark/scripts && PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python -c "
|
||||
from spark import fetch_gitlink_repos, fetch_gitlink_issues
|
||||
repos = fetch_gitlink_repos('图神经网络')
|
||||
print('repos:', len(repos))
|
||||
if repos: print(' sample:', repos[0]['owner'], '/', repos[0]['repo'])
|
||||
iss = fetch_gitlink_issues(repos[:2])
|
||||
print('issues(from first 2 repos):', len(iss))
|
||||
"
|
||||
```
|
||||
Expected: `repos: N` (N≥1, includes GraphGallery-class), `issues: M`. If `repos: 0`, the field keyword missed — retry with `'graph neural'`. If issues JSON parse fails, inspect gitlink-cli `issue +list` structure and adapt field names.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/scripts/spark.py
|
||||
git commit -m "feat(spark): fetch_gitlink_repos + fetch_gitlink_issues(逐仓库,绕开 search+issues HTML)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: spark.py — fetch_github_count + main() + end-to-end smoke
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-spark/scripts/spark.py`
|
||||
|
||||
- [ ] **Step 1: Add fetch_github_count (cache+throttle) and implement main()**
|
||||
|
||||
Replace `def main(): pass` and add fetch_github_count before it:
|
||||
|
||||
```python
|
||||
_GH_CACHE = {}
|
||||
|
||||
def fetch_github_count(query, token=None, throttle=True):
|
||||
"""GitHub search total_count + top3 for a query. Caches + throttles (10/min unauth)."""
|
||||
if query in _GH_CACHE:
|
||||
return _GH_CACHE[query]
|
||||
url = f"{GITHUB_ENDPOINT}?q={urllib.parse.quote(query)}&per_page=3&sort=stars"
|
||||
req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "gitlink-spark/1.0"})
|
||||
if token:
|
||||
req.add_header("Authorization", f"Bearer {token}")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=25) as r:
|
||||
res = parse_github_search(r.read().decode("utf-8", "replace"))
|
||||
except Exception as e:
|
||||
res = {"total_count": None, "top": [], "error": str(e)[:80]}
|
||||
if throttle and not token:
|
||||
time.sleep(7) # unauthenticated = 10 req/min
|
||||
_GH_CACHE[query] = res
|
||||
return res
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="gitlink-spark data fusion")
|
||||
ap.add_argument("--field", required=True)
|
||||
ap.add_argument("--max-papers", type=int, default=10)
|
||||
ap.add_argument("--gap-type", default="both", choices=["both", "theory", "demand"])
|
||||
ap.add_argument("--github-token", default=os.environ.get("GITHUB_TOKEN"))
|
||||
args = ap.parse_args()
|
||||
|
||||
papers = fetch_arxiv(args.field, args.max_papers)
|
||||
grepos = fetch_gitlink_repos(args.field)
|
||||
gissues = fetch_gitlink_issues(grepos) if args.gap_type in ("both", "demand") else []
|
||||
gh_counts = {}
|
||||
if args.gap_type in ("both", "theory"):
|
||||
for p in papers:
|
||||
q = p["title"][:60] # primary query = paper title (truncated)
|
||||
gh_counts[q] = fetch_github_count(q, args.github_token)
|
||||
|
||||
out = {
|
||||
"field": args.field,
|
||||
"papers": papers,
|
||||
"gitlink_repos": grepos,
|
||||
"gitlink_issues": gissues,
|
||||
"github_counts": gh_counts,
|
||||
}
|
||||
json.dump(out, sys.stdout, ensure_ascii=False, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: End-to-end smoke (tiny, live)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd skills/gitlink-spark/scripts && PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python spark.py --field "graph neural network" --max-papers 3 --gap-type theory > _smoke.json 2>&1
|
||||
PYTHONUTF8=1 python -c "
|
||||
import json
|
||||
d=json.load(open('_smoke.json',encoding='utf-8'))
|
||||
print('field:', d['field'])
|
||||
print('papers:', len(d['papers']), '| gitlink_repos:', len(d['gitlink_repos']))
|
||||
print('github_counts keys:', len(d['github_counts']))
|
||||
g=d['github_counts']
|
||||
for k,v in list(g.items())[:1]: print(' sample gh:', k[:30], '-> total', v.get('total_count'))
|
||||
"
|
||||
rm -f _smoke.json
|
||||
```
|
||||
Expected: papers=3, gitlink_repos≥1, github_counts has 3 entries with real total_count ints. Takes ~25s (3 GitHub calls × 7s throttle). If GitHub 403 rate-limit, set `GITHUB_TOKEN` env or wait 60s.
|
||||
|
||||
- [ ] **Step 3: Re-run unit tests (regression)**
|
||||
|
||||
Run: `cd skills/gitlink-spark/scripts && python test_spark.py`
|
||||
Expected: `ALL TESTS PASSED`
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/scripts/spark.py
|
||||
git commit -m "feat(spark): fetch_github_count + main() 编排 → JSON(端到端 smoke 通过)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: REFERENCE.md — gap taxonomy detail + GitHub tiers + LLM prompt + data sources
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-spark/REFERENCE.md`
|
||||
|
||||
- [ ] **Step 1: Create REFERENCE.md**
|
||||
|
||||
Content (exact):
|
||||
|
||||
```markdown
|
||||
# gitlink-spark 参考文档
|
||||
|
||||
> SKILL.md 的深度参考:缺口分类细则、GitHub 阈值、LLM prompt 模板、数据源实测、诚实边界。
|
||||
|
||||
## 一、缺口分类法细则
|
||||
|
||||
### 类型 A 有理论无实现
|
||||
- 输入:arXiv 论文方法 M(title + method_keywords)
|
||||
- GitLink 侧:`search +repos -k <M>` 命中数(0 或极少,如 ≤2)
|
||||
- GitHub 侧:`total_count`(按 §二阈值分级)
|
||||
- 判定为"缺口"条件:GitLink ≤2 **且** GitHub < 50(全球稀缺或新兴)
|
||||
|
||||
### 类型 B 有需求无解答
|
||||
- 输入:领域仓库的 open issue(`issue +list`,排除关闭)
|
||||
- LLM 筛"研究性痛点":含性能/可扩展性/新场景/新数据集,排除安装报错/使用咨询
|
||||
- 判定:GitLink 无现成实现解此痛点 **且** GitHub 无成熟开源方案
|
||||
|
||||
## 二、GitHub 全球对照阈值
|
||||
|
||||
| total_count | 分级 | 报告 |
|
||||
|---|---|---|
|
||||
| < 10 | 全球稀缺 | 高价值缺口 |
|
||||
| 10–50 | 新兴 | 中等缺口 |
|
||||
| ≥ 50 | 已成熟 | **不报为空白**,列入"已诚实排除" |
|
||||
|
||||
spark.py 缓存 GitHub 结果(按 query key),避免重复调用。
|
||||
|
||||
## 三、LLM 缺口匹配 prompt 模板
|
||||
|
||||
```
|
||||
你是科研机会发现助手。下面是 spark.py 抓取的真实数据(JSON)。
|
||||
请跨"arXiv 论文 × GitLink 仓库/issues × GitHub 全球计数"找出语义缺口,输出机会报告。
|
||||
|
||||
规则:
|
||||
1. 只输出可溯源到下列数据的缺口;每张缺口卡带"实证三件套"。
|
||||
2. 类型A(理论无实现):论文 M 的 GitLink 命中≤2 且 GitHub total_count<50 才报;
|
||||
GitHub ≥50 的论文列入"已诚实排除",不报为空白。
|
||||
3. 类型B(需求无解答):只挑研究性痛点 issue,排除使用/安装类。
|
||||
4. 每张卡给一句"机会建议"(主观),但证据必须客观可查。
|
||||
5. 宁可少报,不误报。
|
||||
|
||||
数据:
|
||||
{spark.py 的 JSON}
|
||||
```
|
||||
|
||||
## 四、数据源实测结论(2026-07-01)
|
||||
|
||||
| 源 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| arXiv API | ✅ 必须 HTTPS | HTTP 被沙箱阻断返回 0 字节 |
|
||||
| gitlink-cli search +repos | ✅ | 用 identifier/关键词 |
|
||||
| gitlink-cli issue +list | ✅ | 逐仓库,绕开 search+issues |
|
||||
| gitlink-cli search +issues | ❌ 返回 HTML | 不可用,勿用 |
|
||||
| GitHub Search API | ✅ | 未认证 10/min;GITHUB_TOKEN 提额 |
|
||||
| OpenAlex | ⚠ 间歇 503 | best-effort 富集,降级跳过 |
|
||||
|
||||
## 五、诚实边界
|
||||
|
||||
1. **GitLink 覆盖薄**:缺口卡明确标 "GitLink 0 / GitHub N";GitHub ≥50 不报为空白。
|
||||
2. LLM 缺口必须可溯源实证三件套,否则丢弃。
|
||||
3. arXiv 仅覆盖 CS/物理等,报告标注学科范围。
|
||||
4. GitHub 未认证 10/min:spark.py sleep 7s + 缓存;建议 demo 设 GITHUB_TOKEN。
|
||||
5. "机会建议"为主观启发,标注"需研究者自行判断"。
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run: `grep -c -e "类型 A 有理论无实现" -e "GitHub ≥50 不报为空白" -e "search +issues.*HTML" -e "LLM 缺口匹配 prompt" skills/gitlink-spark/REFERENCE.md`
|
||||
Expected: `4`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-spark/REFERENCE.md
|
||||
git commit -m "feat(spark): REFERENCE(缺口分类法+GitHub阈值+LLM prompt+数据源实测)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Live GNN run — produce 2-3 gap cards + ≥1 kickoff
|
||||
|
||||
**Files:**
|
||||
- (no committed code; produces real data feeding Task 10's examples doc)
|
||||
|
||||
- [ ] **Step 1: Run spark.py on GNN (full)**
|
||||
|
||||
```bash
|
||||
cd skills/gitlink-spark/scripts
|
||||
PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python spark.py --field "graph neural network" --max-papers 8 --gap-type both > _gnn.json 2>&1
|
||||
PYTHONUTF8=1 python -c "
|
||||
import json
|
||||
d=json.load(open('_gnn.json',encoding='utf-8'))
|
||||
print('papers:',len(d['papers']),'| repos:',len(d['gitlink_repos']),'| issues:',len(d['gitlink_issues']))
|
||||
# 列出 GitHub 稀缺(<10) 的论文 = 理论缺口候选
|
||||
for p in d['papers']:
|
||||
q=p['title'][:60]; gh=d['github_counts'].get(q,{})
|
||||
tc=gh.get('total_count')
|
||||
if tc is not None and tc < 10:
|
||||
print(' THEORY gap cand:', p['arxiv_id'], '|', p['title'][:45], '| GitHub', tc)
|
||||
"
|
||||
```
|
||||
Expected: papers=8, repos≥1, and ≥1 THEORY gap candidate (GitHub <10). Note the candidate arxiv_ids + GitHub counts. Keep `_gnn.json` for the examples doc.
|
||||
|
||||
- [ ] **Step 2: LLM-match gap cards from _gnn.json**
|
||||
|
||||
Following REFERENCE §三 prompt, produce the opportunity report from `_gnn.json`:
|
||||
- ≥1 "理论无实现" gap card (from Step 1 candidates) — with evidence triple
|
||||
- ≥1 "需求无解答" gap card (from gitlink_issues, if any research-pain issue; if issues empty/none research-y, note honestly and lean on theory gaps + lower the demand bar OR widen field keyword)
|
||||
- ≥1 "已诚实排除" entry (a paper with GitHub ≥50)
|
||||
Save the report text (will go into Task 10 examples). If no demand-side issue exists, be honest: report 2 theory gaps + 1 排除, note demand-side sparse for GNN on GitLink.
|
||||
|
||||
- [ ] **Step 3: Kickoff (fork + issue) on ONE theory gap**
|
||||
|
||||
Pick the best theory gap (GitHub <10). Fork its GitHub top repo's nearest GitLink equivalent OR the GitHub top repo isn't forkable via gitlink-cli (cross-platform) — instead: if a GitLink baseline exists, `gitlink-cli repo +fork` it; else create a todo issue on an existing GitLink GNN repo (e.g. GraphGallery) describing the reproduction plan with the paper's pseudocode.
|
||||
|
||||
```bash
|
||||
# 若有 GitLink 基准仓库,fork 它;否则在 leejt/GraphGallery 开个复现 todo issue
|
||||
gitlink-cli issue +create --owner leejt --repo GraphGallery \
|
||||
--title "Reproduction todo: <paper title> (gitlink-spark 机会)" \
|
||||
--body "<论文 arxiv 链接 + Algorithm 伪代码摘要 + 机会报告卡>"
|
||||
```
|
||||
Capture the issue URL/number. If write fails (no permission on leejt/GraphGallery), fall back: create the issue on your own fork (fork first) OR output the todo text for manual creation. Note the actual outcome.
|
||||
|
||||
- [ ] **Step 4: No commit (data-gathering)** — proceed to Task 10.
|
||||
|
||||
---
|
||||
|
||||
## Task 10: examples/spark-图神经网络.md — real walkthrough
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-spark/examples/spark-图神经网络.md`
|
||||
|
||||
- [ ] **Step 1: Write the real walkthrough using Task 9 outputs**
|
||||
|
||||
Content skeleton (fill with REAL data from `_gnn.json` + Task 9 report/kickoff — no placeholders):
|
||||
|
||||
```markdown
|
||||
# 示例:gitlink-spark GNN 缺口挖掘(真实数据)
|
||||
|
||||
> 基于 `spark.py --field "graph neural network" --max-papers 8` 于 2026-07-XX 实跑。
|
||||
|
||||
## 数据采集(真实)
|
||||
- arXiv 论文:N 篇(近 90 天)
|
||||
- GitLink 仓库:M 个(含 <列举>)
|
||||
- GitLink open issues:K 条
|
||||
- GitHub 全球对照:8 个查询
|
||||
|
||||
## 机会报告(真实全文)
|
||||
<贴 Task 9 §2 的报告全文,含 ≥1 理论缺口 + ≥1 排除,每张带实证三件套>
|
||||
|
||||
## 起跑(真实)
|
||||
<fork/issue 链接或降级说明>
|
||||
|
||||
## 关键结论
|
||||
- 三源融合真实可跑
|
||||
- GitHub 阈值生效(≥50 不报为空白)
|
||||
- 每条缺口可溯源到 spark.py JSON
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify no placeholders**
|
||||
|
||||
Run: `grep -E "TBD|TODO|<论文|<列举|<贴 Task" skills/gitlink-spark/examples/spark-图神经网络.md`
|
||||
Expected: no matches. If any, fill from Task 9 outputs.
|
||||
|
||||
- [ ] **Step 3: Clean temp + commit**
|
||||
|
||||
```bash
|
||||
rm -f skills/gitlink-spark/scripts/_gnn.json
|
||||
git add skills/gitlink-spark/examples/spark-图神经网络.md
|
||||
git commit -m "feat(spark): examples GNN 真实走查(2-3缺口卡+起跑)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 11: README + workflow link + register + acceptance
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/README.md`
|
||||
- Modify: `skills/gitlink-workflow/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Register the skill**
|
||||
|
||||
Run: `bash scripts/setup-skills.sh` then `ls ~/.claude/skills/ | grep gitlink-spark`
|
||||
Expected: `gitlink-spark` listed.
|
||||
|
||||
- [ ] **Step 2: Add README row**
|
||||
|
||||
In `skills/README.md` 智能 Skills table, after the gitlink-research-fair row add:
|
||||
|
||||
```markdown
|
||||
| **gitlink-spark** | 文献-代码语义缺口挖掘机 | arXiv×GitLink×GitHub 三源挖"理论无实现/需求无解答"缺口,出机会报告,一键 fork+issue 起跑 |
|
||||
```
|
||||
|
||||
Verify: `grep -c gitlink-spark skills/README.md` → `≥1`.
|
||||
|
||||
- [ ] **Step 3: workflow cross-link**
|
||||
|
||||
In `skills/gitlink-workflow/SKILL.md` 专项 Skill list, add:
|
||||
```markdown
|
||||
> - 科研机会发现(缺口挖掘) → [`../gitlink-spark/SKILL.md`](../gitlink-spark/SKILL.md)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Acceptance (spec §15)**
|
||||
|
||||
Verify:
|
||||
- [ ] 四件套齐全(SKILL.md + spark.py + REFERENCE.md + examples/)
|
||||
- [ ] `python spark.py --field 图神经网络` 输出合法 JSON
|
||||
- [ ] GNN 跑出 ≥1 理论 + ≥1 demand(或诚实标注 demand 稀疏)缺口卡,三件套可查
|
||||
- [ ] GitHub 阈值生效(≥1 "已诚实排除")
|
||||
- [ ] ≥1 理论缺口走完起跑
|
||||
- [ ] 每条缺口可溯源 spark.py JSON
|
||||
- [ ] REFERENCE 含分类法+数据源实测+诚实边界
|
||||
- [ ] README 登记
|
||||
|
||||
- [ ] **Step 5: Commit + push (updates PR #5)**
|
||||
|
||||
```bash
|
||||
git add skills/README.md skills/gitlink-workflow/SKILL.md
|
||||
git commit -m "docs(spark): README 登记 + workflow 链接 + 注册"
|
||||
git push myfork feat/gitlink-research-fair
|
||||
```
|
||||
|
|
@ -0,0 +1,759 @@
|
|||
# gitlink-research-fair v2 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Upgrade `gitlink-research-fair` from "5-axis FAIR card" to **科研软件 X 光 (Research Software X-Ray)** — a runnable `fair.py` extracts a real research profile (paper/datasets/repro/citation), LLM renders a varied per-repo report (verdict line + real Mermaid knowledge graph + repo-specific findings) with optional prescription.
|
||||
|
||||
**Architecture:** `scripts/fair.py` = stdlib-only deterministic extractor (README regex + file scan → research-profile JSON). `SKILL.md` orchestrates the LLM: 4-dimension verdicts (each citing fair.py evidence) → real knowledge graph → hybrid report → optional fork+PR prescription. Pure extractors are unit-tested; Feature_Critic + Edge live runs are integration validation.
|
||||
|
||||
**Tech Stack:** Python 3.11 stdlib only (argparse/json/subprocess/re). gitlink-cli (file +get/+list, repo +info, commit +list). Mermaid for the graph.
|
||||
|
||||
**Source of truth:** `docs/superpowers/specs/2026-07-07-gitlink-research-fair-v2-design.md` (read it first).
|
||||
|
||||
---
|
||||
|
||||
## Environment & Gotchas (engineer must know)
|
||||
|
||||
- **Branch:** `feat/gitlink-research-fair` (v2 升级与 v1/spark 同 PR #5). Commit only your own files; leave pre-existing `D README_TASKB.md` / `D gitlink-cli.exe` / `?? dist/` / `?? _edge_prescription/` / `?? report-cards/` untouched.
|
||||
- **Encoding:** python touching gitlink-cli output MUST run `PYTHONUTF8=1 PYTHONIOENCODING=utf-8`. Never inline Chinese in `python -c` — write a `.py` file.
|
||||
- **`file +list` returns `data` as a stringified JSON** → `json.loads` it (known structure, see Task 3).
|
||||
- **`file +get` content lives at `data.entries.content`** (plain text, not base64).
|
||||
- **`--repo` uses identifier** (ASCII slug, e.g. `Feature_Critic`), not Chinese display name.
|
||||
- **stdlib only** — no pip. Tests run via `python test_fair.py` (assert-based, no pytest).
|
||||
- **v1 to remove:** the old `examples/research-fair-workflow.md` (songhui18 v1 report card) is replaced by v2 examples (Task 8 `git rm`s it).
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility |
|
||||
|------|----------------|
|
||||
| `skills/gitlink-research-fair/scripts/fair.py` | Stdlib extractor: README regex + file scan → research-profile JSON. Pure extractors + gitlink-cli fetchers + main() |
|
||||
| `skills/gitlink-research-fair/scripts/test_fair.py` | Assert-based unit tests for pure extractors (extract_paper, extract_datasets, assess_repro, assess_citation, extract_methods_frameworks) |
|
||||
| `skills/gitlink-research-fair/SKILL.md` | REWRITE: X-ray pipeline, 4-dim verdict table, hybrid report template, real KG section, prescription guardrails, degradation table |
|
||||
| `skills/gitlink-research-fair/REFERENCE.md` | REWRITE: 4-dim verdict rules, real KG schema, extraction rules, data-source findings, FAIR4RS anchor |
|
||||
| `skills/gitlink-research-fair/examples/feature-critic-xray.md` | Real Feature_Critic X-ray (live-demo script) |
|
||||
| `skills/gitlink-research-fair/examples/edge-xray.md` | Real Edge X-ray (engine-class contrast: license-conflict finding) |
|
||||
| `skills/README.md` + `skills/gitlink-workflow/SKILL.md` | Update fair description → "科研软件 X 光" |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: fair.py — extract_paper + extract_datasets + tests (TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-research-fair/scripts/fair.py`
|
||||
- Create: `skills/gitlink-research-fair/scripts/test_fair.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
`test_fair.py` (exact):
|
||||
|
||||
```python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Assert-based unit tests for fair.py pure extractors. Run: python test_fair.py"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from fair import extract_paper, extract_datasets, assess_repro, assess_citation, extract_methods_frameworks
|
||||
|
||||
SAMPLE_README = """# Feature_Critic
|
||||
Demo code for 'Feature-Critic Networks for Heterogeneous Domain Generalisation'.
|
||||
This paper is located at https://arxiv.org/abs/1901.11448 and will appear in ICML 2019.
|
||||
Evaluated on PACS and Visual Decathlon.
|
||||
|
||||
@inproceedings{li2019feature,
|
||||
title={Feature-Critic Networks},
|
||||
booktitle={ICML}}
|
||||
"""
|
||||
|
||||
SAMPLE_FILES = ["README.md", "main_Feature_Critic.py", "main_baseline.py", "model_PACS.py",
|
||||
"alexnet.py", "resnet.py", "vggnet.py", "data_gen_PACS.py", "get_model_dataset.sh", "utils.py"]
|
||||
|
||||
def test_extract_paper():
|
||||
p = extract_paper(SAMPLE_README)
|
||||
assert p["arxiv_id"] == "1901.11448", p["arxiv_id"]
|
||||
assert p["arxiv_url"] == "https://arxiv.org/abs/1901.11448"
|
||||
assert p["venue"] == "ICML"
|
||||
assert p["in_readme"] is True
|
||||
print("test_extract_paper OK")
|
||||
|
||||
def test_extract_paper_none():
|
||||
p = extract_paper("# Hello\nA normal project with no paper.")
|
||||
assert p["in_readme"] in (False, True) # title-only may set in_readme; arxiv must be None
|
||||
assert p["arxiv_id"] is None
|
||||
print("test_extract_paper_none OK")
|
||||
|
||||
def test_extract_datasets():
|
||||
ds = extract_datasets(SAMPLE_README, SAMPLE_FILES)
|
||||
names = [d["name"] for d in ds]
|
||||
assert "PACS" in names and "Visual Decathlon" in names
|
||||
pacs = [d for d in ds if d["name"] == "PACS"][0]
|
||||
assert pacs["download_script"] and "data_gen_PACS.py" in pacs["download_script"]
|
||||
print("test_extract_datasets OK")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_extract_paper(); test_extract_paper_none(); test_extract_datasets()
|
||||
print("PART 1 OK (run all after Task 2)")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd skills/gitlink-research-fair/scripts && python test_fair.py`
|
||||
Expected: FAIL with `ModuleNotFoundError: No module named 'fair'`
|
||||
|
||||
- [ ] **Step 3: Write fair.py with extract_paper + extract_datasets (+ stubs for Task 2 functions so import works)**
|
||||
|
||||
`fair.py` (exact):
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gitlink-research-fair v2: research software X-ray. Extract a research profile from a GitLink repo. Stdlib only."""
|
||||
import argparse, json, os, sys, subprocess, re
|
||||
|
||||
_ARXIV_PATS = [
|
||||
r'https?://arxiv\.org/(?:abs|pdf)/(\d{4}\.\d{4,5})',
|
||||
r'arXiv:(\d{4}\.\d{4,5})',
|
||||
r'\b(\d{4}\.\d{4,5})\b',
|
||||
]
|
||||
_DOI_PAT = r'10\.\d{4,9}/\S+'
|
||||
_VENUES = ["ICML", "NeurIPS", "NIPS", "ICLR", "CVPR", "ICCV", "ECCV", "ACL", "EMNLP",
|
||||
"NAACL", "KDD", "WWW", "AAAI", "IJCAI", "SIGGRAPH", "Nature", "Science"]
|
||||
_KNOWN_DATASETS = ["Visual Decathlon", "PACS", "ImageNet", "CIFAR-10", "CIFAR-100", "CIFAR",
|
||||
"Cora", "Citeseer", "Pubmed", "MNIST", "COCO", "QM9", "ZINC", "OGB", "ogbn",
|
||||
"Wikipedia", "PPI", "Reddit", "Amazon", "Yelp", "MUTAG"]
|
||||
|
||||
def extract_paper(readme):
|
||||
"""Extract paper provenance (arxiv/doi/title/venue) from README text."""
|
||||
if not readme:
|
||||
return {"in_readme": False, "arxiv_id": None, "arxiv_url": None, "doi": None,
|
||||
"title": None, "authors": [], "venue": None}
|
||||
arxiv_id = arxiv_url = None
|
||||
for pat in _ARXIV_PATS:
|
||||
m = re.search(pat, readme)
|
||||
if m:
|
||||
arxiv_id = m.group(1); arxiv_url = f"https://arxiv.org/abs/{m.group(1)}"; break
|
||||
doi = None
|
||||
m = re.search(_DOI_PAT, readme)
|
||||
if m:
|
||||
doi = m.group(0).rstrip(").,;]")
|
||||
venue = None
|
||||
for v in _VENUES:
|
||||
if re.search(rf"\b{re.escape(v)}\b", readme):
|
||||
venue = v; break
|
||||
title = None
|
||||
m2 = (re.search(r"[Cc]ode (?:for|of)\s+'([^']+)'", readme)
|
||||
or re.search(r'[Cc]ode (?:for|of)\s+"([^"]+)"', readme)
|
||||
or re.search(r"^\s*#\s+(.+)$", readme, re.M))
|
||||
if m2:
|
||||
title = m2.group(1).strip()
|
||||
return {"in_readme": bool(arxiv_id or doi or title), "arxiv_id": arxiv_id,
|
||||
"arxiv_url": arxiv_url, "doi": doi, "title": title, "authors": [], "venue": venue}
|
||||
|
||||
def extract_datasets(readme, files):
|
||||
"""Identify referenced datasets (known-name match + data scripts)."""
|
||||
text = readme or ""
|
||||
found = []
|
||||
for ds in _KNOWN_DATASETS:
|
||||
if re.search(rf"\b{re.escape(ds)}\b", text, re.I):
|
||||
found.append(ds)
|
||||
scripts = [f for f in files if any(k in (f or "").lower()
|
||||
for k in ["data_gen", "get_data", "download", "prepare_data", "data_load"])]
|
||||
return [{"name": ds, "evidence": "mentioned in README",
|
||||
"download_script": scripts[:2] or None, "license": None} for ds in found]
|
||||
|
||||
def assess_repro(files, readme): # implemented in Task 2
|
||||
return {"deps_files": [], "deps_pinned": False, "entry_points": [], "expected_results": False, "env_spec": False}
|
||||
|
||||
def assess_citation(files, readme): # implemented in Task 2
|
||||
return {"cff": False, "codemeta": False, "zenodo": False, "readme_bibtex": None}
|
||||
|
||||
def extract_methods_frameworks(files, readme): # implemented in Task 2
|
||||
return {"methods": [], "frameworks": []}
|
||||
|
||||
def main(): # implemented in Task 3
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify Task-1 tests pass**
|
||||
|
||||
Run: `cd skills/gitlink-research-fair/scripts && python test_fair.py`
|
||||
Expected: `test_extract_paper OK` / `test_extract_paper_none OK` / `test_extract_datasets OK` / `PART 1 OK`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-research-fair/scripts/fair.py skills/gitlink-research-fair/scripts/test_fair.py
|
||||
git commit -m "feat(fair-v2): fair.py extract_paper + extract_datasets + test(TDD)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: fair.py — assess_repro + assess_citation + extract_methods_frameworks (real impl + tests)
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-research-fair/scripts/fair.py` (replace the 3 stubs)
|
||||
- Modify: `skills/gitlink-research-fair/scripts/test_fair.py` (add tests)
|
||||
|
||||
- [ ] **Step 1: Add tests for the 3 functions**
|
||||
|
||||
Append to `test_fair.py` (before the `if __name__` block):
|
||||
|
||||
```python
|
||||
def test_assess_repro():
|
||||
files = ["README.md", "main_Feature_Critic.py", "requirements.txt", "model_PACS.py"]
|
||||
r = assess_repro(files, SAMPLE_README)
|
||||
assert "requirements.txt" in r["deps_files"]
|
||||
assert r["deps_pinned"] is True
|
||||
assert "main_Feature_Critic.py" in r["entry_points"]
|
||||
assert r["expected_results"] is False # SAMPLE_README has no accuracy/results table
|
||||
print("test_assess_repro OK")
|
||||
|
||||
def test_assess_citation():
|
||||
c = assess_citation(SAMPLE_FILES, SAMPLE_README)
|
||||
assert c["cff"] is False and c["codemeta"] is False
|
||||
assert c["readme_bibtex"] and "@inproceedings" in c["readme_bibtex"]
|
||||
print("test_assess_citation OK")
|
||||
|
||||
def test_extract_methods_frameworks():
|
||||
mf = extract_methods_frameworks(SAMPLE_FILES, SAMPLE_README)
|
||||
assert "domain generalisation" in mf["methods"], mf["methods"] # SAMPLE_README 提到 Domain Generalisation
|
||||
assert isinstance(mf["frameworks"], list)
|
||||
print("test_extract_methods_frameworks OK")
|
||||
```
|
||||
|
||||
And replace the `if __name__ == "__main__":` block with:
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
test_extract_paper(); test_extract_paper_none(); test_extract_datasets()
|
||||
test_assess_repro(); test_assess_citation(); test_extract_methods_frameworks()
|
||||
print("ALL TESTS PASSED")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify the 3 new ones fail**
|
||||
|
||||
Run: `cd skills/gitlink-research-fair/scripts && python test_fair.py`
|
||||
Expected: `test_assess_repro OK` may print then FAIL on assert (stubs return empty deps_files → `"requirements.txt" in []` is False → assert fails). Confirms stubs need replacing.
|
||||
|
||||
- [ ] **Step 3: Replace the 3 stubs with real implementations**
|
||||
|
||||
Replace `def assess_repro(...) ...` / `def assess_citation(...) ...` / `def extract_methods_frameworks(...) ...` with:
|
||||
|
||||
```python
|
||||
def assess_repro(files, readme):
|
||||
"""Static reproducibility readiness: deps + entry + env + expected results."""
|
||||
name_set = {(f or "") for f in files}
|
||||
deps_candidates = ["requirements.txt", "environment.yml", "go.mod", "package.json",
|
||||
"Dockerfile", "setup.py", "pyproject.toml"]
|
||||
deps_files = [f for f in deps_candidates if f in name_set]
|
||||
entry_points = sorted([f for f in name_set if re.match(r"(main|train|run|demo)_?\w*\.py$", f, re.I)])
|
||||
env_spec = any(f in ("Dockerfile", "environment.yml") for f in deps_files)
|
||||
expected = bool(re.search(r"(accuracy|f1\b|bleu|rouge|results?\s*(table|in section)|table\s*\d)",
|
||||
readme or "", re.I))
|
||||
return {"deps_files": deps_files, "deps_pinned": bool(deps_files),
|
||||
"entry_points": entry_points[:5], "expected_results": expected, "env_spec": env_spec}
|
||||
|
||||
def assess_citation(files, readme):
|
||||
"""Citation readiness: CITATION.cff / codemeta / zenodo + README bibtex."""
|
||||
name_set = {(f or "") for f in files}
|
||||
m = re.search(r"@(inproceedings|article|misc|book)\{[^}]+\}", readme or "", re.S | re.I)
|
||||
return {"cff": "CITATION.cff" in name_set,
|
||||
"codemeta": "codemeta.json" in name_set,
|
||||
"zenodo": ".zenodo.json" in name_set,
|
||||
"readme_bibtex": (m.group(0)[:200] if m else None)}
|
||||
|
||||
def extract_methods_frameworks(files, readme):
|
||||
"""Infer methods + frameworks from filenames + README."""
|
||||
text = " ".join(files) + " " + (readme or "")
|
||||
frameworks = []
|
||||
if re.search(r"\b(torch|pytorch|nn\.module)\b", text, re.I): frameworks.append("PyTorch")
|
||||
if re.search(r"\b(tensorflow|tf\.|keras)\b", text, re.I): frameworks.append("TensorFlow")
|
||||
if re.search(r"\b(jax|flax|haiku)\b", text, re.I): frameworks.append("JAX")
|
||||
if re.search(r"\b(sklearn|scikit-learn)\b", text, re.I): frameworks.append("scikit-learn")
|
||||
methods = []
|
||||
for kw in ["attention", "transformer", "contrastive", "meta-learning", "federated",
|
||||
"graph", "convolution", "resnet", "gan", "diffusion", "reinforcement",
|
||||
"domain generalisation", "domain generalization"]:
|
||||
if re.search(rf"\b{kw}", text, re.I):
|
||||
methods.append(kw)
|
||||
return {"methods": methods[:6],
|
||||
"frameworks": frameworks or ["unknown (infer from filenames; verify imports)"]}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run all tests — expect ALL PASS**
|
||||
|
||||
Run: `cd skills/gitlink-research-fair/scripts && python test_fair.py`
|
||||
Expected: `ALL TESTS PASSED` (6 tests)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-research-fair/scripts/fair.py skills/gitlink-research-fair/scripts/test_fair.py
|
||||
git commit -m "feat(fair-v2): assess_repro + assess_citation + extract_methods_frameworks(6 测试全过)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: fair.py — gitlink-cli fetchers + main + end-to-end smoke
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/gitlink-research-fair/scripts/fair.py` (add network layer + replace main stub)
|
||||
|
||||
- [ ] **Step 1: Add gitlink-cli fetchers + implement main()**
|
||||
|
||||
Insert before `def main():` and replace the `def main(): pass` stub:
|
||||
|
||||
```python
|
||||
def _gitlink(*args):
|
||||
"""Run gitlink-cli with json output; return parsed dict (UTF-8 safe)."""
|
||||
r = subprocess.run(["gitlink-cli"] + list(args) + ["--format", "json"],
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60)
|
||||
raw = r.stdout
|
||||
i = raw.find("{")
|
||||
return json.loads(raw[i:]) if i >= 0 else {}
|
||||
|
||||
def fetch_readme(owner, repo):
|
||||
d = _gitlink("file", "+get", "--owner", owner, "--repo", repo, "--path", "README.md")
|
||||
ent = (d.get("data", {}) or {}).get("entries", {}) or {}
|
||||
return ent.get("content", "") if isinstance(ent, dict) else ""
|
||||
|
||||
def fetch_file_list(owner, repo):
|
||||
d = _gitlink("file", "+list", "--owner", owner, "--repo", repo)
|
||||
fd = d.get("data", "[]")
|
||||
if isinstance(fd, str):
|
||||
fd = json.loads(fd)
|
||||
return [f.get("name") for f in fd if isinstance(f, dict)] if isinstance(fd, list) else []
|
||||
|
||||
def fetch_repo_meta(owner, repo):
|
||||
info = _gitlink("repo", "+info", "--owner", owner, "--repo", repo)
|
||||
comm = _gitlink("commit", "+list", "--owner", owner, "--repo", repo, "--page", "1")
|
||||
cd = comm.get("data", {})
|
||||
cl = cd.get("commits") if isinstance(cd, dict) else None
|
||||
head = (cl[0].get("sha") if cl and isinstance(cl, list) and cl else None)
|
||||
d = info.get("data", {}) or {}
|
||||
return {"identifier": d.get("identifier"), "license_id": d.get("license_id"),
|
||||
"has_dataset": d.get("has_dataset"), "head_sha": head}
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="gitlink-research-fair v2: research software X-ray")
|
||||
ap.add_argument("--owner", required=True)
|
||||
ap.add_argument("--repo", required=True)
|
||||
args = ap.parse_args()
|
||||
|
||||
readme = fetch_readme(args.owner, args.repo)
|
||||
files = fetch_file_list(args.owner, args.repo)
|
||||
meta = fetch_repo_meta(args.owner, args.repo)
|
||||
mf = extract_methods_frameworks(files, readme)
|
||||
profile = {
|
||||
"repo": f"{args.owner}/{args.repo}",
|
||||
"head_sha": meta.get("head_sha"),
|
||||
"paper": extract_paper(readme),
|
||||
"datasets": extract_datasets(readme, files),
|
||||
"repro": assess_repro(files, readme),
|
||||
"citation": assess_citation(files, readme),
|
||||
"methods": mf["methods"],
|
||||
"frameworks": mf["frameworks"],
|
||||
"license": {"file": any("LICENSE" in (f or "") for f in files),
|
||||
"license_id": meta.get("license_id")},
|
||||
"files_count": len(files),
|
||||
}
|
||||
json.dump(profile, sys.stdout, ensure_ascii=False, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: End-to-end smoke (live, Feature_Critic)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd skills/gitlink-research-fair/scripts
|
||||
PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python fair.py --owner liyiying10 --repo Feature_Critic > _xray.json 2>&1
|
||||
PYTHONUTF8=1 python -c "
|
||||
import json
|
||||
d=json.load(open('_xray.json',encoding='utf-8'))
|
||||
print('repo:', d['repo'], '| head:', (d.get('head_sha') or '')[:7])
|
||||
print('paper arxiv:', d['paper']['arxiv_id'], '| venue:', d['paper']['venue'], '| in_readme:', d['paper']['in_readme'])
|
||||
print('datasets:', [x['name'] for x in d['datasets']])
|
||||
print('repro deps:', d['repro']['deps_files'], '| entry:', d['repro']['entry_points'], '| env:', d['repro']['env_spec'])
|
||||
print('citation cff:', d['citation']['cff'], '| bibtex?', bool(d['citation']['readme_bibtex']))
|
||||
print('files:', d['files_count'])
|
||||
"
|
||||
rm -f _xray.json
|
||||
```
|
||||
Expected: arxiv `1901.11448` + venue `ICML` + datasets incl `PACS`/`Visual Decathlon` + entry `main_Feature_Critic.py` + cff False + bibtex True + files 15. If arxiv None, inspect README (the repo may have changed; adapt regex). If `file +list` parse fails, confirm data is stringified-JSON and json.loads handles it.
|
||||
|
||||
- [ ] **Step 3: Re-run unit tests (regression)**
|
||||
|
||||
Run: `cd skills/gitlink-research-fair/scripts && python test_fair.py`
|
||||
Expected: `ALL TESTS PASSED`
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-research-fair/scripts/fair.py
|
||||
git commit -m "feat(fair-v2): gitlink-cli fetchers + main() 编排 → 科研画像 JSON(Feature_Critic smoke 通过)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Rewrite SKILL.md (X-ray pipeline + 4-dim + hybrid report + real KG + prescription + degradation)
|
||||
|
||||
**Files:**
|
||||
- Modify (overwrite): `skills/gitlink-research-fair/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Overwrite SKILL.md with the v2 content**
|
||||
|
||||
New `SKILL.md` (exact — this replaces the v1 5-axis content entirely):
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: gitlink-research-fair
|
||||
version: 2.0.0
|
||||
description: "科研软件 X 光:用 fair.py 真抽取 GitLink 科研仓库的论文/数据/复现/引用画像,LLM 四维裁决,输出含真科研图谱与特有关键发现的洞察报告,可选处方 PR。当用户需要深挖科研仓库的科研产物、评估可复现/可引用性时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "python skills/gitlink-research-fair/scripts/fair.py --help"
|
||||
---
|
||||
|
||||
# gitlink-research-fair v2(科研软件 X 光)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 裁决由 LLM 做出,但每条必须引用 `fair.py` 抽到的实证(arxiv id / 文件名 / deps 状态);无实证的判断丢弃。**
|
||||
**CRITICAL — 处方(开 PR)默认预览确认;绝不自动 merge、绝不 force-push、绝不碰原仓库。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md);四维裁决细则、真 KG schema、抽取规则见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
## 概述
|
||||
|
||||
**科研软件 X 光**:`scripts/fair.py` 真抽取仓库内容(README/文件/依赖)→ 科研画像 JSON;LLM 对 4 个科研专属维度裁决(论文溯源/数据链/复现就绪/引用就绪),渲染**每 repo 特有的洞察报告**(裁决总览 + 真科研图谱 + 关键发现),可选处方 PR。与 `gitlink-health`(项目过程健康)正交,与 `gitlink-spark`(跨仓挖缺口)互补——本 skill **单仓深挖科研产物**。
|
||||
|
||||
## 命令接口
|
||||
|
||||
```bash
|
||||
python skills/gitlink-research-fair/scripts/fair.py --owner <owner> --repo <identifier>
|
||||
# → stdout: 科研画像 JSON {paper, datasets, repro, citation, methods, frameworks, license, head_sha, files_count}
|
||||
```
|
||||
|
||||
skill 约定参数(非 CLI flag):
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner/--repo` | 自动从 cwd 解析 | 目标科研仓库(用 identifier) |
|
||||
| `--auto` | 关 | 跳过预览直接开处方 PR(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出 X 光报告,不开 PR |
|
||||
| `--refresh` | 关 | 即使有旧哨兵也重评 |
|
||||
|
||||
## 管道
|
||||
|
||||
### ① 抽取(fair.py,确定性)
|
||||
`fetch_readme`(file +get)+ `fetch_file_list`(file +list)+ `fetch_repo_meta`(repo +info + commit +list 取 HEAD sha)→ `extract_paper` / `extract_datasets` / `assess_repro` / `assess_citation` / `extract_methods_frameworks` → 科研画像 JSON
|
||||
|
||||
### ② 四维裁决(LLM,读 JSON)
|
||||
论文溯源 / 数据链 / 复现就绪 / 引用就绪。每维 `✅/⚠️/❌` + **引用画像字段的具体证据**。规则见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
### ③ 真科研图谱(从画像生成 Mermaid)
|
||||
节点 `Paper↔Method↔Code↔Dataset↔Framework↔Citation`,带状态色(✅绿/⚠️黄/❌红)。schema 见 REFERENCE。
|
||||
|
||||
### ④ 渲染 X 光报告(hero)并落盘
|
||||
裁决一行 + 真图谱 + **本 repo 特有关键发现** + 处方摘要 + 双裁决证书。**始终保存** `report-cards/<owner>-<repo>-xray.md`(绝不只在终端)。
|
||||
|
||||
### ⑤ 处方(可选)
|
||||
对 ❌/⚠️ 项生成 CITATION.cff(从 README 抽的引用)/ requirements.txt(从 import 扫)/ Dockerfile → fork → PR(默认预览)。
|
||||
|
||||
## 四维裁决(速览,细则见 REFERENCE)
|
||||
|
||||
| 维度 | ✅ | ⚠️ | ❌ |
|
||||
|------|---|----|----|
|
||||
| 论文溯源 | arxiv/DOI + 元数据全 | 仅 README 文字,无稳定链接 | 无论文线索 |
|
||||
| 数据链 | 命名 + 下载脚本 + license | 命名但无脚本/无 license | 未提及数据集 |
|
||||
| 复现就绪 | 依赖锁+入口+环境+期望结果齐全 | 有入口但缺依赖锁/环境/期望结果 | 无入口/无依赖 |
|
||||
| 引用就绪 | CITATION.cff/codemeta + DOI + 版本 | 仅 README 引用文本 | 无引用信息 |
|
||||
|
||||
## 报告格式(混合主视觉,hero)
|
||||
|
||||
````markdown
|
||||
🔬 **科研软件 X 光 — <owner>/<repo>**
|
||||
|
||||
═══════════════════════════════════════
|
||||
论文溯源 <V> | 数据链 <V> | 复现就绪 <V> | 引用就绪 <V>
|
||||
═══════════════════════════════════════
|
||||
|
||||
### 🧬 真科研图谱
|
||||
```mermaid
|
||||
graph LR
|
||||
P[<paper venue+arxiv>]:::ok -->|proposes| M[<method>]
|
||||
M -->|implements| C[<entry file>]:::ok
|
||||
C -->|uses| D[<datasets>]:::warn
|
||||
C -->|depends| F[<framework>]:::warn
|
||||
P -->|cited-via| Ci[<CITATION? or 无>]:::bad
|
||||
classDef ok fill:#cfe,stroke:#3a3; classDef warn fill:#ffe,stroke:#cc3; classDef bad fill:#fee,stroke:#c33;
|
||||
```
|
||||
|
||||
### 🔍 关键发现(本 repo 特有)
|
||||
- <LLM 从画像抽出的 ≥3 条具体发现,每条引用 fair.py 字段>
|
||||
|
||||
### 🔧 处方(可选)
|
||||
- <对 ❌/⚠️ 项的修复建议>
|
||||
|
||||
### 📜 双裁决证书
|
||||
复现就绪 <V> | 引用就绪 <V> | 锚定 commit `<sha>`
|
||||
|
||||
---
|
||||
<!-- gitlink-research-fair v2 | repo:<owner>/<repo> | paper:<✅/⚠️/❌> | repro:<V> | cite:<V> | sha:<head> -->
|
||||
*由 gitlink-research-fair v2(科研软件 X 光)生成。*
|
||||
````
|
||||
|
||||
## 处方闭环 + 护栏
|
||||
|
||||
对 ❌/⚠️ 项生成修复:`CITATION.cff`(从 README 抽的引用文本构造)+ `requirements.txt`(从代码 import 扫)+ `Dockerfile`(模板)→ `repo +fork` → `pr +create`(PR body 带报告摘要)。**护栏**:默认预览;`--auto` 跳过但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 报告已落盘(④),不开 PR。
|
||||
|
||||
## 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| README 读失败 | 降级用 file list + 元数据,标注"README 不可读,结论受限" |
|
||||
| `file +list` data 为字符串 | json.loads 解套 |
|
||||
| arxiv/DOI 抽不到 | 论文溯源判 ⚠️/❌,据实 |
|
||||
| 框架无法推断 | frameworks 标"未知",不编造 |
|
||||
| `--repo` 用中文显示名 404 | 提示用 identifier |
|
||||
| fork/PR 失败 | 处方物料落本地,告知路径 |
|
||||
| 报告/PR 发布失败 | 报告已在 ④ 落盘,告知路径 |
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the rewrite removed v1 content**
|
||||
|
||||
Run: `grep -c -e "5 轴评分 Rubric" -e "Repro 可复现" -e "五轴雷达" skills/gitlink-research-fair/SKILL.md`
|
||||
Expected: `0` (v1 5-axis content gone). Then `grep -c -e "科研软件 X 光" -e "四维裁决" -e "真科研图谱" skills/gitlink-research-fair/SKILL.md` → expect `≥3`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-research-fair/SKILL.md
|
||||
git commit -m "feat(fair-v2): 重写 SKILL.md(X光管道+四维裁决+混合报告+真图谱+处方),移除v1五轴"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Rewrite REFERENCE.md (4-dim rules + KG schema + extraction rules + FAIR4RS anchor)
|
||||
|
||||
**Files:**
|
||||
- Modify (overwrite): `skills/gitlink-research-fair/REFERENCE.md`
|
||||
|
||||
- [ ] **Step 1: Overwrite REFERENCE.md with v2 content**
|
||||
|
||||
New `REFERENCE.md` (exact — replaces v1):
|
||||
|
||||
```markdown
|
||||
# gitlink-research-fair v2 参考文档(科研软件 X 光)
|
||||
|
||||
> SKILL.md 的深度参考:四维裁决细则、真 KG schema、fair.py 抽取规则、数据源实测、学术锚。
|
||||
|
||||
## 一、四维裁决细则
|
||||
|
||||
每维 `✅/⚠️/❌`,**必须引用 fair.py 画像字段作证据**。
|
||||
|
||||
### 论文溯源(F2/R1.2)
|
||||
- ✅ `paper.arxiv_id` 或 `paper.doi` 非空 + `paper.title`/`venue` 抽到
|
||||
- ⚠️ 仅 `paper.title` 抽到("Code for..." 句式),无 arxiv/DOI
|
||||
- ❌ `paper.in_readme` = False
|
||||
|
||||
### 数据链(FAIR 数据维度)
|
||||
- ✅ `datasets` 非空 + 至少一个有 `download_script` + 数据 license 可考
|
||||
- ⚠️ `datasets` 非空(命名)但无 download_script 或无 license
|
||||
- ❌ `datasets` 为空
|
||||
|
||||
### 复现就绪(独立于 FAIR)
|
||||
- ✅ `repro.deps_pinned`=True + `entry_points` 非空 + `env_spec`=True + `expected_results`=True(四件齐全)
|
||||
- ⚠️ 有 `entry_points` 但缺依赖锁/环境/期望结果中任一
|
||||
- ❌ 无 `entry_points` 或无 `deps_files`
|
||||
|
||||
### 引用就绪(R1.1/R2)
|
||||
- ✅ `citation.cff` 或 `citation.codemeta` 为 True + 有版本/DOI
|
||||
- ⚠️ 仅 `citation.readme_bibtex` 非空(README 有引用文本,无机器可读文件)
|
||||
- ❌ 三者皆 False
|
||||
|
||||
## 二、真科研图谱 schema(Mermaid)
|
||||
|
||||
节点:`Paper(venue+arxiv)` / `Method` / `Code(entry file)` / `Dataset` / `Framework` / `Citation`
|
||||
边:`Paper —proposes→ Method`、`Method —implements→ Code`、`Code —uses→ Dataset`、`Code —depends→ Framework`、`Paper —cited-via→ Citation`
|
||||
状态色(按对应维度裁决):✅ `classDef ok fill:#cfe` / ⚠️ `classDef warn fill:#ffe` / ❌ `classDef bad fill:#fee`
|
||||
|
||||
## 三、fair.py 抽取规则
|
||||
|
||||
- **arxiv**:三路正则(arxiv URL / `arXiv:id` / 裸 `\d{4}.\d{4,5}`),取首个命中
|
||||
- **venue**:白名单(ICML/NeurIPS/ICLR/CVPR/ACL/...)正则
|
||||
- **datasets**:已知名白名单(PACS/Visual Decathlon/Cora/ImageNet/...)+ 数据脚本(data_gen/get_data/download)
|
||||
- **repro**:依赖文件名匹配(requirements/go.mod/environment.yml/Dockerfile/setup.py)+ 入口(main/train/run*.py)+ 期望结果(accuracy/f1/results table 正则)
|
||||
- **citation**:CITATION.cff/codemeta.json/.zenodo.json 文件存在 + README `@inproceedings/@article` bibtex
|
||||
- **frameworks**:torch/tensorflow/jax/sklearn 关键词(文件名+README),无则标"未知,verify imports"
|
||||
|
||||
## 四、数据源实测(2026-07)
|
||||
|
||||
| 源 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| `gitlink-cli file +get` | ✅ | content 在 `data.entries.content`(纯文本) |
|
||||
| `gitlink-cli file +list` | ✅ | `data` 是字符串化 JSON,需 json.loads |
|
||||
| `gitlink-cli repo +info` | ✅ | license_id/identifier/has_dataset |
|
||||
| `gitlink-cli commit +list` | ✅ | HEAD sha |
|
||||
| OpenAlex | ❌ 已砍 | v1 弱环节(间歇 503),v2 不依赖 |
|
||||
|
||||
## 五、学术锚(FAIR4RS)
|
||||
|
||||
四维裁决对标 **FAIR4RS**(Barker et al. 2022, Nature Sci Data):论文溯源→F2/R1.2、数据链→FAIR-Data、复现就绪→(独立轴,FAIR 必要非充分)、引用就绪→R1.1/R2。诚实声明:这是适配版评分(社区尚无认证级自动校验器),非官方认证。
|
||||
|
||||
## 六、诚实边界
|
||||
|
||||
1. fair.py 抽取覆盖度受 README 写法影响;非标准 README 可能漏(同时匹配多句式兜底)。
|
||||
2. 框架/方法为推断,标"推断"/"未知",不肯定。
|
||||
3. 复现就绪是**静态**判断(依赖/入口/环境/期望结果四件套),不实际跑代码。
|
||||
4. v1 的 OpenAlex 溯源已砍(避免 503 弱环节)。
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run: `grep -c -e "四维裁决细则" -e "真科研图谱 schema" -e "fair.py 抽取规则" -e "FAIR4RS" skills/gitlink-research-fair/REFERENCE.md`
|
||||
Expected: `4`. And `grep -c -e "5 轴" -e "OpenAlex 字段" skills/gitlink-research-fair/REFERENCE.md` → `0`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/gitlink-research-fair/REFERENCE.md
|
||||
git commit -m "feat(fair-v2): 重写 REFERENCE(四维裁决细则+真KG schema+抽取规则+FAIR4RS锚)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Live run — Feature_Critic X-ray (live-demo) + 落盘
|
||||
|
||||
**Files:**
|
||||
- (produces `report-cards/liyiying10-Feature_Critic-xray.md`; data feeds Task 8 example)
|
||||
|
||||
- [ ] **Step 1: Run fair.py on Feature_Critic**
|
||||
|
||||
```bash
|
||||
cd skills/gitlink-research-fair/scripts
|
||||
PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python fair.py --owner liyiying10 --repo Feature_Critic > _fc.json 2>&1
|
||||
PYTHONUTF8=1 python -c "
|
||||
import json
|
||||
d=json.load(open('_fc.json',encoding='utf-8'))
|
||||
assert d['paper']['arxiv_id']=='1901.11448', 'arxiv 抽取失败'
|
||||
assert d['paper']['venue']=='ICML'
|
||||
assert any(x['name']=='PACS' for x in d['datasets'])
|
||||
assert 'main_Feature_Critic.py' in d['repro']['entry_points']
|
||||
assert d['citation']['cff'] is False
|
||||
print('Feature_Critic 画像 OK | head:', (d.get('head_sha') or '')[:7])
|
||||
"
|
||||
```
|
||||
Expected: assertions pass. Keep `_fc.json` for the report.
|
||||
|
||||
- [ ] **Step 2: LLM-render the X-ray report (controller)**
|
||||
|
||||
Read `_fc.json`, apply REFERENCE §一 verdict rules, render the full hybrid report (verdict line + Mermaid graph + ≥3 repo-specific findings + prescription + dual-verdict certificate) per SKILL.md §报告格式. Expected verdicts: 论文溯源 ✅ / 数据链 ⚠️ / 复现就绪 ⚠️ / 引用就绪 ❌.
|
||||
|
||||
- [ ] **Step 3: Save report to report-cards/**
|
||||
|
||||
Save the rendered report to `report-cards/liyiying10-Feature_Critic-xray.md` (with sentinel). This is the live-demo artifact.
|
||||
|
||||
- [ ] **Step 4: No commit (data-gathering)** — proceed to Task 8.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Live run — Edge X-ray (contrast) + 落盘
|
||||
|
||||
**Files:**
|
||||
- (produces `report-cards/Edgedev-Edge-Computing-Engine-xray.md`; data feeds Task 8 example)
|
||||
|
||||
- [ ] **Step 1: Run fair.py on Edge**
|
||||
|
||||
```bash
|
||||
cd skills/gitlink-research-fair/scripts
|
||||
PYTHONUTF8=1 PYTHONIOENCODING=utf-8 python fair.py --owner Edgedev --repo Edge-Computing-Engine > _edge.json 2>&1
|
||||
PYTHONUTF8=1 python -c "
|
||||
import json
|
||||
d=json.load(open('_edge.json',encoding='utf-8'))
|
||||
print('Edge | paper:', d['paper'].get('arxiv_id'), '| methods:', d['methods'][:3], '| frameworks:', d['frameworks'][:2])
|
||||
print('license file:', d['license']['file'], '| files:', d['files_count'])
|
||||
"
|
||||
```
|
||||
Expected: paper arxiv likely None (Edge has no arxiv), methods incl autodiff/CNN-ish, license file True. Keep `_edge.json`.
|
||||
|
||||
- [ ] **Step 2: LLM-render Edge X-ray + save**
|
||||
|
||||
Render the Edge X-ray (controller). The **license-conflict finding** (README "禁止闭源商用" vs Apache LICENSE — note: fair.py detects `license.file=True`; the conflict is read from README content during rendering) must appear as a key finding. Verdicts will differ from Feature_Critic (e.g., 论文溯源 ❌, no paper). Save to `report-cards/Edgedev-Edge-Computing-Engine-xray.md`. The two reports must look visibly different (proves not 千篇一律).
|
||||
|
||||
- [ ] **Step 3: No commit** — proceed to Task 8.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: examples (2 docs) + remove v1 example
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/gitlink-research-fair/examples/feature-critic-xray.md`
|
||||
- Create: `skills/gitlink-research-fair/examples/edge-xray.md`
|
||||
- Remove: `skills/gitlink-research-fair/examples/research-fair-workflow.md` (v1 songhui18)
|
||||
|
||||
- [ ] **Step 1: Write feature-critic-xray.md (using Task 6 outputs)**
|
||||
|
||||
Real walkthrough: the fair.py command + the画像摘要 + the full X-ray report (from `report-cards/liyiying10-Feature_Critic-xray.md`) + a "答辩演示脚本" section (what to say when demoing live). No placeholders — all real values (arxiv 1901.11448, PACS/Visual Decathlon, etc.).
|
||||
|
||||
- [ ] **Step 2: Write edge-xray.md (using Task 7 outputs)**
|
||||
|
||||
Real walkthrough: fair.py command + 画像 + X-ray report (license-conflict finding). Emphasize the contrast with Feature_Critic (different profile → different report).
|
||||
|
||||
- [ ] **Step 3: Remove v1 example + clean temp + commit**
|
||||
|
||||
```bash
|
||||
cd "C:\Users\CWQ98\Desktop\演化与运维\gitlink-cli"
|
||||
git rm skills/gitlink-research-fair/examples/research-fair-workflow.md
|
||||
rm -f skills/gitlink-research-fair/scripts/_fc.json skills/gitlink-research-fair/scripts/_edge.json
|
||||
rm -rf skills/gitlink-research-fair/scripts/__pycache__
|
||||
git add skills/gitlink-research-fair/examples/feature-critic-xray.md skills/gitlink-research-fair/examples/edge-xray.md
|
||||
git commit -m "feat(fair-v2): examples Feature_Critic+Edge 真实X光走查;移除 v1 songhui18 示例"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: README + workflow update + register + acceptance + push
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/README.md`
|
||||
- Modify: `skills/gitlink-workflow/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Update README fair row**
|
||||
|
||||
In `skills/README.md`, change the gitlink-research-fair row to:
|
||||
```markdown
|
||||
| **gitlink-research-fair** | 科研软件 X 光 | fair.py 抽论文/数据/复现/引用画像,四维裁决 + 真科研图谱 + 特有关键发现报告,可选处方 PR |
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update workflow link text**
|
||||
|
||||
In `skills/gitlink-workflow/SKILL.md`, change "科研软件 FAIR 体检" → "科研软件 X 光(fair v2)".
|
||||
|
||||
- [ ] **Step 3: Re-register + acceptance (spec §16)**
|
||||
|
||||
```bash
|
||||
bash scripts/setup-skills.sh # re-link (no-op if already linked)
|
||||
ls ~/.claude/skills/ | grep gitlink-research-fair
|
||||
```
|
||||
Verify spec §16:
|
||||
- [ ] SKILL.md(重写)+ fair.py + test_fair.py + REFERENCE.md(重写)+ 2 examples 齐全
|
||||
- [ ] `python fair.py --owner liyiying10 --repo Feature_Critic` 出合法画像 JSON(arxiv 1901.11448)
|
||||
- [ ] test_fair.py 6 测试全过
|
||||
- [ ] Feature_Critic X 光:论文✅/数据⚠️/复现⚠️/引用❌ + 真图谱 ≥6 节点 + 关键发现 ≥3
|
||||
- [ ] Edge X 光:license 冲突作为发现;与 Feature_Critic 报告明显不同
|
||||
- [ ] 报告落盘 report-cards/
|
||||
- [ ] v1 五轴雷达/假 KG 已从 SKILL.md 移除(grep 0)
|
||||
- [ ] README/workflow 描述更新
|
||||
|
||||
- [ ] **Step 4: Commit + push (updates PR #5)**
|
||||
|
||||
```bash
|
||||
git add skills/README.md skills/gitlink-workflow/SKILL.md
|
||||
git commit -m "docs(fair-v2): README/workflow 更新为「科研软件 X 光」+ 注册"
|
||||
git push myfork feat/gitlink-research-fair
|
||||
```
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
# 文献-代码语义缺口挖掘机 Skill(gitlink-spark)设计
|
||||
|
||||
- **日期**:2026-07-01
|
||||
- **状态**:已批准,待编写实现计划
|
||||
- **作者**:CWQ + Claude
|
||||
- **定位**:子任务四(应用 GitLink 辅助科研)的**第二部分**——与 `gitlink-research-fair`(评估已有)组成"科研辅助双联装";本 skill 负责"**发现空白**"。
|
||||
- **形态**:skill + **可运行 python 脚本**(非纯 Markdown);不写 Go。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
`gitlink-research-fair` 解决"已有科研软件合不合格"。研究者还有个更核心的痛点:**找新的研究点**。他们读大量论文找灵感,却很难发现"**学术界的最新理论**"与"**开源社区的实际落地**"之间有哪些未被填补的空白——而这往往是低成本发论文/出成果的黄金地带。
|
||||
|
||||
本 skill 填补该缺口:给一个研究领域,跨 **arXiv(学术)× GitLink(中文生态)× GitHub(全球)** 三源,挖出两类语义缺口,输出**空白学术机会报告**,并可一键 **fork+issue 起跑**。
|
||||
|
||||
**实用性目标**(demo 级,非生产级全场景通用):在 GNN 领域跑出 **2-3 个漂亮真实例子**供答辩展示;每条缺口带可查实证。
|
||||
|
||||
**创意性目标**:从"paper→code gap"角度启发创新,区别于 Papers With Code(只链接、不挖缺口)/ OpenAlex(只文献)/ GitHub Archive(只存档)。
|
||||
|
||||
## 2. 非目标(YAGNI)
|
||||
|
||||
- **不做生产级全领域通用**——demo 级,GNN 跑通 2-3 例即可,不追求任意领域鲁棒
|
||||
- 不做实时订阅/webhook——按需 agent 调用
|
||||
- 不自动撰写论文/实现代码——只"提议机会 + 起跑 scaffold"
|
||||
- **绝不自动 merge**;起跑(fork+issue)默认预览,`--auto` 跳过预览
|
||||
- 不修改 gitlink-cli 的 Go 代码——skill + python 脚本
|
||||
- 不重复 `gitlink-research-fair`——fair 评估单仓 FAIR,spark 跨仓跨源挖缺口
|
||||
|
||||
## 3. 关键决策(用户确认)
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| v1 缺口类型 | **两种都做**(有理论无实现 + 有需求无解答) | 完整愿景;demo 级 2-3 例即可 |
|
||||
| 验证领域 | **图神经网络 GNN** | 三源最均衡:arXiv 海量、GitLink 少量(缺口真实)、GitHub 海量(对照鲜明) |
|
||||
| GitHub 全球对照 | **硬需求**,做 | 避免"GitLink 0 ≠ 全球空白"误导;含阈值过滤 |
|
||||
| 缺口匹配引擎 | **A. LLM 语义匹配为主** | 脚本抓真实数据,LLM 提缺口假设+附实证三件套;demo 级最轻最灵活 |
|
||||
| 起跑动作 | fork 最近基准 + issue 粘伪代码 todo | 对称 fair 处方 PR;强 demo 闭环 |
|
||||
| 交付方式 | 与 fair 同 PR #5(子任务四双联装) | 统一叙事 |
|
||||
|
||||
## 4. 数据源与可行性(实测 2026-07-01)
|
||||
|
||||
| 数据源 | 状态 | 用途 | 备注 |
|
||||
|--------|------|------|------|
|
||||
| arXiv API(HTTPS) | ✅ HTTP 200,返回论文条目 | ① 学术采:领域近 N 天论文 | 必须 HTTPS(HTTP 被沙箱阻断) |
|
||||
| `gitlink-cli search +repos` | ✅ | ② GitLink 仓库 | 用 identifier/关键词 |
|
||||
| `gitlink-cli issue +list`(逐仓库) | ✅ | ② GitLink open issue | 绕开 `search +issues`(返回 HTML 的坑) |
|
||||
| GitHub Search API | ✅ HTTP 200(total_count + items) | ③ 全球对照 | 未认证 10 req/min;用 `GITHUB_TOKEN` 提至 5000/h |
|
||||
| OpenAlex | ⚠ 间歇 503 | 引用计数(可选富集) | best-effort,降级跳过 |
|
||||
|
||||
## 5. 文件清单
|
||||
|
||||
| 文件 | 动作 | 内容 |
|
||||
|------|------|------|
|
||||
| `skills/gitlink-spark/SKILL.md` | 新增 | 4 阶段管道编排、缺口分类法、报告模板、GitHub 阈值规则、起跑护栏、错误降级、命令接口 |
|
||||
| `skills/gitlink-spark/scripts/spark.py` | 新增 | **可独立运行的数据融合脚本**:arXiv + gitlink-cli + GitHub API → 输出 JSON 给 LLM;含缓存与限流 |
|
||||
| `skills/gitlink-spark/REFERENCE.md` | 新增 | 缺口分类法细则、GitHub 阈值与分级、LLM prompt 模板、数据源实测结论、诚实边界 |
|
||||
| `skills/gitlink-spark/examples/spark-图神经网络.md` | 新增 | GNN 真实跑出的 2-3 缺口卡(含起跑截图) |
|
||||
| `skills/README.md` | 修改 | 智能技能表加 gitlink-spark |
|
||||
| `skills/gitlink-workflow/SKILL.md` | 修改(可选) | 专项 skill 链表加 gitlink-spark |
|
||||
|
||||
## 6. 命令接口
|
||||
|
||||
### spark.py(独立可运行)
|
||||
```bash
|
||||
python skills/gitlink-spark/scripts/spark.py \
|
||||
--field "图神经网络" \
|
||||
[--max-papers 10] \
|
||||
[--gap-type both|theory|demand] \
|
||||
[--github-token $GITHUB_TOKEN] # 可选,未设则按 10/min 限流
|
||||
# → stdout 输出融合 JSON:{papers, gitlink_repos, gitlink_issues, github_counts}
|
||||
```
|
||||
|
||||
### skill 约定参数(SKILL.md,非 CLI flag)
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--field` | 必填 | 研究领域(如 `图神经网络`、`federated learning`) |
|
||||
| `--gap-type` | `both` | `theory` / `demand` / `both` |
|
||||
| `--max-papers` | 10 | arXiv 抓取论文上限(控制 GitHub 调用) |
|
||||
| `--auto` | 关 | 跳过预览直接起跑(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出报告,不起跑 |
|
||||
|
||||
## 7. 数据流(4 阶段管道)
|
||||
|
||||
```
|
||||
① 学术采 spark.py: arXiv HTTPS 抓领域近 90 天论文(标题/摘要/arxiv id/方法关键词)
|
||||
② GitLink spark.py: gitlink-cli search +repos 抓领域仓库;
|
||||
对每个仓库 issue +list 抓 open issue(绕开 search+issues HTML)
|
||||
③ 全球对照 spark.py: GitHub Search API 对每个论文方法查 total_count + Top3 仓库
|
||||
④ 缺口匹配(LLM,SKILL.md 编排)
|
||||
读 spark.py 输出的 JSON → 语义匹配两类缺口 → 每张带实证三件套 → 渲染机会报告
|
||||
⑤ 起跑(可选) 选定缺口 → repo +fork 基准 → issue +create 粘论文伪代码 todo
|
||||
```
|
||||
|
||||
**职责切分**:`spark.py` 只抓**真实数据**(确定性、可复现);**缺口发现**交给 LLM(语义判断),但必须附实证,受置信度门控。
|
||||
|
||||
spark.py 输出 JSON schema:
|
||||
```json
|
||||
{
|
||||
"field": "图神经网络",
|
||||
"papers": [{"arxiv_id":"2403.xxxxx","title":"...","abstract":"...","published":"2024-03-15","method_keywords":[...]}],
|
||||
"gitlink_repos": [{"owner":"leejt","repo":"GraphGallery","desc":"...","topics":[...]}],
|
||||
"gitlink_issues": [{"repo":"leejt/GraphGallery","number":12,"subject":"...","status":"open","participants":3}],
|
||||
"github_counts": [{"method":"graph attention XXX","total_count":2,"top":[{"full_name":"...","stars":3534}]}]
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 两类缺口分类法 + 实证三件套
|
||||
|
||||
每张缺口卡**必须**带齐三件套,否则被置信度门控丢弃(防 LLM 编造):
|
||||
|
||||
### 类型 A:有理论无实现(paper → code gap)
|
||||
- **三件套**:① 论文 arxiv id + 标题 + 发表日期 ② GitLink 搜索查询串 + 命中数(0 或极少) ③ GitHub total_count + Top 仓库(参考基准)
|
||||
- **判定**:论文提出方法 M;GitLink 实现 0/极少;GitHub 按下面阈值分级。
|
||||
|
||||
### 类型 B:有需求无解答(open issue → applied research gap)
|
||||
- **三件套**:① issue URL + 主题 + 讨论人数/状态 ② GitLink 无现成实现解此痛点 ③ GitHub 是否有成熟开源解(对照)
|
||||
- **判定**:领域仓库中存在"研究性痛点"open issue(排除"安装报错"类使用问题);学术/GitHub 无现成解。
|
||||
- **降噪**:LLM 筛"研究性痛点"(含性能/可扩展性/新场景),排除使用/部署类。
|
||||
|
||||
## 9. GitHub 全球对照与阈值(诚实核心,硬需求落地)
|
||||
|
||||
防止"GitLink 0 ≠ 全球空白"误导。对每个"理论无实现"候选,按 GitHub total_count 分级:
|
||||
|
||||
| GitHub total_count | 分级 | 报告行为 |
|
||||
|--------------------|------|----------|
|
||||
| `< 10` | **全球稀缺(真空白)** | 报为高价值缺口:"GitLink 生态空白 × 全球稀缺 → 复现并开源到 GitLink,易成本平台标杆" |
|
||||
| `10–50` | **新兴(部分空白)** | 报为中等缺口:"GitLink 空白,全球新兴(N 个),可做中文生态首个完整实现" |
|
||||
| `≥ 50` | **全球已成熟** | **不报为空白**,列入"✅ 已诚实排除"区:"GitLink 虽 0,但 GitHub 已 N 个(含官方)→ 全球已成熟,非空白" |
|
||||
|
||||
> 这个分级是本 skill 的诚实命门:宁可少报,不误报机会。GitHub 计数缓存(spark.py 按 method key 缓存去重)。
|
||||
|
||||
## 10. 机会报告格式(hero)
|
||||
|
||||
````markdown
|
||||
⚡ **gitlink-spark 机会报告:<field>**
|
||||
|
||||
学术采:arXiv 近 90 天 N 篇 | GitLink 仓库 M 个 | GitHub 全球基线已对照
|
||||
生成时间:YYYY-MM-DD
|
||||
|
||||
### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值]
|
||||
**论文**:[arxiv:<id>] "<title>" (<venue/date>)
|
||||
**方法关键词**:<...>
|
||||
**GitLink**:search "<query>" → **0 命中**(查询串留底可复现)
|
||||
**GitHub 全球**:total_count = **2**(Top: <repo> <stars>⭐)→ 全球稀缺
|
||||
**机会建议**:<LLM 一句话:为何值得复现 + 开源到 GitLink>
|
||||
**起跑**:[按钮] fork 基准 <repo> → 创建 issue 粘论文 Algorithm 1 伪代码
|
||||
|
||||
### 🧩 缺口 2 · 有需求无解答 [应用机会]
|
||||
**Issue**:<repo>#<n> "<subject>"(N 人讨论, open, <date>)
|
||||
**痛点**:<LLM 一句话研究性痛点归纳>
|
||||
**GitLink / GitHub**:均无成熟开源解
|
||||
**机会建议**:<LLM 一句话:可写应用级论文 + GitLink 落地>
|
||||
|
||||
### ✅ 已诚实排除(非空白)
|
||||
- 论文 Y:GitLink 虽 0,但 GitHub 已 47 个实现(含官方)→ 全球已成熟,不报
|
||||
|
||||
---
|
||||
<!-- gitlink-spark v1 | field:<field> | gaps:<N> | date:<YYYY-MM-DD> -->
|
||||
*由 gitlink-spark skill 生成。*
|
||||
````
|
||||
|
||||
## 11. 起跑动作(M5)+ 安全护栏
|
||||
|
||||
选定一张"理论无实现"缺口卡 → 用户确认 →
|
||||
1. `gitlink-cli repo +fork` 最近基准(GitHub Top 仓库 或 GitLink 最近实现)
|
||||
2. LLM 从 arXiv 论文抓取 Algorithm/Pseudocode 节
|
||||
3. `gitlink-cli issue +create` 在 fork 上建一个复现 todo issue,body 粘入论文伪代码 + 报告卡摘要
|
||||
|
||||
**护栏**(沿用既有偏好):默认预览确认;`--auto` 跳过预览但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 只出报告。
|
||||
|
||||
## 12. 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| arXiv 返回空/超时 | 改 HTTPS 重试;仍空则报告"学术采失败",降级用既有论文 |
|
||||
| `search +issues` 返回 HTML(已知坑) | 不用它;用 `issue +list` 逐仓库枚举 |
|
||||
| GitHub 未认证限流(10/min) | spark.py sleep ~7s 限速;建议设 `GITHUB_TOKEN` |
|
||||
| GitHub total_count 查询失败 | 该论文标注"GitHub 对照失败",不进缺口判定 |
|
||||
| OpenAlex 503 | 跳过引用富集,不影响主报告 |
|
||||
| LLM 缺口无实证三件套 | 置信度门控丢弃 |
|
||||
| fork/issue 起跑失败 | 输出 fork 目标 + 伪代码文本供手动起跑 |
|
||||
| 二进制是 npm 旧版 | 强制 `./gitlink-cli` 或 `go build` |
|
||||
|
||||
## 13. 验证计划(demo 2-3 例,GNN 领域)
|
||||
|
||||
实跑 `spark.py --field "图神经网络"` + LLM 匹配,产出:
|
||||
- [ ] **≥1 张"理论无实现"缺口卡**(GitHub total_count < 10,全球稀缺),三件套可查
|
||||
- [ ] **≥1 张"需求无解答"缺口卡**(真实 open issue + 研究性痛点),三件套可查
|
||||
- [ ] **≥1 张"理论无实现"缺口卡走完 fork+issue 起跑**(截图/链接;demand 卡为写论文方向、无 fork 起跑)
|
||||
- [ ] "✅ 已诚实排除"区至少 1 条(GitHub ≥50 的非空白),证明阈值生效
|
||||
- [ ] 全报告无 LLM 编造(每条可溯源到 spark.py JSON)
|
||||
|
||||
结果写入 `examples/spark-图神经网络.md`。
|
||||
|
||||
## 14. 风险与未决
|
||||
|
||||
- **arXiv 方法抽取**:从论文摘要自动抽"方法关键词"供 GitHub 查询,LLM 抽取有噪声 → spark.py 同时用论文标题关键词 + LLM 抽取双路查询 GitHub,取 total_count。
|
||||
- **GitLink GNN 仓库数量**:可能很少(之前扫到 GraphGallery 等);需求侧缺口(issue)依赖仓库数,若太少则 demand 缺口样本不足 → 必要时放宽领域关键词(如含 `图`/`GNN`/`graph neural`)。
|
||||
- **GitHub 限流**:demo 一次 10 篇论文 × 1 查询 = 10 次,刚好未认证上限;建议跑 demo 时设 `GITHUB_TOKEN`。
|
||||
- **缺口"机会建议"主观**:靠实证三件套兜底;建议标注"机会仅为启发,需研究者自行判断"。
|
||||
|
||||
## 15. 验收标准
|
||||
|
||||
1. `skills/gitlink-spark/` 四件套齐全(SKILL.md + scripts/spark.py + REFERENCE.md + examples/)
|
||||
2. `spark.py` 可独立运行:`python spark.py --field 图神经网络` 输出合法融合 JSON
|
||||
3. GNN 跑出 2-3 张真实缺口卡(≥1 theory + ≥1 demand),每张三件套可查
|
||||
4. GitHub 阈值生效:"已诚实排除"区至少 1 条
|
||||
5. ≥1 张缺口卡走完 fork+issue 起跑,有截图/链接
|
||||
6. 全报告每条缺口可溯源到 spark.py JSON(无 LLM 编造)
|
||||
7. REFERENCE.md 含缺口分类法 + 数据源实测结论 + 诚实边界
|
||||
8. README 登记 gitlink-spark
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
# gitlink-research-fair v2 设计(科研软件 X 光 / Research Software X-Ray)
|
||||
|
||||
- **日期**:2026-07-07
|
||||
- **状态**:已批准,待编写实现计划
|
||||
- **作者**:CWQ + Claude
|
||||
- **定位**:对 v1(5 轴 FAIR 体检卡)的**重大升级**,回应"与 health 重合 / 报告千篇一律 / 假科研图谱 / 纯 Markdown 无脚本"四条批评。同一 skill 演进(保留 fair 名 + FAIR4RS 学术锚 + PR #5),重构内部。
|
||||
- **形态**:skill + **可运行 `scripts/fair.py`**(stdlib only),内容感知的科研分析工作流。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
v1 的 fair 是"5 轴 FAIR 打分 + 报告卡",存在四个真问题:
|
||||
1. 与 `gitlink-health` 形态重合(都是"聚合 gitlink-cli → AI → 打分报告"),且不够科研专属。
|
||||
2. 报告千篇一律(每仓库都是同一组 ✓/✗);五轴 ASCII 雷达呈现烂;"科研关系图"是假的(只画作者/license/dataset?)。
|
||||
3. 纯 Markdown,无脚本,分析靠 LLM"看一眼",不可复现。
|
||||
4. 缺真实科研洞察。
|
||||
|
||||
v2 升级为**科研软件 X 光**:`fair.py` **真抽取** repo 内容(README/文件/依赖)→ 科研画像 JSON → LLM 对 4 个科研专属维度做裁决(每条带实证)→ **每 repo 特有的洞察报告**(裁决总览 + 真科研图谱 + 关键发现)+ 可选处方闭环。
|
||||
|
||||
**目标**:内容感知(每份报告说本 repo 特有的东西)、科研专属(论文/数据/复现/引用,非通用软件质量)、可执行(fair.py 脚本)、demo 级(1-2 示例 + 1 答辩演示,非全场景覆盖)。
|
||||
|
||||
## 2. 非目标(YAGNI)
|
||||
|
||||
- 不做"实际运行代码验证复现"(静态复现就绪度检查即可,跑代码超 demo 范围)
|
||||
- 不做全场景/全领域覆盖——Feature_Critic + Edge 两个场景跑通即可
|
||||
- 不重复 spark(spark 跨仓跨源挖缺口;fair v2 单仓深挖科研产物)
|
||||
- 不自动 merge;处方默认预览
|
||||
- 不写 Go——skill + python 脚本
|
||||
- **砍掉 v1 的**:5 轴雷达、假 KG、OpenAlex 溯源(弱环节)、与 health 重合的通用质量味
|
||||
|
||||
## 3. 关键决策(用户确认)
|
||||
|
||||
| 决策点 | 选择 | 理由 |
|
||||
|--------|------|------|
|
||||
| 主角定位 | **科研软件 X 光**(深挖科研产物 + 4 维裁决) | 替代 5 轴打分,科研专属,每 repo 特有 |
|
||||
| 报告主视觉 | **混合**(裁决一行 + 真 Mermaid 图谱 + 关键发现) | 扫读 + 视觉兼顾,回应雷达烂 + 假图谱 + 千篇一律三连批 |
|
||||
| 改造方式 | **演进式**(保留 fair 名/FAIR4RS 锚/PR #5) | 不浪费 v1 资产,重构内部 |
|
||||
| 演示仓库 | **liyiying10/Feature_Critic**(ICML2019 paper-code) | README 明含 arxiv 链接,X 光论文溯源能真抽,4 维有区分度 |
|
||||
| 第二示例 | **Edgedev/Edge-Computing-Engine**(引擎类) | 与 Feature_Critic 不同 profile(license 冲突发现),证明不千篇一律 |
|
||||
| 脚本 | **fair.py**(stdlib only,fair.py 抽数据 + LLM 裁决) | 内容感知、可复现,取代 LLM"看一眼" |
|
||||
|
||||
## 4. 与 v1 的差异 + 取舍
|
||||
|
||||
| 维度 | v1 | v2 |
|
||||
|------|----|----|
|
||||
| 评分对象 | 5 轴 FAIR(F/A/I/R/Repro)通用软件质量 | 4 维科研专属(论文溯源/数据链/复现就绪/引用就绪) |
|
||||
| 数据来源 | LLM 看 gitlink-cli 元数据 | fair.py 真抽取 README/文件/依赖 |
|
||||
| 图谱 | 假 KG(作者/license/dataset?) | 真 Mermaid:paper↔method↔code↔dataset↔framework↔citation |
|
||||
| 报告 | 千篇一律 ✓/✗ + ASCII 雷达 | 裁决一行 + 真图谱 + **本 repo 特有关键发现** |
|
||||
| 脚本 | 无(纯 Markdown) | fair.py(可独立运行) |
|
||||
| 处方 | 有(保留) | 保留升级(CITATION.cff 从 README 抽 / requirements 从 import 扫) |
|
||||
| OpenAlex 溯源 | 有(弱、易 503) | **砍**(去掉更自洽) |
|
||||
| 落盘 | 有(v1 已补) | 保留(report-cards/<owner>-<repo>-xray.md) |
|
||||
|
||||
## 5. 文件清单
|
||||
|
||||
| 文件 | 动作 | 内容 |
|
||||
|------|------|------|
|
||||
| `skills/gitlink-research-fair/SKILL.md` | 重写 | X 光管道(抽数→裁决→图谱→报告→处方)、4 维裁决表、报告模板、真图谱说明、处方护栏、降级 |
|
||||
| `skills/gitlink-research-fair/scripts/fair.py` | 新增 | 真抽取(README/文件/依赖)→ 科研画像 JSON;stdlib only |
|
||||
| `skills/gitlink-research-fair/scripts/test_fair.py` | 新增 | 抽取函数单测(arxiv 正则 / 依赖检测 / 数据集识别 / 引用检测) |
|
||||
| `skills/gitlink-research-fair/REFERENCE.md` | 重写 | 4 维裁决细则 + 真 KG schema + 抽取规则 + 数据源实测 + 学术锚(FAIR4RS) |
|
||||
| `skills/gitlink-research-fair/examples/feature-critic-xray.md` | 新增 | Feature_Critic 真实 X 光走查(答辩演示脚本) |
|
||||
| `skills/gitlink-research-fair/examples/edge-xray.md` | 新增 | Edge 引擎类对照(license 冲突发现) |
|
||||
| `skills/README.md` / `skills/gitlink-workflow/SKILL.md` | 微调 | fair 描述改为"科研软件 X 光" |
|
||||
|
||||
> v1 的旧 examples(songhui18 报告卡)替换为 v2 X 光示例。
|
||||
|
||||
## 6. 命令接口
|
||||
|
||||
### fair.py(可独立运行)
|
||||
```bash
|
||||
python skills/gitlink-research-fair/scripts/fair.py --owner liyiying10 --repo Feature_Critic
|
||||
# → stdout: 科研画像 JSON {paper, datasets, repro, citation, methods, frameworks, license, files, head_sha}
|
||||
```
|
||||
|
||||
### skill 约定参数(SKILL.md)
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner/--repo` | 自动从 cwd 解析 | 目标科研仓库(用 identifier) |
|
||||
| `--auto` | 关 | 跳过预览直接开处方 PR(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出 X 光报告,不开 PR |
|
||||
| `--refresh` | 关 | 即使有旧哨兵也重评 |
|
||||
|
||||
## 7. 数据流(管道)
|
||||
|
||||
```
|
||||
① 抽取(fair.py,确定性)
|
||||
fetch readme (file +get) + file list (file +list) + repo +info + commit +list(HEAD sha)
|
||||
→ extract_paper / extract_datasets / assess_repro / assess_citation / extract_methods_frameworks
|
||||
→ 科研画像 JSON
|
||||
② 裁决(LLM,读 JSON)4 维 × (✅/⚠️/❌ + 具体证据)
|
||||
③ 真科研图谱(从画像生成 Mermaid)
|
||||
④ 渲染 X 光报告(hero:裁决一行 + 真图谱 + 关键发现 + 处方摘要 + 双裁决证书)→ 落盘
|
||||
⑤ 处方(可选):fork → 建 CITATION.cff/requirements/Dockerfile → PR(默认预览)
|
||||
```
|
||||
|
||||
**职责切分**:fair.py 只做**确定性抽取**(可复现);**裁决与"关键发现"叙述**交给 LLM(语义),但每条必须引用 fair.py 抽到的实证。
|
||||
|
||||
## 8. fair.py 抽取目标 + 科研画像 JSON schema
|
||||
|
||||
抽取函数(纯函数单测 + gitlink-cli 网络层):
|
||||
- `extract_paper(readme)` → 正则抽 arxiv id/URL、DOI、OpenReview/aclanthology、标题/作者/会议、"Code for the paper" 句式
|
||||
- `extract_datasets(readme, files)` → 已知数据集名(PACS/Cora/ImageNet/Visual Decathlon/MNIST/CIFAR…)+ 数据脚本(data_gen/get_data/download.sh)+ 数据 license/DOI
|
||||
- `assess_repro(files, readme)` → 依赖文件(requirements/go.mod/environment.yml/Dockerfile)+ 锁版本?;入口(main/train/run/*.py);期望结果(results.md/accuracy/表);环境说明
|
||||
- `assess_citation(files, readme)` → CITATION.cff / codemeta.json / .zenodo.json 存在?+ README BibTeX/引用文本
|
||||
- `extract_methods_frameworks(files, readme)` → 方法词 + 框架(torch/tensorflow/jax,从 import/文件名推断)
|
||||
|
||||
科研画像 JSON:
|
||||
```json
|
||||
{
|
||||
"repo": "liyiying10/Feature_Critic", "head_sha": "<sha>",
|
||||
"paper": {"arxiv_id":"1901.11448","arxiv_url":"https://arxiv.org/abs/1901.11448","doi":null,
|
||||
"title":"Feature-Critic Networks for Heterogeneous Domain Generalisation",
|
||||
"authors":["Yiying Li","Yongxin Yang","Wei Zhou","Timothy M. Hospedales"],"venue":"ICML 2019","in_readme":true},
|
||||
"datasets": [{"name":"PACS","evidence":"data_gen_PACS.py + README","download_script":"get_model_dataset.sh","license":null},
|
||||
{"name":"Visual Decathlon","evidence":"data_gen_VD.py","download_script":null,"license":null}],
|
||||
"repro": {"deps_files":[],"deps_pinned":false,"entry_points":["main_Feature_Critic.py","main_baseline.py"],
|
||||
"expected_results":false,"env_spec":null},
|
||||
"citation": {"cff":false,"codemeta":false,"zenodo":false,
|
||||
"readme_bibtex":"Li, Yang, Zhou, Hospedales. Feature-Critic Networks... ICML 2019"},
|
||||
"methods": ["feature-critic","meta-learning","domain generalisation"],
|
||||
"frameworks": ["torch (inferred: alexnet/resnet/vggnet)"],
|
||||
"license": {"file":false,"type":null,"conflict":null},
|
||||
"files_count": 15
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 4 维裁决规则(LLM 读画像,每维 ✅/⚠️/❌ + 证据)
|
||||
|
||||
| 维度 | ✅ | ⚠️ | ❌ |
|
||||
|------|---|----|----|
|
||||
| 论文溯源 | 有 arxiv/DOI + 元数据全 | 仅 README 文字提及,无稳定链接 | 无任何论文线索 |
|
||||
| 数据链 | 数据集命名 + 下载脚本 + license | 命名但无脚本/无 license | 未提及数据集 |
|
||||
| 复现就绪 | 依赖锁 + 入口 + 环境 + 期望结果四件齐全 | 有入口但缺依赖锁/环境/期望结果 | 无入口/无依赖 |
|
||||
| 引用就绪 | CITATION.cff/codemeta + DOI + 版本 | 仅 README 引用文本 | 无任何引用信息 |
|
||||
|
||||
每维裁决**必须引用画像里的具体字段**(如"arxiv 1901.11448 已抽""requirements.txt 缺失"),无实证的判断丢弃。
|
||||
|
||||
## 10. 真科研图谱 schema(Mermaid,从画像生成)
|
||||
|
||||
节点(带状态色):`Paper(arxiv+venue)` / `Method` / `Code(file)` / `Dataset` / `Framework` / `Citation`
|
||||
边:`Paper —proposes→ Method`、`Method —implements→ Code`、`Code —uses→ Dataset`、`Code —depends→ Framework`、`Paper —cited-via→ Citation`
|
||||
状态色:✅绿 / ⚠️黄 / ❌红(缺失项)。
|
||||
|
||||
Feature_Critic 实例化:`ICML2019(1901.11448)✅ —proposes→ Feature-Critic方法 —implements→ main_Feature_Critic.py✅ —uses→ PACS⚠️/Visual Decathlon⚠️ —depends→ PyTorch(推断)⚠️;Paper —cited-via→ 无CITATION❌`。真有科研含义,非 v1 假图。
|
||||
|
||||
## 11. 报告格式(混合主视觉,hero)
|
||||
|
||||
````markdown
|
||||
🔬 **科研软件 X 光 — liyiying10/Feature_Critic**
|
||||
|
||||
═══════════════════════════════════════
|
||||
论文溯源 ✅ | 数据链 ⚠️ | 复现就绪 ⚠️ | 引用就绪 ❌
|
||||
═══════════════════════════════════════
|
||||
|
||||
### 🧬 真科研图谱
|
||||
```mermaid
|
||||
graph LR
|
||||
P[ICML2019 arxiv:1901.11448]:::ok -->|proposes| M[Feature-Critic 方法]
|
||||
M -->|implements| C[main_Feature_Critic.py]:::ok
|
||||
C -->|uses| D1[PACS]:::warn
|
||||
C -->|uses| D2[Visual Decathlon]:::warn
|
||||
C -->|depends| F[PyTorch 推断]:::warn
|
||||
P -->|cited-via| Ci[无 CITATION.cff]:::bad
|
||||
classDef ok fill:#cfe,stroke:#3a3; classDef warn fill:#ffe,stroke:#cc3; classDef bad fill:#fee,stroke:#c33;
|
||||
```
|
||||
|
||||
### 🔍 关键发现(本 repo 特有)
|
||||
- 论文 ICML2019 arxiv:1901.11448 已溯源 ✓,但**无 CITATION.cff** → 机器不可引用
|
||||
- 数据集 PACS/Visual Decathlon 命名 + `get_model_dataset.sh`,但**无数据 license**
|
||||
- 入口 `main_Feature_Critic.py` 在,但**无 requirements.txt** → 依赖未锁,复现风险
|
||||
- 框架:alexnet/resnet/vggnet(疑似 PyTorch,**依赖未声明**)
|
||||
|
||||
### 🔧 处方(可选)
|
||||
补 `CITATION.cff`(从 README 抽的引用)+ `requirements.txt`(从 import 扫)+ `Dockerfile`
|
||||
|
||||
### 📜 双裁决证书
|
||||
复现就绪 ⚠️ 部分 | 引用就绪 ❌ | 锚定 commit `<sha>`
|
||||
|
||||
---
|
||||
<!-- gitlink-research-fair v2 | repo:<owner>/<repo> | paper:? | repro:? | cite:? | sha:<head> -->
|
||||
*由 gitlink-research-fair v2(科研软件 X 光)生成。*
|
||||
````
|
||||
|
||||
报告**始终落盘** `report-cards/<owner>-<repo>-xray.md`(沿用 v1 落盘原则)。
|
||||
|
||||
## 12. 处方闭环 + 护栏(保留 v1 升级)
|
||||
|
||||
对 ❌/⚠️ 项生成修复:`CITATION.cff`(从 README 抽的引用文本构造)+ `requirements.txt`(从代码 import 扫出依赖)+ `Dockerfile`(模板)。→ fork → PR(默认预览,`--auto` 跳过;永不 force-push/碰原仓库/自动 merge;`--no-fork` 报告已落盘)。
|
||||
|
||||
## 13. 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| README 读取失败 | 降级用 file list + 元数据,标注"README 不可读,结论受限" |
|
||||
| `file +list` data 为字符串 | json.loads 解套(已知结构) |
|
||||
| arxiv/DOI 抽不到 | 论文溯源维度判 ⚠️ 或 ❌,据实 |
|
||||
| 框架无法推断 | frameworks 标 "未知",不编造 |
|
||||
| `--repo` 用了中文显示名 404 | 提示用 identifier(从 search 取) |
|
||||
| fork/PR 失败 | 处方物料落本地,告知路径 |
|
||||
| 报告/PR 发布失败 | 报告已在 ④ 落盘,告知路径 |
|
||||
|
||||
## 14. 验证计划(1-2 示例 + 答辩演示)
|
||||
|
||||
- **答辩现场演示 + 示例1**:`liyiying10/Feature_Critic`
|
||||
- [ ] fair.py 抽出 arxiv 1901.11448 + ICML2019 + PACS/VD 数据集 + 入口 + 无 CITATION/requirements
|
||||
- [ ] 4 维裁决:论文 ✅ / 数据 ⚠️ / 复现 ⚠️ / 引用 ❌
|
||||
- [ ] 真图谱画出 6 类节点 + 状态色
|
||||
- [ ] 关键发现 ≥3 条本 repo 特有
|
||||
- **示例2**:`Edgedev/Edge-Computing-Engine`
|
||||
- [ ] X 光发现 license 冲突(Apache vs README"禁商用")作为关键发现
|
||||
- [ ] 与 Feature_Critic 报告内容明显不同(证明不千篇一律)
|
||||
- 两示例均落盘 report-cards/ + 写入 examples/。
|
||||
|
||||
## 15. 风险与未决
|
||||
|
||||
- **README 抽取覆盖度**:正则抽 arxiv/DOI 依赖 README 写法;对非标准 README 可能漏。fair.py 同时匹配多种句式(arxiv URL / arXiv:id / 裸 id)兜底。
|
||||
- **框架推断**:从文件名/import 推断 PyTorch/TF 有噪声 → 标"推断",不肯定。
|
||||
- **数据集识别**:已知数据集名白名单有限;未知名标"unnamed dataset (mentioned)"。
|
||||
- **GitLink file +list data 字符串化**:fair.py 需 json.loads 解套(已知)。
|
||||
- **演示仓库稳定性**:Feature_Critic 是 1⭐ 小仓但 ICML2019 真实学术代码,内容稳定;演示前复跑一次确认 README/文件未变。
|
||||
|
||||
## 16. 验收标准
|
||||
|
||||
1. `skills/gitlink-research-fair/` 含 SKILL.md(重写)+ scripts/fair.py + test_fair.py + REFERENCE.md(重写)+ 2 个 examples
|
||||
2. `python fair.py --owner liyiying10 --repo Feature_Critic` 输出合法科研画像 JSON(含真实 arxiv 1901.11448)
|
||||
3. test_fair.py 抽取函数单测全过
|
||||
4. Feature_Critic X 光:4 维裁决正确(论文✅/数据⚠️/复现⚠️/引用❌)+ 真图谱 ≥6 节点 + 关键发现 ≥3 条
|
||||
5. Edge X 光:license 冲突作为关键发现,报告与 Feature_Critic 明显不同
|
||||
6. 报告落盘 report-cards/
|
||||
7. v1 的 5 轴雷达/假 KG 已从 SKILL.md 移除
|
||||
8. README/workflow 描述更新为"科研软件 X 光"
|
||||
|
|
@ -162,7 +162,8 @@ skills/
|
|||
| **gitlink-changelog** | Release Notes 自动生成 | 从 commit/PR/Issue 历史自动生成版本说明 |
|
||||
| **gitlink-triage** | Issue 智能分拣 + 新人引导 | 自动分类、打标签、分配责任人、good-first-issue 引导 |
|
||||
| **gitlink-review** | 智能代码审查 | 分析 PR diff,多视角评审 + 对抗式自检,结构化 Review 意见自动评论 |
|
||||
| **gitlink-research-fair** | 科研软件 FAIR 体检 | 5 轴 FAIR/可复现评分、体检报告卡、自动开 PR 修复缺口、SWH/commit 可复现证书 |
|
||||
| **gitlink-research-fair** | 科研软件 X 光 | fair.py 抽论文/数据/复现/引用画像,四维裁决 + 真科研图谱 + 特有关键发现报告,可选处方 PR |
|
||||
| **gitlink-spark** | 文献-代码语义缺口挖掘机 | arXiv×GitLink×GitHub 三源挖"理论无实现/需求无解答"缺口,出机会报告,一键 fork+issue 起跑 |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,105 +1,81 @@
|
|||
# gitlink-research-fair 参考文档
|
||||
# gitlink-research-fair v2 参考文档(科研软件 X 光)
|
||||
|
||||
> 本文件为 SKILL.md 的深度参考。评分细则、KG schema、OpenAlex/SWH 技术细节、学术引用库与诚实边界均在此。
|
||||
> SKILL.md 的深度参考:四维裁决细则、真 KG schema、fair.py 抽取规则、数据源实测、学术锚。
|
||||
|
||||
## 一、FAIR4RS 逐条映射
|
||||
## 一、四维裁决细则
|
||||
|
||||
| 原则 | 含义 | 本 Skill 检查项 | GitLink 证据 | 兜底(无字段时) |
|
||||
|------|------|----------------|--------------|------------------|
|
||||
| F1 | 全局唯一持久标识 | A4 持久标识 | repo identifier/URL + commit SHA | commit-SHA 锚定 |
|
||||
| F1.2 | 版本级唯一标识 | A3 有版本发布 | `release +list` | ✗(无 release tag) |
|
||||
| F2 | 富元数据 | F1 描述/F2 topics/F3 codemeta | `repo +info` desc/topics;`file +get` codemeta.json | README 抽取(标注推断) |
|
||||
| R1.1 | 清晰可访问许可证 | A2/I2 | `repo +info` license_id;LICENSE 文件 | gitlink-license 识别 |
|
||||
| R1.2 | 详细溯源 | R1 | CITATION.cff/README 引用 | LLM 从 README 推断(标注) |
|
||||
| R2 | 对其他软件的限定引用 | R3 | 源码 SPDX/依赖清单 | gitlink-license 维度4 |
|
||||
每维 `✅/⚠️/❌`,**必须引用 fair.py 画像字段作证据**。
|
||||
|
||||
## 二、评分算法
|
||||
### 论文溯源(F2/R1.2)
|
||||
- ✅ `paper.arxiv_id` 或 `paper.doi` 非空 + `paper.title`/`venue` 抽到
|
||||
- ⚠️ 仅 `paper.title` 抽到("Code for..." 句式),无 arxiv/DOI
|
||||
- ❌ `paper.in_readme` = False
|
||||
|
||||
- 每项状态:`✓`(1) / `⚠`(0.5) / `✗`(0) / `⊥`(跳过,不计入分母)
|
||||
- 轴分 = Σ状态值 / (轴内项数 − ⊥项数) × 100
|
||||
- 总评 = 各轴分均值(全 ⊥ 轴记 N/A 排除)
|
||||
- 等级:A🟢≥80 / B🟡60-79 / C🔴<60
|
||||
- `confidence=low`:标注"待人工确认",不计入分子分母
|
||||
### 数据链(FAIR 数据维度)
|
||||
- ✅ `datasets` 非空 + 至少一个有 `download_script` + 数据 license 可考
|
||||
- ⚠️ `datasets` 非空(命名)但无 download_script 或无 license
|
||||
- ❌ `datasets` 为空
|
||||
|
||||
## 三、可复现性 checklist(独立于 FAIR)
|
||||
### 复现就绪(独立于 FAIR)
|
||||
- ✅ `repro.deps_pinned`=True + `entry_points` 非空 + `env_spec`=True + `expected_results`=True(四件齐全)
|
||||
- ⚠️ 有 `entry_points` 但缺依赖锁/环境/期望结果中任一
|
||||
- ❌ 无 `entry_points` 或无 `deps_files`
|
||||
|
||||
Rep1 环境锁(requirements 锁版本/Dockerfile/environment.yml)· Rep2 数据集说明(has_dataset/README 引用/下载脚本)· Rep3 复现步骤(README "运行/复现/Quick Start" 章节)· Rep4 版本固定(release tag 或 commit 锚定)· Rep5 入口可执行(main.py/train.py/Makefile/CLI)
|
||||
### 引用就绪(R1.1/R2)
|
||||
- ✅ `citation.cff` 或 `citation.codemeta` 为 True + 有版本/DOI
|
||||
- ⚠️ 仅 `citation.readme_bibtex` 非空(README 有引用文本,无机器可读文件)
|
||||
- ❌ 三者皆 False
|
||||
|
||||
## 四、KG schema(triples)
|
||||
## 二、真科研图谱 schema(Mermaid)
|
||||
|
||||
实体:Repo, Contributor, File, Commit, Paper, Dataset, License
|
||||
关系(主谓宾三元组):
|
||||
- (Contributor)—contributes-to→(Repo)
|
||||
- (Contributor)—authored→(Paper)
|
||||
- (Repo)—depends-on→(File/依赖)
|
||||
- (Paper)—cites→(Paper)
|
||||
- (Repo)—licensed-under→(License)
|
||||
- (Repo)—version-at→(Commit)
|
||||
- (Repo)—has→(Dataset)
|
||||
**节点**(按 fair.py 画像实例化,每个 dataset/entry/framework 各一个节点,不合并):
|
||||
- `Repo`(`:::anchor` 蓝灰,锚点:repo 名 + commit)
|
||||
- `Paper`(venue + arxiv/DOI;无则红色 `Paper: none`)
|
||||
- `Method`(从 methods[])
|
||||
- `Code`(每个 entry_point 一个节点)
|
||||
- `Dataset`(每个 dataset 一个节点;无则一个红色 `Dataset: none`)
|
||||
- `Framework`(frameworks[];未知则黄色 `framework unknown`)
|
||||
- `Citation`(cff/bibtex 状态;无则红色 `Citation: none`)
|
||||
- `License`(有/无/冲突)
|
||||
|
||||
输出:`triples.json`(数组 of {s,p,o})+ Mermaid `graph LR` 小图。
|
||||
**边**:
|
||||
- `Repo --> Paper`、`Repo --> License`
|
||||
- `Paper -->|proposes| Method`
|
||||
- `Method -->|implements| Code`
|
||||
- `Code -->|trains-on| Dataset`、`Code -->|depends-on| Framework`、`Code -->|built-by| Build(Makefile/CMake)`
|
||||
- `Paper -->|cited-via| Citation`
|
||||
- 推断/不确定的关系用虚边 `-.->`
|
||||
|
||||
## 五、OpenAlex 字段
|
||||
**状态色**(按对应维度裁决):✅ `classDef ok fill:#cfe,stroke:#3a3` / ⚠️ `classDef warn fill:#ffe,stroke:#cc3` / ❌ `classDef bad fill:#fee,stroke:#c33` / 锚点 `classDef anchor fill:#eef,stroke:#336`
|
||||
|
||||
- 端点:`https://api.openalex.org/works?search=<title>` (2025-02 起 freemium,建议带 `mailto` 参数走 polite pool)
|
||||
- 取字段:`authorships[].author.display_name`、`authorships[].institutions[].display_name`、`host_venue.display_name`(或 `primary_location.source.display_name`)、`concepts[].display_name`、`doi`
|
||||
- 降级:HTTP 429/503/无结果 → 报告卡溯源栏标"未找到关联论文"
|
||||
**语法纪律**(违反则渲染失败):节点 label 必须双引号 `P["..."]`;label 内禁用 `<> ? () {} | "`,可用 `: , . / - _`;边 label 仅字母/连字符;classDef 放最后。详见 SKILL.md「Mermaid 语法纪律」。
|
||||
|
||||
## 六、SWH-ID 说明
|
||||
## 三、fair.py 抽取规则
|
||||
|
||||
- 完整 SWH-ID = `swh:1:dir:<hash>` 或 `swh:1:rev:<hash>`,基于 Merkle DAG,需 `swh.model`(Python)计算或 `swh-identify`
|
||||
- 环境无 `swh.model` 时:用 `git+<commit-SHA>` 作版本锚定,报告卡标注"完整 SWH-ID 需 swh-identify"
|
||||
- commit SHA 来自 `commit +list`(HEAD)
|
||||
- **arxiv**:三路正则(arxiv URL / `arXiv:id` / 裸 `\d{4}.\d{4,5}`),取首个命中
|
||||
- **venue**:白名单(ICML/NeurIPS/ICLR/CVPR/ACL/...)正则
|
||||
- **datasets**:已知名白名单(PACS/Visual Decathlon/Cora/ImageNet/...)+ 数据脚本(data_gen/get_data/download)
|
||||
- **repro**:依赖文件名匹配(requirements/go.mod/environment.yml/Dockerfile/setup.py)+ 入口(main/train/run*.py)+ 期望结果(accuracy/f1/results table 正则)
|
||||
- **citation**:CITATION.cff/codemeta.json/.zenodo.json 文件存在 + README `@inproceedings/@article` bibtex
|
||||
- **frameworks**:torch/tensorflow/jax/sklearn 关键词(文件名+README),无则标"未知,verify imports"
|
||||
|
||||
## 七、学术引用库("信服"骨架,每条对抗式核验过)
|
||||
## 四、数据源实测(2026-07)
|
||||
|
||||
| 支撑点 | 文献 |
|
||||
|--------|------|
|
||||
| 科研软件 FAIR 原则 F1/F1.2/F2/R1.1/R1.2/R2 可机器校验 | Barker et al. 2022, *Nature Scientific Data*, https://www.nature.com/articles/s41597-022-01710-x |
|
||||
| 可复现性危机:2024 ICLR/ICML/NeurIPS 仅 19.5% 提供官方代码 | PaperCoder, arXiv:2504.17192 (Table 9) |
|
||||
| 结构化对比表/报告卡范式 | ORKG, Jaradeh et al. K-CAP 2019 |
|
||||
| 仓库→RDF 知识图谱 schema(13 实体/47 关系/794 万三元组) | LPWC, ISWC 2023 |
|
||||
| SWH-ID 版本锚定(Merkle DAG, git 兼容) | Di Cosmo et al. ICMS 2020, PMC7340894 |
|
||||
| 作者/机构溯源用 OpenAlex REST | Priem et al. 2022, arXiv:2205.01833 |
|
||||
| 仓库级 KG 问答(四实体+SZZ+Cypher, CoT 50%→90%) | Repo-KG, arXiv:2412.03815 |
|
||||
| 科研软件可复现徽章体系 | ACM Artifact Review Badging, https://www.acm.org/publications/policies/artifact-review-badging |
|
||||
| 直接竞品 howfairis(5 维,仅 GitHub) | https://github.com/fair-software/howfairis |
|
||||
| 战略时机:PWC 不稳定 | TIB 博客 2025-10 "Papers With Code went offline"(单一二手源,pitch 前复核) |
|
||||
| 源 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| `gitlink-cli file +get` | ✅ | content 在 `data.entries.content`(纯文本) |
|
||||
| `gitlink-cli file +list` | ✅ | `data` 是字符串化 JSON,需 json.loads |
|
||||
| `gitlink-cli repo +info` | ✅ | license_id/identifier/has_dataset |
|
||||
| `gitlink-cli commit +list` | ✅ | HEAD sha |
|
||||
| OpenAlex | ❌ 已砍 | v1 弱环节(间歇 503),v2 不依赖 |
|
||||
|
||||
## 八、诚实边界(不可过度宣称)
|
||||
## 五、学术锚(FAIR4RS)
|
||||
|
||||
1. FAIR4RS 自称 **aspirational**;本 Skill 是**自建 checker**(社区尚无认证级校验器),不说"套用现成标准工具"。
|
||||
2. **FAIRness ≠ 可复现性**(FAIR 必要非充分)—— Repro 单独成轴。
|
||||
3. OpenAlex 自 2025-02 起 freemium(~$1/day、需 key、100 req/s)—— 单仓演示够,批量控量。
|
||||
4. **禁止使用两条已证伪论点**:①"MSR 六分类法"、②"FAIR 分高→被引更多"因果。
|
||||
5. 文献多跑在 GitHub;"在 GitLink 上复刻"是合理外推,须真机跑通闭环。
|
||||
四维裁决对标 **FAIR4RS**(Barker et al. 2022, Nature Sci Data):论文溯源→F2/R1.2、数据链→FAIR-Data、复现就绪→(独立轴,FAIR 必要非充分)、引用就绪→R1.1/R2。诚实声明:这是适配版评分(社区尚无认证级自动校验器),非官方认证。
|
||||
|
||||
## 九、实测结论(live probe,2026-07-01 于 songhui18/ICCV2021)
|
||||
## 六、诚实边界
|
||||
|
||||
**目标仓库**:`songhui18/ICCV2021`(显示名"ICCV2021论文复现",URL identifier = `ICCV2021`,**注意 CLI 要用 identifier 不是中文显示名**),19⭐/17fork,3328 文件,17 个论文复现子目录,最后更新约 4 年前。
|
||||
|
||||
**关键事实**:
|
||||
- `license_id` = None;**根目录无 LICENSE**(20 个 LICENSE 全在子目录随上游代码)→ **A2 ✗**
|
||||
- releases = 0(无任何版本发布)→ **A3 ✗ / Rep4 ✗**
|
||||
- 根级文件仅 `README.md`;无根级 requirements.txt / CITATION.cff / codemeta.json / .zenodo.json
|
||||
- CITATION.cff=0、codemeta.json=0、.zenodo.json=0 → **F3 ✗ / I3 ✗ / R1 ✗**
|
||||
- requirements.txt 13 处(仅子目录)、Dockerfile 5、Makefile 3、main.py 13、train.py 16、test.py 15 → 子目录复现较完整,但**根级无统一依赖锁**
|
||||
- `has_dataset` = False → **Rep2 ✗**
|
||||
- topics = [python, jupyter notebook, cuda] → **F2 ✓**;description 详尽(≥20 字)→ **F1 ✓**
|
||||
- HEAD commit SHA = `e14ac625752171fd46c90778cf5c7b000d05307b`
|
||||
|
||||
**5 轴实测分**:F 67 · A 50 · I 17 · R 50 · Repro 40 → **总评 C 🔴 45/100**
|
||||
|
||||
| 轴 | 分 | 关键依据 |
|
||||
|----|----|----------|
|
||||
| F 可发现 | 67 | F1✓ 详尽描述 · F2✓ 3 topics · F3✗ 无 codemeta/.zenodo |
|
||||
| A 可访问 | 50 | A1✓ 公开 · A2✗ 无仓库级 license · A3✗ 无 release · A4✓ commit SHA |
|
||||
| I 可互操作 | 17 | I1⚠ 依赖散落子目录无根级锁 · I2✗ 无机器可读 license · I3✗ 无标准元数据 |
|
||||
| R 可复用 | 50 | R1✗ 无 CITATION · R2⚠ 根 README 是论文列表缺统一用法 · R4✓ 无明显敏感泄露 |
|
||||
| Repro 可复现 | 40 | Rep1⚠ 子目录 requirements 无根级锁 · Rep2✗ 无数据集说明 · Rep3⚠ 子目录有步骤无统一复现章 · Rep4✗ 无 release · Rep5✓ 入口齐全 |
|
||||
|
||||
**结论**:一个 19⭐ 的"论文复现"合集,FAIR/可复现维度仅得 C——无仓库级 license、无 release、无 CITATION、无数据集说明、依赖散落子目录。**正好印证可复现性危机**(2024 顶会仅 19.5% 提供官方代码)。处方空间大:补根级 LICENSE + CITATION.cff + 统一 requirements + 打 v1.0 release + README 复现章节。
|
||||
|
||||
> **fork→PR 链路(Task 7 实测,2026-07-01)**:✅ 跑通。`repo +fork` → `branch +create feat/fair-remediation --from master` → `file +create` 建 4 文件(LICENSE / CITATION.cff / codemeta.json / REPRODUCIBILITY.md)→ `pr +create` → **PR #1**(`caoweiqiong/ICCV2021#1`,open,id 145336)。**全程纯 API 建文件,无需克隆 3328 文件大仓**(`file +create` 支持 `--branch`)。
|
||||
> **OpenAlex 溯源(Task 7 实测)**:⚠️ 匿名搜索被限流(HTTP 503 *"rate-limited due to heavy load, use free API key"*),溯源栏降级跳过——**正好印证 freemium 边界(§八-3)**,skill 优雅降级、报告卡标注"未命中"。
|
||||
> **SWH-ID(Task 7 实测)**:环境无 `swh.model`,证书用 `git+commit e14ac62` 锚定(符合 §六降级)。
|
||||
1. fair.py 抽取覆盖度受 README 写法影响;非标准 README 可能漏(同时匹配多句式兜底)。
|
||||
2. 框架/方法为推断,标"推断"/"未知",不肯定。
|
||||
3. 复现就绪是**静态**判断(依赖/入口/环境/期望结果四件套),不实际跑代码。
|
||||
4. v1 的 OpenAlex 溯源已砍(避免 503 弱环节)。
|
||||
|
|
|
|||
|
|
@ -1,152 +1,140 @@
|
|||
---
|
||||
name: gitlink-research-fair
|
||||
version: 1.0.0
|
||||
description: "科研软件 FAIR 体检:分析 GitLink 科研仓库的可发现/可访问/可互操作/可复用/可复现性,输出体检报告卡,自动开 PR 修复缺口并签发 SWH/commit 锚定的可复现证书。当用户需要评估科研仓库的 FAIR 性与可复现性、生成科研软件体检报告时触发。"
|
||||
version: 2.0.0
|
||||
description: "科研软件 X 光:用 fair.py 真抽取 GitLink 科研仓库的论文/数据/复现/引用画像,LLM 四维裁决,输出含真科研图谱与特有关键发现的洞察报告,可选处方 PR。当用户需要深挖科研仓库的科研产物、评估可复现/可引用性时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli repo --help"
|
||||
cliHelp: "python skills/gitlink-research-fair/scripts/fair.py --help"
|
||||
---
|
||||
|
||||
# gitlink-research-fair(科研软件 FAIR 体检)
|
||||
# gitlink-research-fair(科研软件 X 光)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有写操作(开修复 PR)默认先预览、确认后再执行;`--auto` 跳过预览但仍受护栏约束。**
|
||||
**CRITICAL — 绝不自动 merge;绝不 force-push;处方只对 fork 开 PR,绝不碰原仓库。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
**CRITICAL — 裁决由 LLM 做出,但每条必须引用 `fair.py` 抽到的实证(arxiv id / 文件名 / deps 状态);无实证的判断丢弃。**
|
||||
**CRITICAL — 处方(开 PR)默认预览确认;绝不自动 merge、绝不 force-push、绝不碰原仓库。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md);详细检查清单、评分算法、KG schema 与学术引用见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md);四维裁决细则、真 KG schema、抽取规则见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
## 概述
|
||||
|
||||
本 Skill 引导 AI 对任意 GitLink 科研仓库做结构化、有学术标准依据、低误报的"FAIR + 可复现性"诊断,产出**体检报告卡**,并对缺口**自动开 PR 修复**、用 **SWH/commit 锚定**签发**可复现证书**。许可证/安全子项**复用 `gitlink-license`**。四幕剧本(v1):诊断 → 处方 → 证书。
|
||||
**科研软件 X 光**:`scripts/fair.py` 真抽取仓库内容(README/文件/依赖)→ 科研画像 JSON;LLM 对 4 个科研专属维度裁决(论文溯源/数据链/复现就绪/引用就绪),渲染**每 repo 特有的洞察报告**(裁决总览 + 真科研图谱 + 关键发现),可选处方 PR。与 `gitlink-health`(项目过程健康)正交,与 `gitlink-spark`(跨仓挖缺口)互补——本 skill **单仓深挖科研产物**。
|
||||
|
||||
## 命令接口(skill 约定参数,非 CLI flag)
|
||||
## 命令接口
|
||||
|
||||
```bash
|
||||
python skills/gitlink-research-fair/scripts/fair.py --owner <owner> --repo <identifier>
|
||||
# → stdout: 科研画像 JSON {paper, datasets, repro, citation, methods, frameworks, license, head_sha, files_count}
|
||||
```
|
||||
|
||||
skill 约定参数(非 CLI flag):
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--owner/--repo` | 自动从 cwd 解析 | 目标科研仓库 |
|
||||
| `--lenses` | 全 5 轴 | 子集,如 `F,A,Repro` |
|
||||
| `--auto` | 关 | 跳过预览直接开 PR(仍受护栏) |
|
||||
| `--no-fork` | 关 | 不开 PR,处方物料落本地 |
|
||||
| `--enrich` | 开 | 启用 OpenAlex 溯源(限流自动降级) |
|
||||
| `--refresh` | 关 | 即使有旧报告哨兵也重评 |
|
||||
| `--owner/--repo` | 自动从 cwd 解析 | 目标科研仓库(用 identifier) |
|
||||
| `--auto` | 关 | 跳过预览直接开处方 PR(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出 X 光报告,不开 PR |
|
||||
| `--refresh` | 关 | 即使有旧哨兵也重评 |
|
||||
|
||||
## 管道(8 步)
|
||||
## 管道
|
||||
|
||||
### ① 取上下文
|
||||
`repo +info`(元数据/license/topics/has_dataset)· `file +list`(关键文件清单)
|
||||
### ① 抽取(fair.py,确定性)
|
||||
`fetch_readme`(file +get)+ `fetch_file_list`(file +list)+ `fetch_repo_meta`(repo +info + commit +list 取 HEAD sha)→ `extract_paper` / `extract_datasets` / `assess_repro` / `assess_citation` / `extract_methods_frameworks` → 科研画像 JSON
|
||||
|
||||
### ② 深采
|
||||
`file +get` 读 LICENSE/CITATION.cff/codemeta.json/README/requirements/Dockerfile · `release +list`(版本)· `commit +list`(SHA)· `issue/pr +list`(协作)· `member +list`(作者)
|
||||
### ② 四维裁决(LLM,读 JSON)
|
||||
论文溯源 / 数据链 / 复现就绪 / 引用就绪。每维 `✅/⚠️/❌` + **引用画像字段的具体证据**。规则见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
### ③ FAIR 评分(M1)
|
||||
逐项评 5 轴(许可证/安全子项委托 `gitlink-license` 流程)→ `✓/✗/⚠/⊥` + 证据 + 置信度
|
||||
### ③ 真科研图谱(从画像生成 Mermaid)
|
||||
节点 `Paper↔Method↔Code↔Dataset↔Framework↔Citation`,带状态色(✅绿/⚠️黄/❌红)。schema 见 REFERENCE。
|
||||
|
||||
### ④ KG 构建(M2)
|
||||
四实体(Repo/Contributor/File/Commit)+Paper/Dataset/License 节点 → Mermaid 小图 + triples JSON
|
||||
### ④ 渲染 X 光报告(hero)并落盘
|
||||
裁决一行 + 真图谱 + **本 repo 特有关键发现** + 处方摘要 + 双裁决证书。**始终保存** `report-cards/<owner>-<repo>-xray.md`(绝不只在终端)。
|
||||
|
||||
### ⑤ 溯源富集(M3)
|
||||
从 README/CITATION 抽论文 → OpenAlex REST 反查作者/机构(best-effort)
|
||||
### ⑤ 处方(可选)
|
||||
对 ❌/⚠️ 项生成 CITATION.cff(从 README 抽的引用)/ requirements.txt(从 import 扫)/ Dockerfile → fork → PR(默认预览)。
|
||||
|
||||
### ⑥ 渲染报告卡(hero)并落盘
|
||||
等级 + 雷达图 + 5 轴逐项表 + 处方摘要 + 证书栏 + KG 小图 + 溯源栏。
|
||||
**始终保存为本地文件** `report-cards/<owner>-<repo>-report-card.md`(cwd 下,含哨兵)——报告卡是可归档/复查的核心产物,**无论后续是否开 PR 都必须落盘,绝不只在终端输出**。
|
||||
## 四维裁决(速览,细则见 REFERENCE)
|
||||
|
||||
### ⑦ 处方(M4)
|
||||
对 `✗/⚠` 项生成 CITATION.cff/codemeta.json/LICENSE/复现章节 → 默认预览
|
||||
| 维度 | ✅ | ⚠️ | ❌ |
|
||||
|------|---|----|----|
|
||||
| 论文溯源 | arxiv/DOI + 元数据全 | 仅 README 文字,无稳定链接 | 无论文线索 |
|
||||
| 数据链 | 命名 + 下载脚本 + license | 命名但无脚本/无 license | 未提及数据集 |
|
||||
| 复现就绪 | 依赖锁+入口+环境+期望结果齐全 | 有入口但缺依赖锁/环境/期望结果 | 无入口/无依赖 |
|
||||
| 引用就绪 | CITATION.cff/codemeta + DOI + 版本 | 仅 README 引用文本 | 无引用信息 |
|
||||
|
||||
### ⑧ 发布
|
||||
报告卡已在 ⑥ 落盘(`report-cards/<owner>-<repo>-report-card.md`)。
|
||||
- **fork 模式**:确认后对 fork 开 PR(PR body 带报告卡摘要)+ 出 SWH/commit 证书
|
||||
- **`--no-fork` 或 PR 失败**:报告卡已在本地,告知用户路径,可手动粘贴或提交
|
||||
- 哨兵内嵌于报告卡(及 PR body,若开)
|
||||
|
||||
## 5 轴评分 Rubric
|
||||
|
||||
> 对标 **FAIR4RS(Nature Sci Data 2022)+ howfairis 5 维**。诚实声明:这是**适配版**评分(社区尚无认证级自动校验器),不是官方认证。**FAIR ≠ 可复现**,故 Repro 单独成轴。逐条映射与证据见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
每项判定 `✓满足 / ✗缺失 / ⚠部分 / ⊥GitLink 无该字段(跳过并标注)`,附证据来源 + 置信度。
|
||||
|
||||
| 轴 | FAIR4RS 映射 | 检查项(证据) |
|
||||
|----|--------------|----------------|
|
||||
| **F 可发现** | F1/F2 | F1 清晰描述(repo desc ≥20 字) · F2 话题/关键词(topics) · F3 标准元数据(codemeta.json/.zenodo.json) |
|
||||
| **A 可访问** | F1.2/R1.1 | A1 公开(is_public) · A2 有许可证(license_id/LICENSE)[gitlink-license] · A3 有版本发布(release tag) · A4 持久标识(commit SHA/SWH-ID) |
|
||||
| **I 可互操作** | R1.1/I | I1 依赖清单(requirements/go.mod/package.json/environment.yml) · I2 许可证机器可读(SPDX/license_id) · I3 元数据标准格式(CITATION.cff/codemeta) |
|
||||
| **R 可复用** | R1.2/R2 | R1 溯源/引用(CITATION.cff/README 引用) · R2 README 质量(安装+使用) · R3 源码声明[gitlink-license] · R4 无敏感泄露[gitlink-license] |
|
||||
| **Repro 可复现** ⭐差异轴 | (独立于 FAIR) | Rep1 环境锁(锁版本/Dockerfile) · Rep2 数据集说明(has_dataset/README 引用) · Rep3 复现步骤(README 章节) · Rep4 版本固定 · Rep5 入口可执行(main/Makefile/CLI) |
|
||||
|
||||
**评分**:轴分 = 满足项 / (总项 − ⊥项)(⚠ 计 0.5);总评 = 5 轴均分(某轴全 ⊥ 则记 N/A 并排除);等级 **A🟢≥80 / B🟡60-79 / C🔴<60**。`confidence=low` 标"待人工确认",不进总评。
|
||||
|
||||
## 报告卡格式(hero 产出)
|
||||
## 报告格式(混合主视觉,hero)
|
||||
|
||||
````markdown
|
||||
🏥 **gitlink-research-fair 科研软件体检报告**
|
||||
🔬 **科研软件 X 光 — <owner>/<repo>**
|
||||
|
||||
**总体**:<等级> <分>/100(F · A · I · R · Repro)|版本锚定:commit <sha>(<release 或 无>)
|
||||
**范围**:检查 19 项(✓N · ✗N · ⚠N · ⊥N,跳过:<原因>)
|
||||
═══════════════════════════════════════
|
||||
论文溯源 <V> | 数据链 <V> | 复现就绪 <V> | 引用就绪 <V>
|
||||
═══════════════════════════════════════
|
||||
|
||||
### 五轴雷达
|
||||
(ASCII 或 Mermaid 雷达:F/A/I/R/Repro)
|
||||
|
||||
### 逐项
|
||||
| 轴 | 项 | 状态 | 证据 |
|
||||
|---|---|:---:|---|
|
||||
| <轴> | <id 项名> | ✓/✗/⚠/⊥ | <gitlink-cli 证据> |
|
||||
|
||||
### 🔧 处方(可自动修复 N 项)
|
||||
- [<轴><id>] <生成物>(从 <来源> 推断)
|
||||
|
||||
### 📜 可复现证书
|
||||
锚定版本:git+commit <sha> | FAIR: <等级> | Repro: <状态>
|
||||
(完整 SWH-ID 需 swh-identify;本次用 commit-SHA 锚定)
|
||||
|
||||
### 🔗 溯源(OpenAlex)
|
||||
<作者/机构/载体 或 "未找到关联论文,已跳过">
|
||||
|
||||
### 🧬 科研关系图(KG)
|
||||
### 🧬 真科研图谱
|
||||
```mermaid
|
||||
<Repo—contributes→Contributor · Repo—licensed?—? · Repo—has→Dataset?>
|
||||
graph LR
|
||||
R["repo: Feature_Critic"]:::anchor
|
||||
P["Paper: ICML 2019, arxiv:1901.11448"]:::ok
|
||||
M["Method: Feature-Critic / meta-learning"]:::ok
|
||||
C["Code: main_Feature_Critic.py"]:::ok
|
||||
D1["Dataset: PACS"]:::warn
|
||||
D2["Dataset: Visual Decathlon"]:::warn
|
||||
D3["Dataset: ImageNet"]:::warn
|
||||
F["Framework: PyTorch"]:::warn
|
||||
Ci["Citation: README bibtex, no CITATION.cff"]:::bad
|
||||
L["License: missing"]:::bad
|
||||
R --> P
|
||||
R --> L
|
||||
P -->|proposes| M
|
||||
M -->|implements| C
|
||||
C -->|trains-on| D1
|
||||
C -->|trains-on| D2
|
||||
C -->|trains-on| D3
|
||||
C -->|depends-on| F
|
||||
P -->|cited-via| Ci
|
||||
classDef ok fill:#cfe,stroke:#3a3
|
||||
classDef warn fill:#ffe,stroke:#cc3
|
||||
classDef bad fill:#fee,stroke:#c33
|
||||
classDef anchor fill:#eef,stroke:#336
|
||||
```
|
||||
|
||||
### 🔍 关键发现(本 repo 特有)
|
||||
- <LLM 从画像抽出的 ≥3 条具体发现,每条引用 fair.py 字段>
|
||||
|
||||
### 🔧 处方(可选)
|
||||
- <对 ❌/⚠️ 项的修复建议>
|
||||
|
||||
### 📜 双裁决证书
|
||||
复现就绪 <V> | 引用就绪 <V> | 锚定 commit `<sha>`
|
||||
|
||||
---
|
||||
<!-- gitlink-research-fair v1 | repo:<owner>/<repo> | grade:<G> | sha:<head-sha> -->
|
||||
*由 gitlink-research-fair skill 生成。*
|
||||
<!-- gitlink-research-fair v2 | repo:<owner>/<repo> | paper:<✅/⚠️/❌> | repro:<V> | cite:<V> | sha:<head> -->
|
||||
*由 gitlink-research-fair v2(科研软件 X 光)生成。*
|
||||
````
|
||||
|
||||
哨兵 `<!-- gitlink-research-fair v1 | repo | grade | sha -->` 用于幂等与"基于哪个 commit"标识。重跑检测旧哨兵 → 默认提议更新(`--refresh` 才覆盖);`sha` 不匹配 → 提示"报告已过期,建议重评"。
|
||||
## Mermaid 语法纪律(必读,否则图谱渲染失败)
|
||||
|
||||
## 处方(M4,惊艳闭环)
|
||||
- 节点 label **必须双引号**:`P["Paper: ..."]`,**禁止**裸 `P[<...>]` 或 `P[label with ?]`
|
||||
- label 内**禁用** `<> ? () {} | "` 等特殊字符;可用:字母、数字、空格、`: , . / - _`
|
||||
- 边 label 用 `-->|word|`,word 仅字母/连字符(如 `trains-on`、`cited-via`),不要放 `?` 或中文标点
|
||||
- **每个 dataset / entry / framework 各一个节点**(如 `D1["Dataset: PACS"]` `D2["Dataset: Visual Decathlon"]`),不要合并成一个 `D["datasets"]`
|
||||
- 缺失项用红色节点(`:::bad`)显式标出(如 `L["License: missing"]:::bad`),不省略——"缺什么"也是图谱信息
|
||||
- `classDef` 放在最后;锚点 repo 节点用 `:::anchor`(蓝灰)区分
|
||||
|
||||
| 缺口 | 生成物 |
|
||||
|------|--------|
|
||||
| F3 缺 codemeta.json | 从 repo +info + README 生成 codemeta.json 草稿 |
|
||||
| R1 缺 CITATION.cff | 从 README/作者生成 CITATION.cff 草稿 |
|
||||
| A2 缺 license | 给 MIT / 木兰 PSL v2 模板(任选) |
|
||||
| Rep1 缺依赖锁 | 从 import 扫描建议 requirements.txt |
|
||||
| Rep3 缺复现章节 | 从入口/README 生成"复现"章节草稿 |
|
||||
## 处方闭环 + 护栏
|
||||
|
||||
**安全护栏**:默认全量预览 → 确认 → 对 **fork** 开一个 PR(PR body 带报告卡摘要);`--auto` 跳过预览但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 物料落本地。所有写操作需认证。
|
||||
|
||||
## 证书(SWH/commit 锚定)
|
||||
|
||||
用 HEAD commit SHA + release tag 锚定版本。若环境有 Python `swh.model` → 算完整 SWH-ID;否则 `git+<commit-SHA>` 锚定,报告卡标注"完整 SWH-ID 需 swh-identify"。证书含:仓库、锚定版本、FAIR 等级、Repro 状态、可引用条目(对标 ACM Artifact Badge)。嵌报告卡证书栏。
|
||||
|
||||
## KG + OpenAlex(支撑栏)
|
||||
|
||||
- **KG**:四实体(Repo/Contributor/File/Commit)+Paper/Dataset/License;关系 contributes-to/authored/depends-on/cites/licensed-under/version-at。输出 Mermaid 小图 + triples JSON(schema 见 REFERENCE)。
|
||||
- **OpenAlex**:README/CITATION 抽论文 → REST `/works` 反查作者/机构/载体。降级:限流或抽不到 → 跳过,标注"未找到关联论文"。
|
||||
对 ❌/⚠️ 项生成修复:`CITATION.cff`(从 README 抽的引用文本构造)+ `requirements.txt`(从代码 import 扫)+ `Dockerfile`(模板)→ `repo +fork` → `pr +create`(PR body 带报告摘要)。**护栏**:默认预览;`--auto` 跳过但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 报告已落盘(④),不开 PR。
|
||||
|
||||
## 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| `file +tree --recursive` 返回空 | 手动递归子目录 |
|
||||
| 无 LICENSE | A2 ✗,处方建议模板 |
|
||||
| OpenAlex 限流/无果 | 跳过溯源栏标注 |
|
||||
| `swh.model` 不可用 | commit-SHA 锚定 + 标注 |
|
||||
| 仓库过大 | 抽样文件 + "部分审查"标注 |
|
||||
| fork 失败/无写权限 | 处方物料落本地(`--no-fork`) |
|
||||
| 二进制是 npm 旧版 | 强制 `./gitlink-cli` 或 `go build` 重建 |
|
||||
| 仓库私有/无权限 | 清晰错误,不发报告 |
|
||||
| 报告/PR 发布失败 | 报告卡已在 ⑥ 落盘,告知路径,用户可手动粘贴/提交 |
|
||||
| README 读失败 | 降级用 file list + 元数据,标注"README 不可读,结论受限" |
|
||||
| `file +list` data 为字符串 | json.loads 解套 |
|
||||
| arxiv/DOI 抽不到 | 论文溯源判 ⚠️/❌,据实 |
|
||||
| 框架无法推断 | frameworks 标"未知",不编造 |
|
||||
| `--repo` 用中文显示名 404 | 提示用 identifier |
|
||||
| fork/PR 失败 | 处方物料落本地,告知路径 |
|
||||
| 报告/PR 发布失败 | 报告已在 ④ 落盘,告知路径 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
# 示例:科研软件 X 光 — Edge-Computing-Engine(引擎类对照)
|
||||
|
||||
> 基于 `fair.py --owner Edgedev --repo Edge-Computing-Engine` 于 2026-07-07 实跑(fair.py v2.1,已修 arxiv 年份校验 + C++ 入口检测)。
|
||||
> Edge 是一个 C++ 科学计算引擎(autodiff + 神经网络模块),与 Feature_Critic(paper-code 类)形成**不同 profile 对照**,证明 X 光报告不千篇一律。
|
||||
|
||||
## Step 1:运行 fair.py 抽取科研画像
|
||||
|
||||
```bash
|
||||
python skills/gitlink-research-fair/scripts/fair.py --owner Edgedev --repo Edge-Computing-Engine
|
||||
```
|
||||
|
||||
**画像摘要**:
|
||||
| 维度 | 抽取结果 |
|
||||
|---|---|
|
||||
| 论文 | arxiv **None**(README 全文无有效 arxiv/DOI)| venue None | 标题 "Edge-Engine"(H1,不计入 in_readme)| in_readme ❌ |
|
||||
| 数据集 | **空**(未提及) |
|
||||
| 复现 | 入口 **main.cpp**(v2.1 已识别 C++ 入口)| deps **Makefile**(deps_pinned ✓)| env ❌ | expected_results ❌ |
|
||||
| 引用 | 无 CITATION.cff | 无 README bibtex |
|
||||
| 方法/框架 | "graph" 命中 | 框架未知(C++,import 推断失效) |
|
||||
| license | **LICENSE 文件存在**(Apache 2.0)| license_id None |
|
||||
| 锚定 | commit `8678c7c7` | 132 文件 |
|
||||
|
||||
## Step 2:四维裁决(LLM 读画像)
|
||||
|
||||
**论文溯源 ❌**(无论文链接)· **数据链 ❌** · **复现就绪 ⚠️**(有 C++ 入口+Makefile 构建,但缺环境锁/期望结果)· **引用就绪 ❌**
|
||||
|
||||
## Step 3:X 光报告(全文,落盘 `report-cards/Edgedev-Edge-Computing-Engine-xray.md`)
|
||||
|
||||
````markdown
|
||||
🔬 **科研软件 X 光 — Edgedev/Edge-Computing-Engine**
|
||||
|
||||
═══════════════════════════════════════
|
||||
论文溯源 ❌ | 数据链 ❌ | 复现就绪 ⚠️ | 引用就绪 ❌
|
||||
═══════════════════════════════════════
|
||||
|
||||
### 🧬 真科研图谱
|
||||
```mermaid
|
||||
graph LR
|
||||
R["repo: Edge-Computing-Engine"]:::anchor
|
||||
P["Paper: none, no arxiv or DOI"]:::bad
|
||||
C["Code: main.cpp / C++"]:::warn
|
||||
F["Build: Makefile, framework unknown"]:::warn
|
||||
Ci["Citation: none"]:::bad
|
||||
L["License: Apache 2.0 file but README forbids commercial use"]:::warn
|
||||
R --> P
|
||||
R --> L
|
||||
P -.->|proposes| C
|
||||
C -->|built-by| F
|
||||
P -->|cited-via| Ci
|
||||
classDef ok fill:#cfe,stroke:#3a3
|
||||
classDef warn fill:#ffe,stroke:#cc3
|
||||
classDef bad fill:#fee,stroke:#c33
|
||||
classDef anchor fill:#eef,stroke:#336
|
||||
```
|
||||
|
||||
### 🔍 关键发现(本 repo 特有)
|
||||
- **无论文**:README 全文无 arxiv/DOI 链接,论文溯源 ❌(fair.py arxiv 候选经年份校验过滤,无有效命中)
|
||||
- **LICENSE 自相矛盾**:根目录有 Apache 2.0 LICENSE,但 README 声明"本项目禁止闭源商用"——与 Apache 2.0(允许商用)冲突,复用有法律风险
|
||||
- **复现部分就绪**:fair.py 已识别 C++ 入口 `main.cpp` + 构建文件 `Makefile`,但无 Dockerfile/environment 锁环境、README 无 expected_results → ⚠️ 而非 ❌
|
||||
- 无数据集声明、无 CITATION.cff、无 README bibtex
|
||||
- 132 文件,含 autodiff / 神经网络模块
|
||||
|
||||
### 🔧 处方(可选)
|
||||
- 澄清许可证(去 README "禁止闭源商用" 或换 CC BY-NC)
|
||||
- 补 README:论文/数据集/构建命令(C++ make/make install 已有,缺期望结果)
|
||||
- 补 `Dockerfile` 锁编译器/依赖环境
|
||||
|
||||
### 📜 双裁决证书
|
||||
复现就绪 ⚠️ 部分 | 引用就绪 ❌ | 锚定 commit `8678c7c7`
|
||||
````
|
||||
|
||||
## Step 4:与 Feature_Critic 对照(证明不千篇一律)
|
||||
|
||||
| 维度 | Feature_Critic(paper-code) | Edge(引擎类) |
|
||||
|---|---|---|
|
||||
| 论文溯源 | ✅ arxiv 1901.11448 真溯源 | ❌ 无论文链接 |
|
||||
| 数据链 | ⚠️ PACS/Visual Decathlon 命名 | ❌ 无数据集 |
|
||||
| 复现就绪 | ⚠️ 有 .py 入口,无依赖锁 | ⚠️ 有 C++ 入口+Makefile,无环境/期望结果 |
|
||||
| 引用就绪 | ⚠️ README bibtex | ❌ 无 |
|
||||
| 真图谱 | 10 节点富图(repo/论文/方法/代码/3数据集/PyTorch/引用/license) | 6 节点多红黄(repo/无论文/main.cpp/Makefile/无引用/license冲突) |
|
||||
| 关键发现 | 论文可溯源但机器不可引用 | **LICENSE 自相矛盾** + 无论文 |
|
||||
|
||||
**两份报告内容截然不同**——X 光由 fair.py 抽取的真实画像驱动,每 repo 说出自己的话。
|
||||
|
||||
## 关键结论
|
||||
- fair.py v2.1 已修复两个已知限制:arxiv 候选加年份合法性校验(防 `5184.0000` 类伪阳性)、入口检测扩展到 `.cpp/.c/.cc/.cu` + `CMakeLists.txt`/`Makefile`(覆盖 C/C++ 科研代码)——Edge 复现就绪从 ❌ 升到 ⚠️,论文溯源 ❌ 干净不再误报
|
||||
- LICENSE 冲突这类"元数据扫描发现不了、需读 README 内容"的问题,由 LLM 裁决层补上(fair.py 抽 file 存在,LLM 读出冲突)
|
||||
- 引擎类(C++/无论文)与 paper-code 类(Python/有论文)画像迥异,报告自然分化
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
# 示例:科研软件 FAIR 体检(真实数据)
|
||||
|
||||
> 基于 `songhui18/ICCV2021`(显示名"ICCV2021论文复现",URL identifier=`ICCV2021`)于 2026-07-01 在 Claude Code 中实跑。
|
||||
> 这是 19⭐/17fork 的 ICCV 2021 论文复现合集,3328 文件、17 个论文复现子目录。
|
||||
> 一个"**可复现性**"工具去体检一个"**论文复现**"合集——主题共振。
|
||||
|
||||
---
|
||||
|
||||
## Step 1:取上下文(真实输出摘要)
|
||||
|
||||
| 项 | 实测值 |
|
||||
|---|---|
|
||||
| URL identifier | `ICCV2021`(**CLI 用 identifier,不是中文显示名**) |
|
||||
| `license_id` | `None`(**根目录无 LICENSE**;20 个 LICENSE 全在子目录随上游代码) |
|
||||
| releases | `0`(无任何版本发布) |
|
||||
| 根级文件 | 仅 `README.md` |
|
||||
| CITATION.cff / codemeta.json / .zenodo.json | 均无 |
|
||||
| requirements.txt | 13 处(**仅子目录,无根级依赖锁**) |
|
||||
| `has_dataset` | `False` |
|
||||
| topics | python, jupyter notebook, cuda |
|
||||
| description | 详尽(≥20 字) |
|
||||
| HEAD commit | `e14ac625752171fd46c90778cf5c7b000d05307b` |
|
||||
|
||||
> ⚠️ **关键踩坑**:`gitlink-cli --repo` 必须传 `identifier=ICCV2021`;用中文显示名"ICCV2021论文复现"会返回 **404**。identifier 从 `search +repos` 结果取。
|
||||
|
||||
采集命令:
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +info --owner songhui18 --repo ICCV2021 --format json
|
||||
gitlink-cli file +list --owner songhui18 --repo ICCV2021 --format json # 3328 文件扁平列表
|
||||
gitlink-cli release +list --owner songhui18 --repo ICCV2021 --format json
|
||||
gitlink-cli commit +list --owner songhui18 --repo ICCV2021 --page 1 --format json
|
||||
```
|
||||
|
||||
## Step 2:5 轴评分(真实)
|
||||
|
||||
| 轴 | 分 | 关键依据 |
|
||||
|---|---|---|
|
||||
| F 可发现 | 67 | F1✓ 详尽描述 · F2✓ 3 topics · F3✗ 无 codemeta/.zenodo |
|
||||
| A 可访问 | 50 | A1✓ 公开 · A2✗ **无仓库级 license** · A3✗ 无 release · A4✓ commit SHA |
|
||||
| I 可互操作 | 17 | I1⚠ 依赖散落子目录无根级锁 · I2✗ 无机器可读 license · I3✗ 无标准元数据 |
|
||||
| R 可复用 | 50 | R1✗ 无 CITATION · R2⚠ 根 README 是论文列表 · R4✓ 无明显敏感泄露 |
|
||||
| Repro 可复现 | 40 | Rep1⚠ · Rep2✗ 无数据集说明 · Rep3⚠ · Rep4✗ 无 release · Rep5✓ 入口齐全 |
|
||||
|
||||
**总评:C 🔴 45/100** —— 一个 19⭐ 的论文复现合集,FAIR/可复现仅得 C。
|
||||
|
||||
## Step 3:报告卡(真实全文)
|
||||
|
||||
````markdown
|
||||
🏥 **gitlink-research-fair 科研软件体检报告**
|
||||
|
||||
**总体**:C 🔴 45/100(F 67 · A 50 · I 17 · R 50 · Repro 40)|版本锚定:commit `e14ac62`(无 release)
|
||||
**范围**:检查 19 项(✓6 · ✗8 · ⚠4 · ⊥1,⊥:R3 源码声明委托 gitlink-license 未在本轮跑)
|
||||
|
||||
### 逐项(节选)
|
||||
| 轴 | 项 | 状态 | 证据 |
|
||||
|---|---|:---:|---|
|
||||
| A | A2 有许可证 | ✗ | `license_id` 为空,根目录无 LICENSE(20 个 LICENSE 全在子目录) |
|
||||
| F | F3 标准元数据 | ✗ | 无 codemeta.json / .zenodo.json |
|
||||
| I | I1 依赖清单 | ⚠ | requirements.txt 仅在 13 个子目录,无根级统一锁 |
|
||||
| R | R1 溯源/引用 | ✗ | 无 CITATION.cff |
|
||||
| R | R3 源码声明 | ⊥ | 委托 gitlink-license,本轮未跑深度源码声明扫描 |
|
||||
| Repro | Rep2 数据集说明 | ✗ | `has_dataset=False`,README 无数据集说明 |
|
||||
| Repro | Rep5 入口可执行 | ✓ | 子目录含 train.py×16 / main.py×13 / test.py×15 |
|
||||
| F | F1 清晰描述 | ✓ | description 详尽描述 CV 顶会论文复现合集 |
|
||||
|
||||
### 🔧 处方(可自动修复 4 项)
|
||||
- [A2] 添加 LICENSE(MIT / 木兰 PSL v2)
|
||||
- [R1] 生成 CITATION.cff
|
||||
- [F3/I3] 生成 codemeta.json
|
||||
- [Rep3] 补 REPRODUCIBILITY.md 复现指南
|
||||
|
||||
### 📜 可复现证书
|
||||
锚定版本:`git+commit e14ac625752171fd46c90778cf5c7b000d05307b` | FAIR: C 🔴 | Repro: ⚠ 部分可复现
|
||||
(完整 SWH-ID 需 `swh-identify`;环境无 `swh.model`,本次用 commit-SHA 锚定)
|
||||
|
||||
### 🔗 溯源(OpenAlex)
|
||||
⚠️ 未命中——匿名搜索被限流(HTTP 503,需 free API key),已降级跳过(符合 freemium 边界)。
|
||||
|
||||
### 🧬 科研关系图(KG)
|
||||
```mermaid
|
||||
graph LR
|
||||
R[Repo: ICCV2021合集] -->|contributes-to| C1[Contributor: 宋辉/songhui18]
|
||||
R -->|has| D{Dataset?}:::miss
|
||||
R -->|licensed-under| L{License?}:::miss
|
||||
R -->|version-at| H[commit e14ac62]
|
||||
classDef miss fill:#fee,stroke:#c33;
|
||||
```
|
||||
|
||||
---
|
||||
<!-- gitlink-research-fair v1 | repo:songhui18/ICCV2021 | grade:C | sha:e14ac625 -->
|
||||
*由 gitlink-research-fair skill 生成。*
|
||||
````
|
||||
|
||||
## Step 4:处方 PR(真实闭环 ✅)
|
||||
|
||||
**全自动跑通**(无需克隆 3328 文件大仓,纯 API 建文件):
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +fork --owner songhui18 --repo ICCV2021 # → caoweiqiong/ICCV2021
|
||||
gitlink-cli branch +create --owner caoweiqiong --repo ICCV2021 --name feat/fair-remediation --from master
|
||||
gitlink-cli file +create --owner caoweiqiong --repo ICCV2021 --branch feat/fair-remediation \
|
||||
--path LICENSE --message "Add MIT LICENSE (fix A2)" --content "$(cat LICENSE)"
|
||||
# … 同样建 CITATION.cff / codemeta.json / REPRODUCIBILITY.md
|
||||
gitlink-cli pr +create --owner caoweiqiong --repo ICCV2021 --head feat/fair-remediation --base master \
|
||||
--title "Improve research FAIRness & reproducibility (gitlink-research-fair 处方)" \
|
||||
-b "$(cat pr_body.md)"
|
||||
```
|
||||
|
||||
**PR**:https://www.gitlink.org.cn/caoweiqiong/ICCV2021/pulls/1 (`pull_request_number: 1`, id `145336`, open)
|
||||
|
||||
PR body 含体检报告卡摘要 + 修复表。新增 4 文件:
|
||||
|
||||
| 文件 | 修复项 | FAIR 原则 |
|
||||
|------|--------|-----------|
|
||||
| `LICENSE` | 补 MIT 许可证 | R1.1 / A2 |
|
||||
| `CITATION.cff` | 补引用信息 | R1.2 / R1 |
|
||||
| `codemeta.json` | 补标准元数据 | F2 / F3 / I3 |
|
||||
| `REPRODUCIBILITY.md` | 补复现指南 | Rep3 |
|
||||
|
||||
**预期效果**:合并后 A2/F3/I3/R1/Rep3 由 ✗/⚠ → ✓,总分 **C 45 → B ~75**。
|
||||
|
||||
## Step 5:证书
|
||||
|
||||
- **锚定版本**:`git+commit e14ac625752171fd46c90778cf5c7b000d05307b`(HEAD,无 release)
|
||||
- **SWH-ID**:环境无 `swh.model`,用 commit-SHA 锚定(完整 SWH-ID 需 `swh-identify`)
|
||||
- **结论**:FAIR C 🔴 · Repro ⚠ 部分可复现 —— 可作为"待改进科研软件"的基线快照,供后续 release 后重评对比。
|
||||
|
||||
---
|
||||
|
||||
## 对照:有 license 的仓库(证明评分区分度)
|
||||
|
||||
对 `leejt/GraphGallery`(图神经网络多框架开发工具,Python,有 license)跑同一 rubric:
|
||||
|
||||
| 轴 | songhui18/ICCV2021 | leejt/GraphGallery |
|
||||
|---|---|---|
|
||||
| F | 67 | 67 |
|
||||
| A | **50**(无 license) | **75**(✓ 根级 LICENSE) |
|
||||
| I | 17(无根级依赖锁) | 67(✓ 根级 requirements.txt + setup.py) |
|
||||
| R | 50 | 67 |
|
||||
| Repro | 40 | 70(✓ setup.py 可安装) |
|
||||
| **总评** | **C 🔴 45** | **B 🟡 69** |
|
||||
|
||||
**区分度 +24 分**,主要由 **A2 license + I1/Rep1 打包可安装** 拉开。rubric 正确奖励"有 license + 可安装"的仓库。(GraphGallery 仍缺 CITATION/release,故是 B 非 A——评分诚实,不虚高。)
|
||||
|
||||
---
|
||||
|
||||
## 关键结论
|
||||
|
||||
1. **主题共振**:用"可复现性"工具体检"论文复现"合集——一个 19⭐ 的真实科研仓库竟只得 C,**正好印证可复现性危机**(2024 顶会仅 19.5% 提供官方代码)。
|
||||
2. **闭环可用**:fork → 建分支 → `file +create` 建修复文件 → 开 PR,**全程纯 API、无需克隆大仓**,处方 PR 真实落地。
|
||||
3. **诚实降级**:OpenAlex 匿名限流(503)→溯源跳过;无 `swh.model`→commit-SHA 锚定——都是设计内的优雅降级,**不掩盖、不编造**。
|
||||
4. **评分有区分度**:C 45(无 license 合集)vs B 69(有 license 工具),rubric 行为正确。
|
||||
5. **差异于 howfairis**:GitLink 原生 + 报告卡 + **自动修复闭环** + SWH 证书 + OpenAlex 溯源,howfairis 只跑 GitHub 且只打分不修复。
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gitlink-research-fair v2: research software X-ray. Extract a research profile from a GitLink repo. Stdlib only."""
|
||||
import argparse, json, os, sys, subprocess, re
|
||||
|
||||
_ARXIV_PATS = [
|
||||
r'https?://arxiv\.org/(?:abs|pdf)/(\d{4}\.\d{4,5})',
|
||||
r'arXiv:(\d{4}\.\d{4,5})',
|
||||
r'\b(\d{4}\.\d{4,5})\b',
|
||||
]
|
||||
_DOI_PAT = r'10\.\d{4,9}/\S+'
|
||||
_VENUES = ["ICML", "NeurIPS", "NIPS", "ICLR", "CVPR", "ICCV", "ECCV", "ACL", "EMNLP",
|
||||
"NAACL", "KDD", "WWW", "AAAI", "IJCAI", "SIGGRAPH", "Nature", "Science"]
|
||||
def _valid_arxiv_id(aid):
|
||||
"""Validate arxiv id YYMM.NNNNN: year 07-26 (2007-2026 arxiv new format), month 01-12.
|
||||
Rejects false positives like 5184.0000 (mm=84)."""
|
||||
m = re.match(r"(\d{2})(\d{2})\.\d{4,5}$", aid)
|
||||
if not m:
|
||||
return False
|
||||
yy, mm = int(m.group(1)), int(m.group(2))
|
||||
return (7 <= yy <= 26) and (1 <= mm <= 12)
|
||||
|
||||
_KNOWN_DATASETS = ["Visual Decathlon", "PACS", "ImageNet", "CIFAR-10", "CIFAR-100", "CIFAR",
|
||||
"Cora", "Citeseer", "Pubmed", "MNIST", "COCO", "QM9", "ZINC", "OGB", "ogbn",
|
||||
"Wikipedia", "PPI", "Reddit", "Amazon", "Yelp", "MUTAG"]
|
||||
|
||||
def extract_paper(readme):
|
||||
"""Extract paper provenance (arxiv/doi/title/venue) from README text."""
|
||||
if not readme:
|
||||
return {"in_readme": False, "arxiv_id": None, "arxiv_url": None, "doi": None,
|
||||
"title": None, "authors": [], "venue": None}
|
||||
arxiv_id = arxiv_url = None
|
||||
for pat in _ARXIV_PATS:
|
||||
for m in re.finditer(pat, readme):
|
||||
cand = m.group(1)
|
||||
if _valid_arxiv_id(cand):
|
||||
arxiv_id = cand
|
||||
arxiv_url = f"https://arxiv.org/abs/{cand}"
|
||||
break
|
||||
if arxiv_id:
|
||||
break
|
||||
doi = None
|
||||
m = re.search(_DOI_PAT, readme)
|
||||
if m:
|
||||
doi = m.group(0).rstrip(").,;]")
|
||||
venue = None
|
||||
for v in _VENUES:
|
||||
if re.search(rf"\b{re.escape(v)}\b", readme):
|
||||
venue = v; break
|
||||
code_for = (re.search(r"[Cc]ode (?:for|of)\s+'([^']+)'", readme)
|
||||
or re.search(r'[Cc]ode (?:for|of)\s+"([^"]+)"', readme))
|
||||
h1 = re.search(r"^\s*#\s+(.+)$", readme, re.M)
|
||||
title = (code_for.group(1).strip() if code_for
|
||||
else (h1.group(1).strip() if h1 else None))
|
||||
return {"in_readme": bool(arxiv_id or doi or code_for), "arxiv_id": arxiv_id,
|
||||
"arxiv_url": arxiv_url, "doi": doi, "title": title, "authors": [], "venue": venue}
|
||||
|
||||
def extract_datasets(readme, files):
|
||||
"""Identify referenced datasets (known-name match + data scripts)."""
|
||||
text = readme or ""
|
||||
found = []
|
||||
for ds in _KNOWN_DATASETS:
|
||||
if re.search(rf"\b{re.escape(ds)}\b", text, re.I):
|
||||
found.append(ds)
|
||||
scripts = [f for f in files if any(k in (f or "").lower()
|
||||
for k in ["data_gen", "get_data", "download", "prepare_data", "data_load"])]
|
||||
return [{"name": ds, "evidence": "mentioned in README",
|
||||
"download_script": scripts[:2] or None, "license": None} for ds in found]
|
||||
|
||||
def assess_repro(files, readme):
|
||||
"""Static reproducibility readiness: deps + entry + env + expected results.
|
||||
Supports Python (.py) AND C/C++ (.cpp/.c/.cc/.cu + Makefile/CMake) repos."""
|
||||
name_set = {(f or "") for f in files}
|
||||
deps_candidates = ["requirements.txt", "environment.yml", "go.mod", "package.json",
|
||||
"Dockerfile", "setup.py", "pyproject.toml", "CMakeLists.txt", "Makefile"]
|
||||
deps_files = [f for f in deps_candidates if f in name_set]
|
||||
entry_re = re.compile(r"(main|train|run|demo)_?\w*\.(py|cpp|c|cc|cu)$", re.I)
|
||||
entry_points = sorted([f for f in name_set if entry_re.match(f or "")])
|
||||
env_spec = any(f in ("Dockerfile", "environment.yml") for f in deps_files)
|
||||
expected = bool(re.search(r"(accuracy|f1\b|bleu|rouge|results?\s*(table|in section)|table\s*\d)",
|
||||
readme or "", re.I))
|
||||
return {"deps_files": deps_files, "deps_pinned": bool(deps_files),
|
||||
"entry_points": entry_points[:5], "expected_results": expected, "env_spec": env_spec}
|
||||
|
||||
def assess_citation(files, readme):
|
||||
"""Citation readiness: CITATION.cff / codemeta / zenodo + README bibtex."""
|
||||
name_set = {(f or "") for f in files}
|
||||
m = re.search(r"@(inproceedings|article|misc|book)\{[^}]+\}", readme or "", re.S | re.I)
|
||||
return {"cff": "CITATION.cff" in name_set,
|
||||
"codemeta": "codemeta.json" in name_set,
|
||||
"zenodo": ".zenodo.json" in name_set,
|
||||
"readme_bibtex": (m.group(0)[:200] if m else None)}
|
||||
|
||||
def extract_methods_frameworks(files, readme):
|
||||
"""Infer methods + frameworks from filenames + README."""
|
||||
text = " ".join(files) + " " + (readme or "")
|
||||
frameworks = []
|
||||
if re.search(r"\b(torch|pytorch|nn\.module)\b", text, re.I): frameworks.append("PyTorch")
|
||||
if re.search(r"\b(tensorflow|tf\.|keras)\b", text, re.I): frameworks.append("TensorFlow")
|
||||
if re.search(r"\b(jax|flax|haiku)\b", text, re.I): frameworks.append("JAX")
|
||||
if re.search(r"\b(sklearn|scikit-learn)\b", text, re.I): frameworks.append("scikit-learn")
|
||||
methods = []
|
||||
for kw in ["attention", "transformer", "contrastive", "meta-learning", "federated",
|
||||
"graph", "convolution", "resnet", "gan", "diffusion", "reinforcement",
|
||||
"domain generalisation", "domain generalization"]:
|
||||
if re.search(rf"\b{kw}", text, re.I):
|
||||
methods.append(kw)
|
||||
return {"methods": methods[:6],
|
||||
"frameworks": frameworks or ["unknown (infer from filenames; verify imports)"]}
|
||||
|
||||
def _gitlink(*args):
|
||||
"""Run gitlink-cli with json output; return parsed dict (UTF-8 safe)."""
|
||||
r = subprocess.run(["gitlink-cli"] + list(args) + ["--format", "json"],
|
||||
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60)
|
||||
raw = r.stdout
|
||||
i = raw.find("{")
|
||||
return json.loads(raw[i:]) if i >= 0 else {}
|
||||
|
||||
def fetch_readme(owner, repo):
|
||||
d = _gitlink("file", "+get", "--owner", owner, "--repo", repo, "--path", "README.md")
|
||||
ent = (d.get("data", {}) or {}).get("entries", {}) or {}
|
||||
return ent.get("content", "") if isinstance(ent, dict) else ""
|
||||
|
||||
def fetch_file_list(owner, repo):
|
||||
d = _gitlink("file", "+list", "--owner", owner, "--repo", repo)
|
||||
fd = d.get("data", "[]")
|
||||
if isinstance(fd, str):
|
||||
fd = json.loads(fd)
|
||||
return [f.get("name") for f in fd if isinstance(f, dict)] if isinstance(fd, list) else []
|
||||
|
||||
def fetch_repo_meta(owner, repo):
|
||||
info = _gitlink("repo", "+info", "--owner", owner, "--repo", repo)
|
||||
comm = _gitlink("commit", "+list", "--owner", owner, "--repo", repo, "--page", "1")
|
||||
cd = comm.get("data", {})
|
||||
cl = cd.get("commits") if isinstance(cd, dict) else None
|
||||
head = (cl[0].get("sha") if cl and isinstance(cl, list) and cl else None)
|
||||
d = info.get("data", {}) or {}
|
||||
return {"identifier": d.get("identifier"), "license_id": d.get("license_id"),
|
||||
"has_dataset": d.get("has_dataset"), "head_sha": head}
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="gitlink-research-fair v2: research software X-ray")
|
||||
ap.add_argument("--owner", required=True)
|
||||
ap.add_argument("--repo", required=True)
|
||||
args = ap.parse_args()
|
||||
|
||||
readme = fetch_readme(args.owner, args.repo)
|
||||
files = fetch_file_list(args.owner, args.repo)
|
||||
meta = fetch_repo_meta(args.owner, args.repo)
|
||||
mf = extract_methods_frameworks(files, readme)
|
||||
profile = {
|
||||
"repo": f"{args.owner}/{args.repo}",
|
||||
"head_sha": meta.get("head_sha"),
|
||||
"paper": extract_paper(readme),
|
||||
"datasets": extract_datasets(readme, files),
|
||||
"repro": assess_repro(files, readme),
|
||||
"citation": assess_citation(files, readme),
|
||||
"methods": mf["methods"],
|
||||
"frameworks": mf["frameworks"],
|
||||
"license": {"file": any("LICENSE" in (f or "") for f in files),
|
||||
"license_id": meta.get("license_id")},
|
||||
"files_count": len(files),
|
||||
}
|
||||
json.dump(profile, sys.stdout, ensure_ascii=False, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Assert-based unit tests for fair.py pure extractors. Run: python test_fair.py"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from fair import extract_paper, extract_datasets, assess_repro, assess_citation, extract_methods_frameworks
|
||||
|
||||
SAMPLE_README = """# Feature_Critic
|
||||
Demo code for 'Feature-Critic Networks for Heterogeneous Domain Generalisation'.
|
||||
This paper is located at https://arxiv.org/abs/1901.11448 and will appear in ICML 2019.
|
||||
Evaluated on PACS and Visual Decathlon.
|
||||
|
||||
@inproceedings{li2019feature,
|
||||
title={Feature-Critic Networks},
|
||||
booktitle={ICML}}
|
||||
"""
|
||||
|
||||
SAMPLE_FILES = ["README.md", "main_Feature_Critic.py", "main_baseline.py", "model_PACS.py",
|
||||
"alexnet.py", "resnet.py", "vggnet.py", "data_gen_PACS.py", "get_model_dataset.sh", "utils.py"]
|
||||
|
||||
def test_extract_paper():
|
||||
p = extract_paper(SAMPLE_README)
|
||||
assert p["arxiv_id"] == "1901.11448", p["arxiv_id"]
|
||||
assert p["arxiv_url"] == "https://arxiv.org/abs/1901.11448"
|
||||
assert p["venue"] == "ICML"
|
||||
assert p["in_readme"] is True
|
||||
print("test_extract_paper OK")
|
||||
|
||||
def test_extract_paper_none():
|
||||
p = extract_paper("# Hello\nA normal project with no paper.")
|
||||
assert p["in_readme"] in (False, True) # title-only may set in_readme; arxiv must be None
|
||||
assert p["arxiv_id"] is None
|
||||
print("test_extract_paper_none OK")
|
||||
|
||||
def test_extract_datasets():
|
||||
ds = extract_datasets(SAMPLE_README, SAMPLE_FILES)
|
||||
names = [d["name"] for d in ds]
|
||||
assert "PACS" in names and "Visual Decathlon" in names
|
||||
pacs = [d for d in ds if d["name"] == "PACS"][0]
|
||||
assert pacs["download_script"] and "data_gen_PACS.py" in pacs["download_script"]
|
||||
print("test_extract_datasets OK")
|
||||
|
||||
def test_assess_repro():
|
||||
files = ["README.md", "main_Feature_Critic.py", "requirements.txt", "model_PACS.py"]
|
||||
r = assess_repro(files, SAMPLE_README)
|
||||
assert "requirements.txt" in r["deps_files"]
|
||||
assert r["deps_pinned"] is True
|
||||
assert "main_Feature_Critic.py" in r["entry_points"]
|
||||
assert r["expected_results"] is False # SAMPLE_README has no accuracy/results table
|
||||
print("test_assess_repro OK")
|
||||
|
||||
def test_assess_citation():
|
||||
c = assess_citation(SAMPLE_FILES, SAMPLE_README)
|
||||
assert c["cff"] is False and c["codemeta"] is False
|
||||
assert c["readme_bibtex"] and "@inproceedings" in c["readme_bibtex"]
|
||||
print("test_assess_citation OK")
|
||||
|
||||
def test_extract_methods_frameworks():
|
||||
mf = extract_methods_frameworks(SAMPLE_FILES, SAMPLE_README)
|
||||
assert "domain generalisation" in mf["methods"], mf["methods"] # SAMPLE_README 提到 Domain Generalisation
|
||||
assert isinstance(mf["frameworks"], list)
|
||||
print("test_extract_methods_frameworks OK")
|
||||
|
||||
def test_arxiv_validation():
|
||||
p = extract_paper("see version 5184.0000 released")
|
||||
assert p["arxiv_id"] is None, ("5184.0000 应被年份校验拒绝", p["arxiv_id"])
|
||||
p2 = extract_paper("paper at https://arxiv.org/abs/1901.11448 ICML")
|
||||
assert p2["arxiv_id"] == "1901.11448"
|
||||
p3 = extract_paper("# MyRepo\njust a project")
|
||||
assert p3["in_readme"] is False, ("H1 单独不应算论文证据", p3["in_readme"])
|
||||
print("test_arxiv_validation OK")
|
||||
|
||||
def test_cpp_entry_detection():
|
||||
r = assess_repro(["main.cpp", "Makefile", "utils.cpp"], "")
|
||||
assert "main.cpp" in r["entry_points"], r["entry_points"]
|
||||
assert "Makefile" in r["deps_files"], r["deps_files"]
|
||||
print("test_cpp_entry_detection OK")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_extract_paper(); test_extract_paper_none(); test_extract_datasets()
|
||||
test_assess_repro(); test_assess_citation(); test_extract_methods_frameworks()
|
||||
test_arxiv_validation(); test_cpp_entry_detection()
|
||||
print("ALL TESTS PASSED")
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# gitlink-spark 参考文档
|
||||
|
||||
> SKILL.md 的深度参考:缺口分类细则、GitHub 阈值、LLM prompt 模板、数据源实测、诚实边界。
|
||||
|
||||
## 一、缺口分类法细则
|
||||
|
||||
### 类型 A 有理论无实现
|
||||
- 输入:arXiv 论文方法 M(title + method_keywords)
|
||||
- GitLink 侧:`search +repos -k <M>` 命中数(0 或极少,如 ≤2)
|
||||
- GitHub 侧:`total_count`(按 §二阈值分级)
|
||||
- 判定为"缺口"条件:GitLink ≤2 **且** GitHub < 50(全球稀缺或新兴)
|
||||
|
||||
### 类型 B 有需求无解答
|
||||
- 输入:领域仓库的 open issue(`issue +list`,排除关闭)
|
||||
- LLM 筛"研究性痛点":含性能/可扩展性/新场景/新数据集,排除安装报错/使用咨询
|
||||
- 判定:GitLink 无现成实现解此痛点 **且** GitHub 无成熟开源方案
|
||||
|
||||
## 二、GitHub 全球对照阈值
|
||||
|
||||
| total_count | 分级 | 报告 |
|
||||
|---|---|---|
|
||||
| < 10 | 全球稀缺 | 高价值缺口 |
|
||||
| 10–50 | 新兴 | 中等缺口 |
|
||||
| ≥ 50 | 已成熟 | **不报为空白**,列入"已诚实排除" |
|
||||
|
||||
spark.py 缓存 GitHub 结果(按 query key),避免重复调用。
|
||||
|
||||
## 三、LLM 缺口匹配 prompt 模板
|
||||
|
||||
```
|
||||
你是科研机会发现助手。下面是 spark.py 抓取的真实数据(JSON)。
|
||||
请跨"arXiv 论文 × GitLink 仓库/issues × GitHub 全球计数"找出语义缺口,输出机会报告。
|
||||
|
||||
规则:
|
||||
1. 只输出可溯源到下列数据的缺口;每张缺口卡带"实证三件套"。
|
||||
2. 类型A(理论无实现):论文 M 的 GitLink 命中≤2 且 GitHub total_count<50 才报;
|
||||
GitHub ≥50 的论文列入"已诚实排除",不报为空白。
|
||||
3. 类型B(需求无解答):只挑研究性痛点 issue,排除使用/安装类。
|
||||
4. 每张卡给一句"机会建议"(主观),但证据必须客观可查。
|
||||
5. 宁可少报,不误报。
|
||||
|
||||
数据:
|
||||
{spark.py 的 JSON}
|
||||
```
|
||||
|
||||
## 四、数据源实测结论(2026-07-01)
|
||||
|
||||
| 源 | 状态 | 备注 |
|
||||
|---|---|---|
|
||||
| arXiv API | ✅ 必须 HTTPS | HTTP 被沙箱阻断返回 0 字节 |
|
||||
| gitlink-cli search +repos | ✅ | 用 identifier/关键词 |
|
||||
| gitlink-cli issue +list | ✅ | 逐仓库,绕开 search+issues |
|
||||
| gitlink-cli search +issues | ❌ 返回 HTML | 不可用,勿用 |
|
||||
| GitHub Search API | ✅ | 未认证 10/min;GITHUB_TOKEN 提额 |
|
||||
| OpenAlex | ⚠ 间歇 503 | best-effort 富集,降级跳过 |
|
||||
|
||||
## 五、诚实边界
|
||||
|
||||
1. **GitLink 覆盖薄**:缺口卡明确标 "GitLink 0 / GitHub N";GitHub ≥50 不报为空白。
|
||||
2. LLM 缺口必须可溯源实证三件套,否则丢弃。
|
||||
3. arXiv 仅覆盖 CS/物理等,报告标注学科范围。
|
||||
4. GitHub 未认证 10/min:spark.py sleep 7s + 缓存;建议 demo 设 GITHUB_TOKEN。
|
||||
5. "机会建议"为主观启发,标注"需研究者自行判断"。
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
name: gitlink-spark
|
||||
version: 1.0.0
|
||||
description: "文献-代码语义缺口挖掘机:给一个研究领域,跨 arXiv × GitLink × GitHub 三源挖'有理论无实现/有需求无解答'语义缺口,输出空白学术机会报告,可一键 fork+issue 起跑。当用户需要找研究点、发现论文-代码空白、科研选题启发时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "python skills/gitlink-spark/scripts/spark.py --help"
|
||||
---
|
||||
|
||||
# gitlink-spark(文献-代码语义缺口挖掘机)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 缺口由 LLM 推断,但每条必须带实证三件套(论文 id / GitLink 查询+命中数 / GitHub total_count);无实证的缺口必须丢弃。**
|
||||
**CRITICAL — 起跑(fork+issue)默认预览确认;绝不自动 merge、绝不 force-push、绝不碰原仓库。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md);缺口分类法、GitHub 阈值、LLM prompt 模板见 [`REFERENCE.md`](REFERENCE.md)。
|
||||
|
||||
## 概述
|
||||
|
||||
给一个研究领域,跨 **arXiv(学术)× GitLink(中文生态)× GitHub(全球)** 三源挖两类语义缺口,输出**空白学术机会报告**。`scripts/spark.py` 抓真实数据(JSON),LLM 做语义匹配并附实证三件套。与 `gitlink-research-fair`(评估已有)组成"科研辅助双联装"——本 skill 负责**发现空白**。
|
||||
|
||||
## 命令接口
|
||||
|
||||
数据融合脚本(可独立运行):
|
||||
|
||||
```bash
|
||||
python skills/gitlink-spark/scripts/spark.py --field "图神经网络" [--max-papers 10] [--gap-type both|theory|demand] [--github-token $GITHUB_TOKEN]
|
||||
# → stdout: 融合 JSON {papers, gitlink_repos, gitlink_issues, github_counts}
|
||||
```
|
||||
|
||||
skill 约定参数(非 CLI flag):
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|------|------|------|
|
||||
| `--field` | 必填 | 研究领域 |
|
||||
| `--gap-type` | `both` | `theory` / `demand` / `both` |
|
||||
| `--max-papers` | 10 | arXiv 抓取上限(控 GitHub 调用) |
|
||||
| `--auto` | 关 | 跳过预览直接起跑(仍受护栏) |
|
||||
| `--no-fork` | 关 | 只出报告,不起跑 |
|
||||
|
||||
## 管道(4 阶段)
|
||||
|
||||
### ① 学术采
|
||||
`spark.py` 调 arXiv HTTPS API 抓领域近 90 天论文(标题/摘要/arxiv id/方法关键词)
|
||||
|
||||
### ② GitLink 采
|
||||
`spark.py` 调 `gitlink-cli search +repos` 抓领域仓库;对每个仓库 `issue +list --state open` 抓 open issue(**不用 search +issues**,它返回 HTML)
|
||||
|
||||
### ③ 全球对照
|
||||
`spark.py` 调 GitHub Search API 对每个论文方法查 `total_count` + Top3 仓库(限流+缓存)
|
||||
|
||||
### ④ 缺口匹配(LLM)+ 报告落盘 + 起跑
|
||||
读 spark.py 的 JSON → 语义匹配两类缺口(每张带实证三件套)→ 渲染机会报告。
|
||||
**始终保存为本地文件** `report-cards/spark-<field>-report.md`(cwd 下,含哨兵)——机会报告无论是否起跑都**必须落盘,绝不只在终端输出**。
|
||||
(可选)fork+issue 起跑:见下方"起跑动作"。
|
||||
|
||||
## 两类缺口 + 实证三件套(信服核心)
|
||||
|
||||
每张缺口卡**必须**带齐三件套,否则丢弃(防 LLM 编造):
|
||||
|
||||
### 类型 A:有理论无实现(paper → code gap)
|
||||
- **三件套**:① 论文 arxiv id + 标题 + 发表日期 ② GitLink 搜索查询串 + 命中数(0/极少) ③ GitHub total_count + Top 仓库
|
||||
- LLM 判定:论文提出方法 M;GitLink 实现 0/极少;GitHub 按下方阈值分级
|
||||
|
||||
### 类型 B:有需求无解答(open issue → applied gap)
|
||||
- **三件套**:① issue URL + 主题 + 讨论人数/状态 ② GitLink 无现成实现解此痛点 ③ GitHub 是否有成熟开源解
|
||||
- 降噪:LLM 只挑"研究性痛点"(性能/可扩展/新场景),排除"安装报错"等使用问题
|
||||
|
||||
## GitHub 全球对照阈值(诚实核心,硬需求)
|
||||
|
||||
防止"GitLink 0 ≠ 全球空白"误导。对每个"理论无实现"候选按 GitHub total_count 分级:
|
||||
|
||||
| GitHub total_count | 分级 | 报告行为 |
|
||||
|--------------------|------|----------|
|
||||
| `< 10` | 全球稀缺(真空白) | 报为高价值缺口 |
|
||||
| `10–50` | 新兴(部分空白) | 报为中等缺口("GitLink 空白,全球新兴") |
|
||||
| `≥ 50` | 全球已成熟 | **不报为空白**,列入"✅ 已诚实排除" |
|
||||
|
||||
宁可少报,不误报机会。
|
||||
|
||||
## 机会报告格式(hero)
|
||||
|
||||
````markdown
|
||||
⚡ **gitlink-spark 机会报告:<field>**
|
||||
|
||||
学术采:arXiv 近 90 天 N 篇 | GitLink 仓库 M 个 | GitHub 全球基线已对照
|
||||
生成时间:YYYY-MM-DD
|
||||
|
||||
### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值]
|
||||
**论文**:[arxiv:<id>] "<title>" (<date>)
|
||||
**方法关键词**:<...>
|
||||
**GitLink**:search "<query>" → **0 命中**(查询串留底)
|
||||
**GitHub 全球**:total_count = **N**(Top: <repo> <stars>⭐)→ 稀缺
|
||||
**机会建议**:<LLM 一句话>
|
||||
**起跑**:[按钮] fork 基准 <repo> → 创建 issue 粘论文伪代码
|
||||
|
||||
### 🧩 缺口 2 · 有需求无解答 [应用机会]
|
||||
**Issue**:<repo>#<n> "<subject>"(N 人讨论, open)
|
||||
**痛点**:<LLM 归纳>
|
||||
**GitLink / GitHub**:均无成熟解
|
||||
**机会建议**:<LLM 一句话>
|
||||
|
||||
### ✅ 已诚实排除(非空白)
|
||||
- 论文 Y:GitLink 虽 0,但 GitHub 已 N 个 → 全球已成熟,不报
|
||||
|
||||
---
|
||||
<!-- gitlink-spark v1 | field:<field> | gaps:<N> | date:<YYYY-MM-DD> -->
|
||||
*由 gitlink-spark skill 生成。*
|
||||
````
|
||||
|
||||
## 起跑动作 + 护栏
|
||||
|
||||
选定一张"理论无实现"缺口卡 → 确认 →
|
||||
1. `gitlink-cli repo +fork` 最近基准(GitHub Top 仓库或 GitLink 最近实现)
|
||||
2. LLM 从 arXiv 论文抓 Algorithm/Pseudocode 节
|
||||
3. `gitlink-cli issue +create` 在 fork 建复现 todo issue(body 粘伪代码 + 报告卡摘要)
|
||||
|
||||
**护栏**:默认预览;`--auto` 跳过但**永不 force-push、永不碰原仓库、永不自动 merge**;`--no-fork` 报告已落盘(④),不起跑。
|
||||
|
||||
## 错误处理与降级
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| arXiv 空/超时 | HTTPS 重试;仍空降级用既有论文 |
|
||||
| `search +issues` 返回 HTML | 不用,改逐仓库 `issue +list` |
|
||||
| GitHub 未认证限流(10/min) | spark.py sleep ~7s;建议设 `GITHUB_TOKEN` |
|
||||
| GitHub 查询失败 | 该论文标"对照失败",不进缺口判定 |
|
||||
| OpenAlex 503 | 跳过引用富集 |
|
||||
| LLM 缺口无三件套 | 置信度门控丢弃 |
|
||||
| fork/issue 起跑失败 | 报告已在 ④ 落盘,告知路径;另输出 fork 目标 + 伪代码文本供手动起跑 |
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# 示例:gitlink-spark GNN 缺口挖掘(真实数据)
|
||||
|
||||
> 基于 `python spark.py --field "graph neural network" --max-papers 8 --gap-type both` 于 2026-07-06 实跑。
|
||||
> 一个"文献-代码语义缺口挖掘机"在 GNN 领域跑出真实研究机会。
|
||||
|
||||
---
|
||||
|
||||
## Step 1:数据采集(真实)
|
||||
|
||||
| 源 | 结果 |
|
||||
|---|---|
|
||||
| arXiv(abs:"graph neural network",近期) | 8 篇 |
|
||||
| GitLink 仓库(search +repos) | 20 个(多为通用 graph/network;GNN 专项如 `leejt/GraphGallery` 需定向) |
|
||||
| GitLink open issues(定向 GraphGallery) | 5 条"图神经网络模型论文复现"请求 |
|
||||
| GitHub 全球对照 | 每篇论文方法 total_count |
|
||||
|
||||
采集命令:
|
||||
```bash
|
||||
python skills/gitlink-spark/scripts/spark.py --field "graph neural network" --max-papers 8 --gap-type both
|
||||
```
|
||||
|
||||
## Step 2:机会报告(真实全文)
|
||||
|
||||
````markdown
|
||||
⚡ **gitlink-spark 机会报告:图神经网络**
|
||||
|
||||
学术采:arXiv 近期 8 篇 | GitLink 仓库 20 个(+ 定向 GraphGallery) | GitHub 全球基线已对照
|
||||
生成时间:2026-07-06
|
||||
|
||||
### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值]
|
||||
**论文**:[arxiv:2607.02063] "SA-HGNN: Sample-Adaptive Hyperbolic Graph Neural Networks"
|
||||
**方法关键词**:sample-adaptive, hyperbolic
|
||||
**GitLink**:search "hyperbolic graph neural network" → **0 命中**(20 仓库无一实现双曲 GNN)
|
||||
**GitHub 全球**:total_count = **0**("sample-adaptive hyperbolic")→ 全球稀缺
|
||||
**机会建议**:GitLink 生态空白 × 全球稀缺 → 复现并开源到 GitLink,易成本平台首个双曲 GNN 实现
|
||||
**起跑**:✅ `caoweiqiong/GraphGallery#1`(已 fork GraphGallery 基准 + 建复现 todo)
|
||||
|
||||
### 🧩 缺口 2 · 有理论无实现 [新兴·中等价值]
|
||||
**论文**:[arxiv:2607.00671] "Multi-Label Node Classification with Label Influence"
|
||||
**方法关键词**:multi-label, node, classification
|
||||
**GitLink**:0 专项实现
|
||||
**GitHub 全球**:total_count = **16** → 新兴(10–50 tier)
|
||||
**机会建议**:全球新兴方向,GitLink 空白 → 可做中文生态较早的完整实现
|
||||
|
||||
### 🧩 缺口 3 · 有需求无解答 [应用机会]
|
||||
**Issue**:`leejt/GraphGallery#1` "图神经网络模型论文复现:节点分类任务" · `#2` 链路预测 · `#3` 节点嵌入(共 5 条 open,均为复现请求;状态"新增",讨论 0 人)
|
||||
**痛点**:GraphGallery 用户在 GitLink 上明确请求 GNN 多任务论文复现(节点分类 / 链路预测 / 嵌入),现有框架未覆盖这些专项
|
||||
**GitLink / GitHub**:GraphGallery 提供框架但无这些专项复现;GitHub 零散有
|
||||
**机会建议**:针对 GitLink 用户实际复现需求,补齐节点分类 / 链路预测论文复现专题
|
||||
|
||||
### ✅ 已诚实排除(非空白)
|
||||
- **Graph Attention Network (GAT)**:GitHub total_count = **1543**(含 PetarV-/GAT 3534⭐)→ 全球已成熟,**不报为空白**
|
||||
- 本轮 8 篇 arXiv 论文中 **3 篇离题**(Cayley 图数学 / WavePID 中微子物理 / EO-Agents LLM)—— arXiv 宽泛匹配所致,已过滤不计入
|
||||
|
||||
---
|
||||
<!-- gitlink-spark v1 | field:图神经网络 | gaps:3 | date:2026-07-06 -->
|
||||
*由 gitlink-spark skill 生成。*
|
||||
````
|
||||
|
||||
## Step 3:起跑(真实闭环 ✅)
|
||||
|
||||
选定缺口 1(SA-HGNN,全球稀缺)起跑:
|
||||
```bash
|
||||
gitlink-cli repo +fork --owner leejt --repo GraphGallery # → caoweiqiong/GraphGallery
|
||||
gitlink-cli issue +create --owner caoweiqiong --repo GraphGallery \
|
||||
--title "Reproduction todo: SA-HGNN (Sample-Adaptive Hyperbolic GNN) [gitlink-spark 起跑]" \
|
||||
--body "<缺口三件套 + 复现计划 + 论文 arxiv 链接>"
|
||||
```
|
||||
**issue**:`caoweiqiong/GraphGallery#1`(fork 基准 + 复现 todo,含 SA-HGNN 论文方法 + 基于 GraphGallery 的复现步骤)
|
||||
|
||||
## Step 4:关键结论
|
||||
|
||||
1. **三源融合真实可跑**:arXiv(8 篇)× GitLink(20 仓库 + 定向 GraphGallery)× GitHub(每方法 total_count)。
|
||||
2. **GitHub 阈值生效(诚实核心)**:GAT(1543) → 已诚实排除;SA-HGNN(0) → 高价值缺口;Multi-Label Node Cls(16) → 新兴。三级分明。
|
||||
3. **离题论文诚实过滤**:arXiv 宽泛匹配混入 3 篇非 GNN(数学/物理/LLM),报告明示排除,不滥竽充数。
|
||||
4. **demand 侧诚实降级**:spark.py 自动扫描的 20 个 GitLink 仓库多为通用 graph/network、0 研究 issue;定向 GraphGallery 发现真实复现需求(5 条)。报告如实标注"自动 0 / 定向发现"。
|
||||
5. **起跑闭环对称 fair**:fair 给已有仓库开修复 PR;spark 给缺口方向 fork 基准 + 复现 todo issue——都是"诊断→行动"闭环。
|
||||
6. **每条缺口可溯源**到 spark.py 的 JSON(arxiv id / 查询串 / total_count 全可查)。
|
||||
```
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""gitlink-spark data fusion: arXiv x GitLink x GitHub -> JSON on stdout. Stdlib only."""
|
||||
import argparse, json, os, sys, time, subprocess, urllib.request, urllib.parse, re
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
ARXIV_ENDPOINT = "https://export.arxiv.org/api/query"
|
||||
GITHUB_ENDPOINT = "https://api.github.com/search/repositories"
|
||||
|
||||
_NS = {"a": "http://www.w3.org/2005/Atom"}
|
||||
|
||||
def parse_arxiv_atom(xml_text):
|
||||
"""Parse arXiv Atom feed -> list of {arxiv_id, title, abstract, published}."""
|
||||
root = ET.fromstring(xml_text)
|
||||
papers = []
|
||||
for e in root.findall("a:entry", _NS):
|
||||
aid = (e.find("a:id", _NS).text or "").strip().split("/")[-1]
|
||||
title = re.sub(r"\s+", " ", (e.find("a:title", _NS).text or "").strip())
|
||||
summary = re.sub(r"\s+", " ", (e.find("a:summary", _NS).text or "").strip())
|
||||
pub = (e.find("a:published", _NS).text or "")[:10]
|
||||
papers.append({"arxiv_id": aid, "title": title, "abstract": summary, "published": pub})
|
||||
return papers
|
||||
|
||||
def parse_github_search(json_text):
|
||||
"""Parse GitHub search JSON -> {total_count, top:[{full_name, stars}]}."""
|
||||
d = json.loads(json_text)
|
||||
return {
|
||||
"total_count": d.get("total_count", 0),
|
||||
"top": [{"full_name": r.get("full_name"), "stars": r.get("stargazers_count")}
|
||||
for r in (d.get("items") or [])[:3]],
|
||||
}
|
||||
|
||||
def extract_method_keywords(title, abstract, max_k=5):
|
||||
"""Crude keyword extraction for GitHub/arXiv query."""
|
||||
text = (title + " " + abstract).lower()
|
||||
stop = {"the", "a", "an", "of", "for", "and", "to", "in", "on", "with", "via",
|
||||
"based", "using", "by", "from", "as", "is", "are", "we", "our", "this",
|
||||
"that", "propose", "proposed", "paper", "method", "approach", "novel", "new"}
|
||||
tokens = re.findall(r"[a-z][a-z0-9-]+", text)
|
||||
seen = set(); out = []
|
||||
for t in tokens:
|
||||
if t in stop or len(t) < 3 or t in seen:
|
||||
continue
|
||||
seen.add(t); out.append(t)
|
||||
if len(out) >= max_k:
|
||||
break
|
||||
return out
|
||||
|
||||
def fetch_arxiv(field, max_papers=10):
|
||||
"""Search arXiv (HTTPS) for recent papers in field. Returns list of paper dicts."""
|
||||
q = urllib.parse.quote(f'abs:"{field}"')
|
||||
url = (f"{ARXIV_ENDPOINT}?search_query={q}&max_results={max_papers}"
|
||||
f"&sortBy=submittedDate&sortOrder=descending")
|
||||
with urllib.request.urlopen(url, timeout=30) as r:
|
||||
papers = parse_arxiv_atom(r.read().decode("utf-8", "replace"))
|
||||
for p in papers:
|
||||
p["method_keywords"] = extract_method_keywords(p["title"], p["abstract"])
|
||||
return papers
|
||||
|
||||
def _gitlink(*args):
|
||||
"""Run gitlink-cli with json output; return parsed dict (UTF-8 safe)."""
|
||||
r = subprocess.run(["gitlink-cli"] + list(args) + ["--format", "json"],
|
||||
capture_output=True, text=True, encoding="utf-8",
|
||||
errors="replace", timeout=60)
|
||||
raw = r.stdout
|
||||
i = raw.find("{")
|
||||
return json.loads(raw[i:]) if i >= 0 else {}
|
||||
|
||||
def fetch_gitlink_repos(field):
|
||||
"""gitlink-cli search +repos -> list of {owner, repo(identifier), name, desc, topics}."""
|
||||
d = _gitlink("search", "+repos", "-k", field)
|
||||
projs = d.get("data", {}).get("projects", []) or []
|
||||
out = []
|
||||
for p in projs:
|
||||
out.append({
|
||||
"owner": (p.get("author") or {}).get("login"),
|
||||
"repo": p.get("identifier"),
|
||||
"name": p.get("name"),
|
||||
"desc": p.get("description"),
|
||||
"topics": [t.get("name") if isinstance(t, dict) else t for t in (p.get("topics") or [])],
|
||||
})
|
||||
return out
|
||||
|
||||
def fetch_gitlink_issues(repos, max_per_repo=10):
|
||||
"""Per-repo issue +list (open) -> list of {repo, number, subject, status, participants}.
|
||||
Works around search +issues returning HTML."""
|
||||
out = []
|
||||
for r in repos:
|
||||
if not (r.get("owner") and r.get("repo")):
|
||||
continue
|
||||
d = _gitlink("issue", "+list", "--owner", r["owner"], "--repo", r["repo"], "--state", "open")
|
||||
data = d.get("data", {}) or {}
|
||||
issues = data.get("issues") or []
|
||||
for it in issues[:max_per_repo]:
|
||||
st = (it.get("status") or {})
|
||||
if st.get("name") == "关闭":
|
||||
continue
|
||||
out.append({
|
||||
"repo": f'{r["owner"]}/{r["repo"]}',
|
||||
"number": it.get("project_issues_index") or it.get("number"),
|
||||
"subject": it.get("subject"),
|
||||
"status": st.get("name"),
|
||||
"participants": it.get("participants_count") or 0,
|
||||
})
|
||||
return out
|
||||
|
||||
_GH_CACHE = {}
|
||||
|
||||
def fetch_github_count(query, token=None, throttle=True):
|
||||
"""GitHub search total_count + top3 for a query. Caches + throttles (10/min unauth)."""
|
||||
if query in _GH_CACHE:
|
||||
return _GH_CACHE[query]
|
||||
url = f"{GITHUB_ENDPOINT}?q={urllib.parse.quote(query)}&per_page=3&sort=stars"
|
||||
req = urllib.request.Request(url, headers={"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "gitlink-spark/1.0"})
|
||||
if token:
|
||||
req.add_header("Authorization", f"Bearer {token}")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=25) as r:
|
||||
res = parse_github_search(r.read().decode("utf-8", "replace"))
|
||||
except Exception as e:
|
||||
res = {"total_count": None, "top": [], "error": str(e)[:80]}
|
||||
if throttle and not token:
|
||||
time.sleep(7) # unauthenticated = 10 req/min
|
||||
_GH_CACHE[query] = res
|
||||
return res
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="gitlink-spark data fusion")
|
||||
ap.add_argument("--field", required=True)
|
||||
ap.add_argument("--max-papers", type=int, default=10)
|
||||
ap.add_argument("--gap-type", default="both", choices=["both", "theory", "demand"])
|
||||
ap.add_argument("--github-token", default=os.environ.get("GITHUB_TOKEN"))
|
||||
args = ap.parse_args()
|
||||
|
||||
papers = fetch_arxiv(args.field, args.max_papers)
|
||||
grepos = fetch_gitlink_repos(args.field)
|
||||
gissues = fetch_gitlink_issues(grepos) if args.gap_type in ("both", "demand") else []
|
||||
gh_counts = {}
|
||||
if args.gap_type in ("both", "theory"):
|
||||
for p in papers:
|
||||
mk = p.get("method_keywords") or []
|
||||
q = " ".join(mk[:3]) if mk else p["title"][:40] # method keywords = implementation prevalence (NOT exact-title)
|
||||
gh_counts[q] = fetch_github_count(q, args.github_token)
|
||||
|
||||
out = {
|
||||
"field": args.field,
|
||||
"papers": papers,
|
||||
"gitlink_repos": grepos,
|
||||
"gitlink_issues": gissues,
|
||||
"github_counts": gh_counts,
|
||||
}
|
||||
json.dump(out, sys.stdout, ensure_ascii=False, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Assert-based unit tests for spark.py pure parsers. Run: python test_spark.py"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from spark import parse_arxiv_atom, parse_github_search, extract_method_keywords
|
||||
|
||||
SAMPLE_ARXIV = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
<id>http://arxiv.org/abs/2403.12345v1</id>
|
||||
<title>Graph Attention Networks with Sparse Transformers</title>
|
||||
<summary>We propose a new graph attention mechanism using sparse attention.</summary>
|
||||
<published>2024-03-15T00:00:00Z</published>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>http://arxiv.org/abs/2404.99999v2</id>
|
||||
<title>Federated Learning on Heterogeneous Graphs</title>
|
||||
<summary>A federated approach for heterogeneous graph neural networks.</summary>
|
||||
<published>2024-04-20T00:00:00Z</published>
|
||||
</entry>
|
||||
</feed>"""
|
||||
|
||||
def test_parse_arxiv_atom():
|
||||
papers = parse_arxiv_atom(SAMPLE_ARXIV)
|
||||
assert len(papers) == 2, f"expected 2 papers, got {len(papers)}"
|
||||
assert papers[0]["arxiv_id"] == "2403.12345v1", papers[0]["arxiv_id"]
|
||||
assert "Graph Attention" in papers[0]["title"]
|
||||
assert papers[0]["published"] == "2024-03-15"
|
||||
assert "sparse" in papers[0]["abstract"].lower()
|
||||
print("test_parse_arxiv_atom OK")
|
||||
|
||||
def test_parse_github_search():
|
||||
import json as _j
|
||||
sample = _j.dumps({"total_count": 1543, "items": [{"full_name": "a/b", "stargazers_count": 3534}]})
|
||||
res = parse_github_search(sample)
|
||||
assert res["total_count"] == 1543
|
||||
assert res["top"][0]["full_name"] == "a/b"
|
||||
assert res["top"][0]["stars"] == 3534
|
||||
print("test_parse_github_search OK")
|
||||
|
||||
def test_extract_method_keywords():
|
||||
kws = extract_method_keywords("Graph Attention Networks", "We propose a sparse attention mechanism for graphs.", max_k=5)
|
||||
assert "graph" in kws and "attention" in kws
|
||||
assert "propose" not in kws # 'propose' is in the stop set, filtered out
|
||||
print("test_extract_method_keywords OK")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parse_arxiv_atom()
|
||||
test_parse_github_search()
|
||||
test_extract_method_keywords()
|
||||
print("ALL TESTS PASSED")
|
||||
|
|
@ -27,7 +27,8 @@ metadata:
|
|||
> - Issue 智能分拣 → [`../gitlink-triage/SKILL.md`](../gitlink-triage/SKILL.md)
|
||||
> - Release Notes 生成 → [`../gitlink-changelog/SKILL.md`](../gitlink-changelog/SKILL.md)
|
||||
> - 项目健康报告 → [`../gitlink-health/SKILL.md`](../gitlink-health/SKILL.md)
|
||||
> - 科研软件 FAIR 体检 → [`../gitlink-research-fair/SKILL.md`](../gitlink-research-fair/SKILL.md)
|
||||
> - 科研软件 X 光(fair v2) → [`../gitlink-research-fair/SKILL.md`](../gitlink-research-fair/SKILL.md)
|
||||
> - 文献-代码缺口挖掘(科研选题) → [`../gitlink-spark/SKILL.md`](../gitlink-spark/SKILL.md)
|
||||
|
||||
## 工作流 1:PR 全流程
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue