diff --git a/docs/superpowers/plans/2026-07-01-gitlink-spark.md b/docs/superpowers/plans/2026-07-01-gitlink-spark.md new file mode 100644 index 0000000..0f50157 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-gitlink-spark.md @@ -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 机会报告:** + +学术采:arXiv 近 90 天 N 篇 | GitLink 仓库 M 个 | GitHub 全球基线已对照 +生成时间:YYYY-MM-DD + +### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值] +**论文**:[arxiv:] "" (<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 + We propose a new graph attention mechanism using sparse attention. + 2024-03-15T00:00:00Z + + + http://arxiv.org/abs/2404.99999v2 + Federated Learning on Heterogeneous Graphs + A federated approach for heterogeneous graph neural networks. + 2024-04-20T00:00:00Z + +""" + +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 | ` (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 ` 命中数(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: (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 排除,每张带实证三件套> + +## 起跑(真实) + + +## 关键结论 +- 三源融合真实可跑 +- 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 +``` diff --git a/docs/superpowers/plans/2026-07-07-gitlink-research-fair-v2.md b/docs/superpowers/plans/2026-07-07-gitlink-research-fair-v2.md new file mode 100644 index 0000000..7ecf8c8 --- /dev/null +++ b/docs/superpowers/plans/2026-07-07-gitlink-research-fair-v2.md @@ -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 --repo +# → 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/--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 光 — /** + +═══════════════════════════════════════ +论文溯源 | 数据链 | 复现就绪 | 引用就绪 +═══════════════════════════════════════ + +### 🧬 真科研图谱 +```mermaid +graph LR + P[]:::ok -->|proposes| M[] + M -->|implements| C[]:::ok + C -->|uses| D[]:::warn + C -->|depends| F[]:::warn + P -->|cited-via| Ci[]:::bad + classDef ok fill:#cfe,stroke:#3a3; classDef warn fill:#ffe,stroke:#cc3; classDef bad fill:#fee,stroke:#c33; +``` + +### 🔍 关键发现(本 repo 特有) +- + +### 🔧 处方(可选) +- <对 ❌/⚠️ 项的修复建议> + +### 📜 双裁决证书 +复现就绪 | 引用就绪 | 锚定 commit `` + +--- + +*由 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 +``` diff --git a/docs/superpowers/specs/2026-07-01-gitlink-spark-design.md b/docs/superpowers/specs/2026-07-01-gitlink-spark-design.md new file mode 100644 index 0000000..51f56cd --- /dev/null +++ b/docs/superpowers/specs/2026-07-01-gitlink-spark-design.md @@ -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 机会报告:** + +学术采:arXiv 近 90 天 N 篇 | GitLink 仓库 M 个 | GitHub 全球基线已对照 +生成时间:YYYY-MM-DD + +### 🧩 缺口 1 · 有理论无实现 [全球稀缺·高价值] +**论文**:[arxiv:] "" (<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 diff --git a/docs/superpowers/specs/2026-07-07-gitlink-research-fair-v2-design.md b/docs/superpowers/specs/2026-07-07-gitlink-research-fair-v2-design.md new file mode 100644 index 0000000..da492e8 --- /dev/null +++ b/docs/superpowers/specs/2026-07-07-gitlink-research-fair-v2-design.md @@ -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 光" diff --git a/skills/README.md b/skills/README.md index b9a3595..803eb93 100644 --- a/skills/README.md +++ b/skills/README.md @@ -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 起跑 | --- diff --git a/skills/gitlink-research-fair/REFERENCE.md b/skills/gitlink-research-fair/REFERENCE.md index 11af407..3216b63 100644 --- a/skills/gitlink-research-fair/REFERENCE.md +++ b/skills/gitlink-research-fair/REFERENCE.md @@ -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 弱环节)。 diff --git a/skills/gitlink-research-fair/SKILL.md b/skills/gitlink-research-fair/SKILL.md index bdb1eed..28efa8d 100644 --- a/skills/gitlink-research-fair/SKILL.md +++ b/skills/gitlink-research-fair/SKILL.md @@ -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 发布失败 | 报告已在 ④ 落盘,告知路径 | diff --git a/skills/gitlink-research-fair/examples/edge-xray.md b/skills/gitlink-research-fair/examples/edge-xray.md new file mode 100644 index 0000000..d89c220 --- /dev/null +++ b/skills/gitlink-research-fair/examples/edge-xray.md @@ -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/有论文)画像迥异,报告自然分化 diff --git a/skills/gitlink-research-fair/examples/research-fair-workflow.md b/skills/gitlink-research-fair/examples/research-fair-workflow.md deleted file mode 100644 index d506789..0000000 --- a/skills/gitlink-research-fair/examples/research-fair-workflow.md +++ /dev/null @@ -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 且只打分不修复。 diff --git a/skills/gitlink-research-fair/scripts/fair.py b/skills/gitlink-research-fair/scripts/fair.py new file mode 100644 index 0000000..7cbf2aa --- /dev/null +++ b/skills/gitlink-research-fair/scripts/fair.py @@ -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() diff --git a/skills/gitlink-research-fair/scripts/test_fair.py b/skills/gitlink-research-fair/scripts/test_fair.py new file mode 100644 index 0000000..f74227f --- /dev/null +++ b/skills/gitlink-research-fair/scripts/test_fair.py @@ -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") diff --git a/skills/gitlink-spark/REFERENCE.md b/skills/gitlink-spark/REFERENCE.md new file mode 100644 index 0000000..079eb70 --- /dev/null +++ b/skills/gitlink-spark/REFERENCE.md @@ -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. "机会建议"为主观启发,标注"需研究者自行判断"。 diff --git a/skills/gitlink-spark/SKILL.md b/skills/gitlink-spark/SKILL.md new file mode 100644 index 0000000..b0a8e84 --- /dev/null +++ b/skills/gitlink-spark/SKILL.md @@ -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 目标 + 伪代码文本供手动起跑 | diff --git a/skills/gitlink-spark/examples/spark-图神经网络.md b/skills/gitlink-spark/examples/spark-图神经网络.md new file mode 100644 index 0000000..996bca9 --- /dev/null +++ b/skills/gitlink-spark/examples/spark-图神经网络.md @@ -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 全可查)。 +``` diff --git a/skills/gitlink-spark/scripts/spark.py b/skills/gitlink-spark/scripts/spark.py new file mode 100644 index 0000000..0af1f50 --- /dev/null +++ b/skills/gitlink-spark/scripts/spark.py @@ -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() diff --git a/skills/gitlink-spark/scripts/test_spark.py b/skills/gitlink-spark/scripts/test_spark.py new file mode 100644 index 0000000..8aab0b8 --- /dev/null +++ b/skills/gitlink-spark/scripts/test_spark.py @@ -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 + We propose a new graph attention mechanism using sparse attention. + 2024-03-15T00:00:00Z + + + http://arxiv.org/abs/2404.99999v2 + Federated Learning on Heterogeneous Graphs + A federated approach for heterogeneous graph neural networks. + 2024-04-20T00:00:00Z + +""" + +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") diff --git a/skills/gitlink-workflow/SKILL.md b/skills/gitlink-workflow/SKILL.md index c4f36d4..42d095e 100644 --- a/skills/gitlink-workflow/SKILL.md +++ b/skills/gitlink-workflow/SKILL.md @@ -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 全流程