forked from Gitlink/gitlink-cli
Merge pull request '新增demo' (#24) from surponess_br into master
This commit is contained in:
commit
86bb4cab98
|
|
@ -23,8 +23,20 @@ workflow:
|
|||
ssh_cmd: '"if [ -d /root/gitlink-cli/.git ]; then cd /root/gitlink-cli && git fetch origin && git checkout master && git reset --hard origin/master; else rm -rf /root/gitlink-cli && git clone https://gitlink.org.cn/whale_hihihi/gitlink-cli.git /root/gitlink-cli && cd /root/gitlink-cli && git checkout master; fi && docker stop gitlink-cli 2>/dev/null; docker rm gitlink-cli 2>/dev/null; docker rmi gitlink-cli:latest 2>/dev/null; docker build --no-cache -t gitlink-cli:latest . && docker run -d --name gitlink-cli -p 8080:8080 -e GITLINK_TOKEN=((gitlink_token.token)) gitlink-cli:latest && echo Deploy success"'
|
||||
needs:
|
||||
- start
|
||||
- ref: ssh_cmd_1
|
||||
name: 构建并部署demo网页(8000)
|
||||
task: ssh_cmd@1.1.1
|
||||
input:
|
||||
ssh_pass: ((deploy_server.password))
|
||||
ssh_ip: '"121.41.222.73"'
|
||||
ssh_port: '"22"'
|
||||
ssh_user: '"root"'
|
||||
ssh_cmd: '"cd /root/gitlink-cli && docker stop gitlink-cli-demo 2>/dev/null; docker rm gitlink-cli-demo 2>/dev/null; docker rmi gitlink-cli-demo:latest 2>/dev/null; docker build --no-cache -f demo/Dockerfile -t gitlink-cli-demo:latest . && docker run -d --name gitlink-cli-demo -p 8000:8000 --restart unless-stopped gitlink-cli-demo:latest && echo Demo deploy success at http://121.41.222.73:8000"'
|
||||
needs:
|
||||
- ssh_cmd_0
|
||||
- ref: end
|
||||
name: 结束
|
||||
task: end
|
||||
needs:
|
||||
- ssh_cmd_0
|
||||
- ssh_cmd_1
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
docs/
|
||||
doc/
|
||||
# Python(demo 网页后端)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.exe
|
||||
research-output/
|
||||
demo/bin/
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# demo/Dockerfile — GitLink CLI 演示网页后端
|
||||
# 多阶段:Go 编译 Linux 二进制 → Python 运行时跑 server.py
|
||||
# 构建上下文 = 仓库根(gitlink-cli/): docker build -f demo/Dockerfile -t gitlink-cli-demo .
|
||||
|
||||
# ---------- Stage 1: 编译 gitlink-cli(Linux) ----------
|
||||
FROM golang:1.26-alpine AS builder
|
||||
ENV GOPROXY=https://goproxy.cn,direct
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build -ldflags="-s -w" -o /out/gitlink-cli .
|
||||
|
||||
# ---------- Stage 2: Python 运行时 ----------
|
||||
FROM python:3.11-alpine
|
||||
RUN apk add --no-cache git ca-certificates
|
||||
WORKDIR /app
|
||||
# 二进制
|
||||
COPY --from=builder /out/gitlink-cli /usr/local/bin/gitlink-cli
|
||||
# 演示网页 + Skills(server.py 要读 SKILL.md)
|
||||
COPY demo/ /app/demo/
|
||||
COPY skills/ /app/skills/
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=8000
|
||||
ENV GITLINK_BIN=/usr/local/bin/gitlink-cli
|
||||
EXPOSE 8000
|
||||
WORKDIR /app/demo/web
|
||||
CMD ["python3", "server.py"]
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# GitLink CLI · 演示网页(demo/)
|
||||
|
||||
> 一个**可交互演示站**,展示子任务一~四全部成果:25 域命令浏览器、48 Skill 卡片墙、pr-guard 工作流、科研四维画像。
|
||||
> 后端 `web/server.py`(Python 标准库,零依赖)真跑 `gitlink-cli`;**访客自带 token,零凭据上云**。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
demo/
|
||||
├── web/ # 演示网页(核心)
|
||||
│ ├── server.py # Python 后端(/api/run /api/skill /api/analyze)
|
||||
│ ├── index.html # 前端单页(Tailwind+Chart.js CDN)
|
||||
│ └── README.md # 网页本地启动说明
|
||||
├── Dockerfile # demo 部署镜像(Go 编译 + Python 运行时)
|
||||
├── build-demo.sh # 本地一键产 Linux 二进制(测 Dockerfile 用)
|
||||
├── research-insight-workflow.sh # 任务四:科研画像端到端脚本
|
||||
├── pr-guard-workflow.sh # 任务三:质量看门人脚本
|
||||
├── live-demo.sh / snippet-live-demo.sh
|
||||
└── *.md # 各任务指南 + 验证记录 + 报告原件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 一、本地跑(Windows / Linux / macOS)
|
||||
|
||||
```bash
|
||||
# 1. 编译 CLI(仓库根)
|
||||
cd gitlink-cli # 含 go.mod 的仓库根
|
||||
go build -o gitlink-cli . # Windows 产出 gitlink-cli.exe
|
||||
|
||||
# 2. 启动后端
|
||||
cd demo/web
|
||||
python server.py # → http://0.0.0.0:8000
|
||||
|
||||
# 3. 浏览器开 http://localhost:8000
|
||||
# 顶栏粘自己的 GitLink token → 平台命令真跑;snippet 等本地命令免 token
|
||||
```
|
||||
|
||||
> 二进制自动探测:`GITLINK_BIN` > 仓库根 `gitlink-cli[.exe]` > PATH。
|
||||
> 端口/主机:`PORT=9000 HOST=127.0.0.1 python server.py`。
|
||||
|
||||
---
|
||||
|
||||
## 二、云端部署(用你们已有服务器 121.41.222.73)
|
||||
|
||||
已配置好「**push 即上线**」:`.devops/自动构建部署.yml` 在原有 CLI 部署之后,**追加一步**构建并运行 demo 容器:
|
||||
|
||||
```
|
||||
docker build -f demo/Dockerfile -t gitlink-cli-demo .
|
||||
docker run -d --name gitlink-cli-demo -p 8000:8000 --restart unless-stopped gitlink-cli-demo
|
||||
```
|
||||
|
||||
**你只需**:
|
||||
1. 把本目录提交并 push 到 `whale_hihihi/gitlink-cli` master → CI 自动跑 → demo 上线。
|
||||
2. 在服务器安全组/防火墙**开放 8000 端口**(阿里云安全组加一条入方向 TCP 8000)。
|
||||
3. 访问 `http://121.41.222.73:8000`。
|
||||
|
||||
> 手动部署(不走 CI):SSH 到服务器,`cd /root/gitlink-cli && docker build -f demo/Dockerfile -t gitlink-cli-demo . && docker run -d --name gitlink-cli-demo -p 8000:8000 --restart unless-stopped gitlink-cli-demo`。
|
||||
|
||||
### 镜像里有什么(demo/Dockerfile 多阶段)
|
||||
- Stage1 `golang:1.26-alpine`:`CGO_ENABLED=0 GOOS=linux go build` 产 Linux 二进制。
|
||||
- Stage2 `python:3.11-alpine`:装 `git`/`ca-certificates`,放二进制到 `/usr/local/bin/gitlink-cli`,拷 `demo/` 和 `skills/`,`ENV PORT=8000`,`CMD python3 demo/web/server.py`。
|
||||
|
||||
---
|
||||
|
||||
## 三、安全模型(为什么能放心公网开放)
|
||||
|
||||
| 点 | 做法 |
|
||||
|----|------|
|
||||
| 团队 token | **不烘焙**进镜像/代码。镜像里没有任何 GitLink 凭据。 |
|
||||
| 访客 token | 只存在访客自己的浏览器 localStorage,按请求传后端 → 注入子进程 `GITLINK_TOKEN` → 用完即弃,**不落盘、不写日志**。 |
|
||||
| 命令注入 | 后端白名单(仅 30 个 gitlink-cli 顶层域)+ subprocess 列表参数(不经 shell)+ 30s 超时。 |
|
||||
| 写操作 | CLI 写操作本就要 `--dry-run`/确认;演示页默认只点只读命令。 |
|
||||
|
||||
→ 公网开放的安全风险≈0:泄露的至多是访客自己输错的那一次请求。
|
||||
|
||||
---
|
||||
|
||||
## 四、访客怎么用(写进 PPT/答辩)
|
||||
|
||||
1. 打开 `http://121.41.222.73:8000`。
|
||||
2. 顶栏粘自己的 GitLink 个人访问令牌(GitLink → 个人中心 → 个人令牌)。
|
||||
3. 点「命令域」里任意动词 → 终端真跑;或点 Skill 卡片读 SKILL.md;或科研区「实拉分析」任一仓库。
|
||||
|
||||
---
|
||||
|
||||
## 五、其它 PaaS 部署(可选,不占你们服务器)
|
||||
|
||||
也可部署到 Render / Railway / Koyeb 等(需能跑 Docker):
|
||||
- 用 `demo/Dockerfile`,暴露端口环境变量 `PORT`(已支持)。
|
||||
- 这些平台默认按其给的端口注入 `PORT`,server.py 已读 `PORT` 环境变量,无需改。
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Skills 功能验收演示文稿
|
||||
|
||||
> 用法:照此 5 分钟流程演示。**Demo 1 可现场实跑**(零依赖、最稳),其余讲解设计。
|
||||
> 配套:`snippet-live-demo.sh`(实演脚本)、`../Skills工作总结.md`(完整成果)
|
||||
|
||||
---
|
||||
|
||||
## 演示总览(5 分钟)
|
||||
|
||||
| 环节 | 时长 | 形式 | 目的 |
|
||||
|------|:----:|------|------|
|
||||
| 开场:Skills 是什么 | 30s | 口述 + 成果速览 | 讲清价值定位 |
|
||||
| **Demo 1 · snippet 实演** | 1.5min | **跑脚本** | 证明 Skill 真能驱动 CLI |
|
||||
| Demo 2 · onboarding 设计 | 1.5min | 打开 SKILL.md 讲 | 展示 AI 工作流设计深度 |
|
||||
| Demo 3 · digest/todo 体验优化 | 1min | 讲设计 + 分工 | 展示体验优化与去重思考 |
|
||||
| 收尾:成果 + 验证 | 30s | 数字 | 强化贡献 |
|
||||
|
||||
---
|
||||
|
||||
## 开场(30 秒)
|
||||
|
||||
> 一句话:**Skills 是写给 AI 的「菜谱」**——告诉 AI「什么场景、按什么顺序、调哪些 gitlink-cli 命令」。我们把 gitlink-cli 从「开发者工具」升级为「AI 可驱动的平台」。
|
||||
>
|
||||
> 本次新增 **5 个 Skill** + 补全 **28 个 examples** + snippet **7 命令端到端实测通过**。
|
||||
|
||||
---
|
||||
|
||||
## Demo 1 · snippet 现场实演(核心,必演)
|
||||
|
||||
```bash
|
||||
bash demo/snippet-live-demo.sh
|
||||
```
|
||||
|
||||
**脚本会演示的闭环**(每个场景都展示「🧑用户提问 → 🤖AI 读 SKILL.md 决策 → 执行命令 → 输出」):
|
||||
|
||||
| 场景 | 命令 | SKILL.md 规则 |
|
||||
|------|------|--------------|
|
||||
| 保存代码 | `snippet +create` | --title 必填、--tags 逗号分隔 |
|
||||
| 浏览 | `snippet +list` | 可按 tag/language 过滤 |
|
||||
| 检索 | `snippet +search` | 全文匹配 |
|
||||
| 详情 | `snippet +view` | 按 id |
|
||||
| 导出 | `snippet +export` | -o 写文件 |
|
||||
| 更新 | `snippet +update` | 至少一个字段 |
|
||||
| 删除 | `snippet +delete` | 不可逆,先确认 |
|
||||
|
||||
**讲解要点**:注意每个场景 AI 都先「读 SKILL.md 决策」再执行——这就是 Skills 的核心价值,**AI 不是瞎调命令,而是按菜谱编排**。输出严格符合 `{"ok":true,"data":{...}}` 格式。
|
||||
|
||||
---
|
||||
|
||||
## Demo 2 · onboarding 设计深度(展示 B 类工作流)
|
||||
|
||||
**操作**:打开 `gitlink-cli/gitlink-cli/skills/gitlink-onboarding/SKILL.md`
|
||||
|
||||
**重点讲三处**(评分重点):
|
||||
|
||||
1. **5 维度友好度评估表**(决策规则章节)——把「哪个 Issue 适合新人」从主观判断变成可量化打分:标题清晰度 / 描述完整度 / 代码定位 / 改动范围 / 难度标签。
|
||||
2. **4 个工作流**——项目概览 → 找任务 → 生成引导评论 → 贡献全流程(Fork→Branch→PR)。
|
||||
3. **引导评论输出模板**——AI 能自动生成「欢迎贡献 + 代码定位 + 修改步骤」的个性化评论。
|
||||
|
||||
> 一句话:A 类(命令包装)做不到「智能推荐 + 生成评论」,所以选 B 类(AI 工作流)。
|
||||
|
||||
---
|
||||
|
||||
## Demo 3 · digest / todo 体验优化(展示第二批 + 去重思考)
|
||||
|
||||
| Skill | 解决的痛点 | 与团队已有 Skill 的关系 |
|
||||
|-------|-----------|----------------------|
|
||||
| `gitlink-digest` | 信息太分散,看动态要挨个刷 | 与团队 `notification-digest` **分工**:它做通知中心,我做项目全景日报(Issue+PR+CI+活跃度) |
|
||||
| `gitlink-todo` | 没有「我的」视角,不知哪些在等我 | 团队**无对应**,真缺口 |
|
||||
|
||||
**去重思考(加分点)**:曾设计「僵尸唤醒 stale」,核查发现团队已有完整的 `gitlink-stale-issue-manager`(563 行),为避免重复造已删除——**体现对项目整体的理解和工程素养**。
|
||||
|
||||
---
|
||||
|
||||
## 收尾:成果 + 验证(30 秒)
|
||||
|
||||
| 指标 | 数据 |
|
||||
|------|------|
|
||||
| 新增 Skill | **5 个**(onboarding / auth / snippet / digest / todo) |
|
||||
| 补充 examples | **28 个**(23 个补已有 Skill + 5 个新增自带) |
|
||||
| 端到端实测 | snippet 全 7 命令通过 |
|
||||
| 命令可调用性 | 新增 Skill 全用已注册命令域,可真实调用 |
|
||||
|
||||
> 演示结束。完整设计详见 `Skills工作总结.md`。
|
||||
|
||||
---
|
||||
|
||||
## 答辩 Q&A 预备
|
||||
|
||||
| 可能的提问 | 回答要点 |
|
||||
|-----------|---------|
|
||||
| 工作边界? | 新增 5 个 Skill + 补 23 个 examples;团队原有 42 个(见总结第二节) |
|
||||
| 怎么证明 Skill 真能用? | 刚跑的 snippet 7 命令闭环;其余 4 个登录后可按 SKILL.md 工作流验证 |
|
||||
| 为什么 onboarding 选 B 类? | 需智能推荐 + 生成评论,A 类命令包装做不到 |
|
||||
| digest 和团队 notification-digest 重复吗? | 不重复,分工明确:通知中心 vs 项目全景日报 |
|
||||
| Skill 遵循什么规范? | 项目模板:YAML frontmatter + CRITICAL 三连 + 引用 gitlink-shared |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
# 一键构建 demo 所需的 Linux gitlink-cli 二进制(本地测试 Dockerfile 用)
|
||||
# 用法:bash demo/build-demo.sh → 产物 demo/bin/gitlink-cli
|
||||
set -e
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$DIR/.." && pwd)" # 仓库根
|
||||
OUT="$DIR/bin"
|
||||
mkdir -p "$OUT"
|
||||
echo "→ 在 $ROOT 编译 Linux amd64 二进制..."
|
||||
( cd "$ROOT" && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$OUT/gitlink-cli" . )
|
||||
echo "✓ 产出:$OUT/gitlink-cli"
|
||||
echo " 本地测容器:docker build -f $DIR/Dockerfile -t gitlink-cli-demo '$ROOT'"
|
||||
echo " docker run --rm -p 8000:8000 gitlink-cli-demo"
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
# 4 个 Skill 登录实演指南(onboarding / digest / todo / auth)
|
||||
|
||||
> 用法:登录后,**在 Claude Code 里用自然语言触发**,让真 AI 读 SKILL.md 自主编排 —— 这是最强的实演证据。
|
||||
> 配套:`live-demo.sh`(辅助采集脚本,不想手敲命令时用)。
|
||||
|
||||
---
|
||||
|
||||
## 〇、前置准备
|
||||
|
||||
1. **登录**:`gitlink-cli auth login`(或 `--token`),用 `gitlink-cli auth status` 确认已登录
|
||||
2. **准备测试仓库**:用一个你自己的/有权限的公开仓库作为演示对象(避免在团队主仓库留痕)
|
||||
3. **开着 Claude Code**:在本仓库目录下启动,让 AI 能读到 `skills/*/SKILL.md`
|
||||
4. **安全原则**:只读命令随便跑;写操作(评论/关闭)让 AI 先 `--dry-run` 或确认
|
||||
|
||||
---
|
||||
|
||||
## 〇〇、推荐演示方式:自然语言触发真 AI(最有说服力)
|
||||
|
||||
> 不要告诉 AI 用哪个命令,只描述需求。看它是否**自主读 SKILL.md → 调对命令 → 输出符合模板**。
|
||||
> 这是"Skills 让 AI 能驱动 CLI"的活证据,比手敲命令强得多。
|
||||
|
||||
每个 Skill 下面都给:**① 触发语**(你对 AI 说这句)→ **② 预期 AI 行为** → **③ 手动备选**(想自己跑时)→ **④ 讲解要点**。
|
||||
|
||||
---
|
||||
|
||||
## 一、auth 实演(最简单,开场暖身)
|
||||
|
||||
**① 触发语**:
|
||||
> "检查一下我的 gitlink 登录状态"
|
||||
|
||||
**② 预期 AI 行为**:读 `auth/SKILL.md` → 调 `gitlink-cli auth status` → 报告登录用户、Token 有效期、存储位置。
|
||||
|
||||
**③ 手动备选**:
|
||||
```bash
|
||||
gitlink-cli auth status
|
||||
gitlink-cli auth login --token # 如需演示登录流程
|
||||
```
|
||||
|
||||
**④ 讲解要点**:
|
||||
- auth Skill 与 `gitlink-shared` 分工:shared 讲认证原理,auth 讲具体命令操作
|
||||
- 决策树:遇到 401 → 引导 `auth login`;403 → 查权限;CI 环境 → 用 `--token`
|
||||
- 演示 `logout` 后提醒:会清凭证,需重新登录
|
||||
|
||||
---
|
||||
|
||||
## 二、onboarding 实演(核心亮点:5 维度评估)
|
||||
|
||||
**① 触发语**:
|
||||
> "我想参与 <owner>/<repo> 这个项目,帮我找几个适合新手的任务"
|
||||
|
||||
**② 预期 AI 行为**:读 `onboarding/SKILL.md` →
|
||||
1. `search +issues --keyword "good first issue" --category opened`(找新手 Issue)
|
||||
2. `repo +info` + `repo +readme`(项目概览)
|
||||
3. 对候选 Issue 做 **5 维度友好度评估**(标题/描述/定位/范围/难度)
|
||||
4. 输出「推荐新手任务」清单 + 可选生成引导评论
|
||||
|
||||
**③ 手动备选**:
|
||||
```bash
|
||||
gitlink-cli search +issues --owner <owner> --repo <repo> --keyword "good first issue" --category opened
|
||||
gitlink-cli repo +info --owner <owner> --repo <repo>
|
||||
gitlink-cli repo +readme --owner <owner> --repo <repo>
|
||||
```
|
||||
|
||||
**④ 讲解要点**:
|
||||
- **5 维度评估表**是设计亮点:把"哪个 Issue 适合新人"从主观判断变成可量化打分(指着 AI 输出的评分讲)
|
||||
- 引导评论模板:AI 能生成「欢迎贡献 + 代码定位 + 修改步骤」个性化评论(写操作,会先确认)
|
||||
- 若无 good-first-issue 标签:AI 应从开放 Issue 推荐最简单的(决策规则)
|
||||
|
||||
---
|
||||
|
||||
## 三、digest 实演(亮点:跨源聚合成简报)
|
||||
|
||||
**① 触发语**:
|
||||
> "给我一份 <owner>/<repo> 的项目简报,今天有什么动态"
|
||||
|
||||
**② 预期 AI 行为**:读 `digest/SKILL.md` → 并行采集 → 聚合分类 →
|
||||
1. `issue +list --state open` + `pr +list`(Issue/PR 动态)
|
||||
2. `ci +builds`(CI 状态)
|
||||
3. `api GET "users/<me>/messages.json"`(通知)
|
||||
4. 按 🔴需关注 / 🟢新增 / 🔵进行中 / 📊指标 分类,输出 Markdown 简报
|
||||
|
||||
**③ 手动备选**:
|
||||
```bash
|
||||
gitlink-cli issue +list --state open --format json
|
||||
gitlink-cli pr +list --format json
|
||||
gitlink-cli ci +builds --owner <owner> --repo <repo> --format json
|
||||
gitlink-cli api GET "users/<me>/messages.json"
|
||||
```
|
||||
|
||||
**④ 讲解要点**:
|
||||
- **跨源聚合**是亮点:一份简报汇总 Issue/PR/CI/通知,不用挨个刷
|
||||
- 与团队 `notification-digest` 分工:它做通知中心(标记已读),digest 做项目全景(不做标记已读)—— 体现去重思考
|
||||
- 纯只读,安全可随时跑
|
||||
|
||||
---
|
||||
|
||||
## 四、todo 实演(亮点:补上「我的」视角)
|
||||
|
||||
**① 触发语**:
|
||||
> "我的待办有哪些?哪些 Issue/PR 在等我处理"
|
||||
|
||||
**② 预期 AI 行为**:读 `todo/SKILL.md` →
|
||||
1. `api GET "users/me"`(识别身份)
|
||||
2. `search +issues --assignee <me> --category opened`(分配我的)
|
||||
3. `api GET "users/<me>/messages.json"`(@我的)
|
||||
4. `pr +list`(我的 PR 状态)
|
||||
5. 按紧急度(@我 > 待 review > 指派)排序,输出待办清单
|
||||
|
||||
**③ 手动备选**:
|
||||
```bash
|
||||
gitlink-cli api GET "users/me" --format json
|
||||
gitlink-cli search +issues --assignee <me> --category opened
|
||||
gitlink-cli api GET "users/<me>/messages.json"
|
||||
```
|
||||
|
||||
**④ 讲解要点**:
|
||||
- **「我的」视角**是 gitlink 最缺的:跨 Issue/PR 汇总个人待办
|
||||
- 紧急度排序逻辑:@我且停留 >24h → 🔴紧急;待 review 的 PR → 🟡本周
|
||||
- 团队无对应 Skill,是真正的新增价值
|
||||
|
||||
---
|
||||
|
||||
## 五、验收串场词(5 分钟版)
|
||||
|
||||
```
|
||||
开场(30s):Skills 让 AI 能驱动 gitlink-cli。先看 snippet 实演(跑 snippet-live-demo.sh)。
|
||||
|
||||
转场:snippet 是本地功能。接下来演示需要平台 API 的 4 个 Skill,
|
||||
我用自然语言提问,看 AI 是否自主读 SKILL.md 编排命令。
|
||||
|
||||
① auth(30s):「检查登录状态」→ AI 调 auth status。
|
||||
② onboarding(1.5min):「找新手任务」→ AI 5 维度评估出推荐清单。(重点讲评估表)
|
||||
③ digest(1.5min):「给我项目简报」→ AI 跨源聚合出报告。(重点讲与团队分工)
|
||||
④ todo(1min):「我的待办」→ AI 汇总排序。(重点讲个人视角是缺口)
|
||||
|
||||
收尾(30s):5 个新增 Skill 都能被 AI 正确调用,snippet 7 命令实测通过。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、安全清单(实演前确认)
|
||||
|
||||
- [ ] 用**测试仓库**演示,不用团队主仓库
|
||||
- [ ] 写操作(onboarding 引导评论、issue close)让 AI **先确认 / --dry-run**
|
||||
- [ ] 演示完 `auth logout` 的话,记得重新登录
|
||||
- [ ] 只读命令(list/view/search/info/messages)可放心反复跑
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# 4 个 Skill 登录实演 · 辅助采集脚本
|
||||
# 作用:把每个 Skill 的「只读采集命令」串起来自动跑,展示真实数据
|
||||
# 分析(评估/聚合/排序)部分由 AI 在 Claude Code 里做——那才是亮点
|
||||
# 用法:bash demo/live-demo.sh <owner> <repo> [your-username]
|
||||
# 例:bash demo/live-demo.sh myorg myproject zhangsan
|
||||
# 前置:先 gitlink-cli auth login
|
||||
# ============================================================
|
||||
|
||||
_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLI="$_DIR/../gitlink-cli.exe"; [ -f "$CLI" ] || CLI="$_DIR/../gitlink-cli" # Win→.exe,Linux→无后缀
|
||||
OWNER="${1:-}"; REPO="${2:-}"; ME="${3:-$OWNER}"
|
||||
|
||||
G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m'; R='\033[0;31m'; B='\033[1m'; N='\033[0m'
|
||||
banner() { echo -e "\n${B}══════════════════════════════════════════════════════${N}"; echo -e "${B} $1${N}"; echo -e "${B}══════════════════════════════════════════════════════${N}"; }
|
||||
section() { echo -e "\n${C}━━━ $1 ━━━${N}"; }
|
||||
run() { echo -e "${Y}❯ $1${N}"; eval "$1" 2>&1 | head -16; echo; }
|
||||
|
||||
# ---------- 前置检查 ----------
|
||||
banner "4 Skill 登录实演 · 辅助采集"
|
||||
[ -f "$CLI" ] || { echo -e "${R}✗ 找不到 gitlink-cli${N}"; exit 1; }
|
||||
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
|
||||
echo -e "${R}用法: bash $0 <owner> <repo> [your-username]${N}"
|
||||
echo -e "${R}例 : bash $0 myorg myproject zhangsan${N}"; exit 1
|
||||
fi
|
||||
echo -e "${G}✓${N} 目标仓库: ${B}$OWNER/$REPO${N},当前用户: ${B}$ME${N}"
|
||||
echo -e "${C}提示:本脚本只跑只读采集命令;分析(评估/聚合/排序)请在 Claude Code 里让 AI 做${N}"
|
||||
|
||||
# ---------- 0. auth:登录状态 ----------
|
||||
section "auth · 登录状态"
|
||||
run "\"$CLI\" auth status"
|
||||
|
||||
# ---------- 1. onboarding:找新手任务 ----------
|
||||
section "onboarding · 新手 Issue + 项目概览(供 AI 做 5 维度评估)"
|
||||
run "\"$CLI\" search +issues --owner $OWNER --repo $REPO --keyword 'good first issue' --category opened --format json"
|
||||
run "\"$CLI\" repo +info --owner $OWNER --repo $REPO --format json"
|
||||
|
||||
# ---------- 2. digest:多源数据(供 AI 聚合成简报)----------
|
||||
section "digest · Issue / PR / CI / 通知(供 AI 跨源聚合)"
|
||||
run "\"$CLI\" issue +list --owner $OWNER --repo $REPO --state open --format json"
|
||||
run "\"$CLI\" pr +list --owner $OWNER --repo $REPO --format json"
|
||||
run "\"$CLI\" ci +builds --owner $OWNER --repo $REPO --format json"
|
||||
run "\"$CLI\" api GET \"users/$ME/messages.json\""
|
||||
|
||||
# ---------- 3. todo:个人待办数据(供 AI 排序)----------
|
||||
section "todo · 分配给我的 Issue + @我消息(供 AI 排序成待办)"
|
||||
run "\"$CLI\" api GET \"users/me\" --format json"
|
||||
run "\"$CLI\" search +issues --assignee $ME --category opened --format json"
|
||||
run "\"$CLI\" api GET \"users/$ME/messages.json\""
|
||||
|
||||
# ---------- 总结 ----------
|
||||
banner "采集完成"
|
||||
echo -e "${B}接下来${N}:在 Claude Code 里用自然语言触发,让 AI 读对应 SKILL.md 分析以上数据:"
|
||||
echo -e " • ${C}「找适合新手的任务」${N} → onboarding 的 5 维度评估"
|
||||
echo -e " • ${C}「给我项目简报」${N} → digest 的跨源聚合"
|
||||
echo -e " • ${C}「我的待办有哪些」${N} → todo 的紧急度排序"
|
||||
echo -e "\n详见 ${Y}live-demo-guide.md${N}"
|
||||
read -p "按回车键继续..."
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
# 代码质量看门人 · 工作流说明与架构(子任务三)
|
||||
|
||||
> 端到端自动化工作流:PR 提交后自动跑完「采集 → AI Review → CI → 评论 → 质量判定/合并」。
|
||||
> 对应 Skill:`skills/gitlink-pr-guard/SKILL.md`;可复现脚本:`demo/pr-guard-workflow.sh`。
|
||||
|
||||
---
|
||||
|
||||
## 一、工作流架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 触发:PR 提交 / 更新 │
|
||||
│ (或用户:帮我把关 PR #<id>) │
|
||||
└──────────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Step 1 采集 PR 变更 │
|
||||
│ pr +view → pr +files → pr +diff --stat │
|
||||
│ 产出:PR 详情 / 变更文件 / diff 统计 │
|
||||
└──────────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Step 2 AI Review(复用 code-review 逻辑) │
|
||||
│ 逐文件分析 diff → 分级找问题 │
|
||||
│ 🔴 Critical / 🟡 Warning / 🔵 Suggestion │
|
||||
└──────────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Step 3 CI 检查 │
|
||||
│ ci +builds → 匹配分支最新构建 → 状态 │
|
||||
│ success / failure / pending │
|
||||
└──────────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Step 4 发布质量看门人报告 │
|
||||
│ api POST .../pulls/:id/reviews │
|
||||
│ 报告:判定 + 问题清单 + CI + 处置建议 │
|
||||
└──────────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Step 5 质量判定(门禁规则) │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 0 Critical + CI success → ✅ 合并 │ │
|
||||
│ │ 有 Critical → 🔴 请求修改 │ │
|
||||
│ │ CI failure → 🔴 请求修改 │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ 达标+确认 → pr +merge │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、串联的 CLI 命令 / Skill(满足"≥3 步")
|
||||
|
||||
| Step | 命令域 | 具体调用 | 类型 |
|
||||
|:----:|:------:|---------|:----:|
|
||||
| 1 | pr | `pr +view` / `+files` / `+diff` | 采集 |
|
||||
| 2 | code-review | Review 分析逻辑(分级找问题) | AI 分析 |
|
||||
| 3 | ci | `ci +builds` / `+log` | 采集 |
|
||||
| 4 | api | `POST .../pulls/:id/reviews` | 写(评论) |
|
||||
| 5 | pr | `pr +merge`(达标确认后) | 写(合并) |
|
||||
|
||||
> **共串联 4 个命令域 + 5 个步骤 + 2 处写操作**,远超任务三"≥3 步"要求。
|
||||
|
||||
---
|
||||
|
||||
## 三、与子任务二的区别(关键)
|
||||
|
||||
| 维度 | 子任务二 Skill(如 code-review) | 子任务三 本工作流(pr-guard) |
|
||||
|------|--------------------------------|-----------------------------|
|
||||
| **交付单位** | 单个 Skill | 串联多步的**完整解决方案** |
|
||||
| **职责** | 只做 Review | Review + CI + 评论 + 合并决策 |
|
||||
| **触发** | 用户要 Review | PR 提交自动跑完整流水线 |
|
||||
| **决策** | 输出意见 | **质量门禁判定(通过/拒绝/合并)** |
|
||||
|
||||
> code-review 是"审查员",pr-guard 是"看门人"——后者在前者基础上加了 CI 维度和合并决策,形成完整门禁。
|
||||
|
||||
---
|
||||
|
||||
## 四、可复现性(对应交付要求)
|
||||
|
||||
| 要求 | 满足方式 |
|
||||
|------|---------|
|
||||
| 串联 ≥3 步 CLI/Skill | 5 步、4 域 ✅ |
|
||||
| 含自定义 Skill 兼容 Agent | `gitlink-pr-guard` SKILL.md(Claude Code 可读)✅ |
|
||||
| 可复现执行脚本 | `demo/pr-guard-workflow.sh`(参数化)✅ |
|
||||
| 真实 GitLink 项目演示 | 登录后对真实 PR 运行(见下) |
|
||||
| 工作流说明 + 架构图 | 本文档 ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 五、真实演示步骤(登录后)
|
||||
|
||||
```bash
|
||||
# 1. 登录
|
||||
gitlink-cli auth login
|
||||
|
||||
# 2. 找一个真实 PR
|
||||
gitlink-cli pr +list --owner <owner> --repo <repo> --state open
|
||||
|
||||
# 3. 跑质量看门人流水线(脚本采集,AI 在 Claude Code 做 Step2 分析)
|
||||
bash demo/pr-guard-workflow.sh <owner> <repo> <pr_id>
|
||||
|
||||
# 或在 Claude Code 里自然语言触发:
|
||||
# "读 skills/gitlink-pr-guard/SKILL.md,帮我把关 <owner>/<repo> 的 PR #42"
|
||||
```
|
||||
|
||||
**预期 AI 行为**:读 pr-guard SKILL.md → 按工作流跑 5 步 → 输出质量看门人报告 + 判定(通过/拒绝)+ 合并建议。
|
||||
|
||||
---
|
||||
|
||||
## 六、交付清单
|
||||
|
||||
- [x] `skills/gitlink-pr-guard/SKILL.md` — 工作流定义 + 门禁规则 + 报告模板
|
||||
- [x] `demo/pr-guard-workflow.sh` — 可复现脚本(5 步串联)
|
||||
- [x] `demo/pr-guard-architecture.md` — 本文档(说明 + 架构图)
|
||||
- [ ] 真实项目演示(登录后运行 + 截图/录屏)
|
||||
- [ ] 报告(暂缓,后续按统一策略补《新需求构思》《变更影响测试》)
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# 代码质量看门人 · 端到端工作流脚本(子任务三)
|
||||
# 串联 5 步:采集 PR → AI Review → CI 检查 → 汇总评论 → 质量判定
|
||||
# 用法:bash demo/pr-guard-workflow.sh <owner> <repo> <pr_id>
|
||||
# 例:bash demo/pr-guard-workflow.sh myorg myproject 42
|
||||
# 前置:gitlink-cli auth login(涉及平台 API)
|
||||
# 说明:采集命令真实执行;Review 分析由 AI Agent(读 pr-guard/SKILL.md)完成
|
||||
# ============================================================
|
||||
|
||||
_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLI="$_DIR/../gitlink-cli.exe"; [ -f "$CLI" ] || CLI="$_DIR/../gitlink-cli" # Win→.exe,Linux→无后缀
|
||||
OWNER="${1:-}"; REPO="${2:-}"; PR_ID="${3:-}"
|
||||
|
||||
G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m'; R='\033[0;31m'; B='\033[1m'; N='\033[0m'
|
||||
banner() { echo -e "\n${B}══════════════════════════════════════════════════════${N}"; echo -e "${B} $1${N}"; echo -e "${B}══════════════════════════════════════════════════════${N}"; }
|
||||
step() { echo -e "\n${C}━━━ Step $1 ━━━ ${B}$2${N}"; }
|
||||
ai() { echo -e "🤖 ${G}AI(读 pr-guard/SKILL.md 后):${N} $1"; }
|
||||
run() { echo -e "${Y}❯ $1${N}"; eval "$1" 2>&1 | head -16; echo; }
|
||||
|
||||
# ---------- 前置检查 ----------
|
||||
banner "代码质量看门人 · PR #${PR_ID:-?} 质量流水线"
|
||||
[ -f "$CLI" ] || { echo -e "${R}✗ 找不到 gitlink-cli${N}"; exit 1; }
|
||||
if [ -z "$OWNER" ] || [ -z "$REPO" ] || [ -z "$PR_ID" ]; then
|
||||
echo -e "${R}用法: bash $0 <owner> <repo> <pr_id>${N}"
|
||||
echo -e "${R}例 : bash $0 myorg myproject 42${N}"; exit 1
|
||||
fi
|
||||
echo -e "${G}✓${N} 目标: ${B}$OWNER/$REPO${N} PR #${B}$PR_ID${N}"
|
||||
|
||||
# ---------- Step 1:采集 PR 变更 ----------
|
||||
step 1 "采集 PR 变更(pr +view / +files / +diff)"
|
||||
ai "先拉 PR 详情、变更文件、diff 统计,作为审查输入。"
|
||||
run "\"$CLI\" pr +view --id $PR_ID --format json"
|
||||
run "\"$CLI\" pr +files --id $PR_ID --format json"
|
||||
run "\"$CLI\" pr +diff --id $PR_ID --stat"
|
||||
|
||||
# ---------- Step 2:AI Review(复用 code-review 逻辑)----------
|
||||
step 2 "AI Review(按 code-review 分级找问题)"
|
||||
ai "逐文件分析 diff,按安全红线/错误处理/规范分级。这一步由 AI Agent 完成(读 code-review/SKILL.md)。"
|
||||
echo -e " ${C}分级框架${N}:"
|
||||
echo -e " 🔴 Critical:硬编码密钥 / SQL·命令注入 / 路径遍历(安全红线,阻断合并)"
|
||||
echo -e " 🟡 Warning :错误处理缺失 / 边界条件 / 明文敏感信息"
|
||||
echo -e " 🔵 Suggestion:命名 / 性能 / 可配置化"
|
||||
echo -e " ${C}Agent 在此输出分级清单(示例见 SKILL.md 输出模板)${N}"
|
||||
|
||||
# ---------- Step 3:CI 检查 ----------
|
||||
step 3 "CI 检查(ci +builds)"
|
||||
ai "查 PR 对应分支的最新构建状态,作为门禁第二维。"
|
||||
run "\"$CLI\" ci +builds --owner $OWNER --repo $REPO --format json"
|
||||
echo -e " ${C}判定:从返回按 source_branch 匹配最新构建 → success / failure / pending${N}"
|
||||
|
||||
# ---------- Step 4:发布汇总评论 ----------
|
||||
step 4 "发布质量看门人报告(api POST .../reviews)"
|
||||
ai "把 Review 意见 + CI 状态 + 质量判定组装成报告,评论到 PR。"
|
||||
echo -e "${Y}❯ gitlink-cli api POST /$OWNER/$REPO/pulls/$PR_ID/reviews --body '<报告>'${N}"
|
||||
echo -e " ${C}报告含${N}:质量判定 + Critical/Warning 清单 + CI 状态 + 处置建议"
|
||||
echo -e " ${C}[实演时此处真实发送;脚本演示仅展示结构]${N}"
|
||||
|
||||
# ---------- Step 5:质量判定 ----------
|
||||
step 5 "质量判定(门禁规则 → 合并 / 请求修改)"
|
||||
ai "按门禁规则决策。注意:合并是写操作,默认只建议,确认后才执行。"
|
||||
echo -e " ${C}门禁规则${N}:"
|
||||
echo -e " 0 Critical + CI success → ✅ 通过,建议合并"
|
||||
echo -e " 有 Critical 任一 → 🔴 拒绝,请求修改"
|
||||
echo -e " CI failure → 🔴 拒绝,附 CI 日志"
|
||||
echo -e " 仅 Warning/Suggestion → 🟡 通过(带建议)"
|
||||
echo ""
|
||||
echo -e " ${G}若判定通过 + 用户确认 →${N} ${Y}gitlink-cli pr +merge --id $PR_ID --method squash${N}"
|
||||
|
||||
# ---------- 总结 ----------
|
||||
banner "流水线完成"
|
||||
echo -e "${B}代码质量看门人${N} 串联了 ${B}5 步${N},覆盖 ${B}4 个 CLI 域${N}:"
|
||||
echo -e " pr(采集/合并)+ code-review(Review)+ ci(构建)+ api(评论)"
|
||||
echo -e "\n${C}真实演示${N}:登录后对本仓库一个真实 PR 跑此脚本,由 AI 完成 Step 2 分析。"
|
||||
echo -e "详见 ${Y}pr-guard-architecture.md${N}(工作流说明 + 架构图)"
|
||||
read -p "按回车键继续..."
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# 科研仓库画像 · 使用指南(子任务四)
|
||||
|
||||
> 把 GitLink 上的开源科研项目,转化成**可评估、可引用、可复现**的科研画像。
|
||||
> 对应 Skill:`skills/gitlink-research-insight/SKILL.md`;脚本:`demo/research-insight-workflow.sh`。
|
||||
> 定位:科研辅助(加分项),面向科研工作者/课题组,无需 Go,Markdown + CLI。
|
||||
|
||||
---
|
||||
|
||||
## 一、它能做什么
|
||||
|
||||
科研工作者面对 GitLink 上的一个开源科研项目,常问三个问题:
|
||||
1. **能不能复现?**(别人的实验我跑得出同样结果吗)
|
||||
2. **能不能引用?**(写论文能引这个项目/代码吗,引哪个版本)
|
||||
3. **能不能合作?**(项目活跃吗,核心圈开放吗)
|
||||
|
||||
本工具用 gitlink-cli 采集仓库数据,从**可复现性 / 活跃度 / 引用价值 / 协作健康**四个维度评估,并生成**贡献者协作知识图谱**,产出一份《科研仓库画像报告》。
|
||||
|
||||
---
|
||||
|
||||
## 二、快速使用
|
||||
|
||||
```bash
|
||||
# 1. 登录
|
||||
gitlink-cli auth login
|
||||
|
||||
# 2. 对一个科研仓库跑画像(脚本采集,AI 评分)
|
||||
bash demo/research-insight-workflow.sh <owner> <repo>
|
||||
|
||||
# 或在 Claude Code 里自然语言触发:
|
||||
# "读 skills/gitlink-research-insight/SKILL.md,帮我评估 <owner>/<repo> 这个科研项目值不值得引用和复现"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、工作流架构
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ 输入:一个 GitLink 科研仓库 │
|
||||
└──────────────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Step 0 fork 检测(避免给 fork 错评) │
|
||||
│ repo +info → fork_info │
|
||||
└──────────────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Step 1 数据采集(只读) │
|
||||
│ repo +info/languages/contributors │
|
||||
│ issue +list / pr +list │
|
||||
│ file +get LICENSE/CI / release +list │
|
||||
│ git clone 兜底(repo +commits 不存在)│
|
||||
└──────────────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Step 2 四维科研评分 │
|
||||
│ 🔁 可复现性 📈 活跃度 │
|
||||
│ 📑 引用价值 🤝 协作健康 │
|
||||
└──────────────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Step 3 协作知识图谱(mermaid) │
|
||||
│ 贡献者协作网络 + 巴士因子 │
|
||||
└──────────────────┬───────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────┐
|
||||
│ Step 4 科研仓库画像报告 │
|
||||
│ 定性 + 综合评分 + 四维详情 + 建议 │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、四维评分体系(科研创新点)
|
||||
|
||||
### 🔁 可复现性(满分 10,科研最核心)
|
||||
| 检查项 | 分值 | 判定方法 |
|
||||
|--------|:---:|---------|
|
||||
| CI 配置 | +2 | `.gitea/workflows` 或 `.github/workflows` 存在 |
|
||||
| 依赖锁定 | +2 | `go.sum` / `package-lock.json` / `requirements.txt` |
|
||||
| 数据/数据说明 | +2 | README 提及数据集,或有 `data/` |
|
||||
| 运行文档 | +2 | README 有安装/运行/环境说明 |
|
||||
| 版本归档 | +2 | 有 release/tag(可引用固定版本) |
|
||||
|
||||
### 📈 活跃度
|
||||
近 3 月提交频率 + Issue/PR 活跃 + 贡献者趋势
|
||||
|
||||
### 📑 引用价值
|
||||
LICENSE + 版本归档 + 文档完整 + 星标/关注
|
||||
|
||||
### 🤝 协作健康
|
||||
Issue 响应时长 + PR 合并率 + **巴士因子**(核心贡献者提交占比,越分散越健康)
|
||||
|
||||
> **与 gitlink-health 区别**:health 看"工程维护好不好",本工具看"科研上值不值得用"(可复现/引用/合作)。
|
||||
|
||||
---
|
||||
|
||||
## 五、报告样例(填好的示例)
|
||||
|
||||
```markdown
|
||||
# 🔬 科研仓库画像 — someresearch/awesome-paper-code
|
||||
|
||||
> 一句话定性:这是一个活跃维护的 NLP 科研项目,适合引用与复现。
|
||||
|
||||
## 📊 综合评分:⭐ 8.5/10
|
||||
| 维度 | 得分 | 评价 |
|
||||
|------|:---:|------|
|
||||
| 🔁 可复现性 | 8/10 | CI+依赖锁定+数据说明齐全,可复现 |
|
||||
| 📈 活跃度 | 9/10 | 近3月持续提交,活跃 |
|
||||
| 📑 引用价值 | 9/10 | MIT 协议 + v2.1 版本,适合引用 |
|
||||
| 🤝 协作健康 | 8/10 | 巴士因子健康(核心占 45%) |
|
||||
|
||||
## 🔁 可复现性详情
|
||||
- CI:✅ GitHub Actions
|
||||
- 依赖锁定:✅ requirements.txt
|
||||
- 数据说明:✅ README 有数据集下载指引
|
||||
- 版本归档:✅ 最新 v2.1
|
||||
|
||||
## 💡 给科研工作者的建议
|
||||
1. 引用:建议引 v2.1(MIT 协议)
|
||||
2. 复现:按 README + CI 可复现
|
||||
3. 合作:协作网络开放,可联系 @核心贡献者
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、真实仓库验证步骤(交付要求)
|
||||
|
||||
```bash
|
||||
# 1. 选一个 GitLink 上的科研类仓库(有 LICENSE/CI/数据 的最佳)
|
||||
# 2. 跑画像脚本
|
||||
bash demo/research-insight-workflow.sh <科研仓库owner> <科研仓库>
|
||||
|
||||
# 3. 在 Claude Code 让 AI 读 SKILL.md 完成评分,产出报告
|
||||
# 4. 截图/录屏报告 → 作为演示成果
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、与子任务二/三的关系
|
||||
|
||||
| 子任务 | 视角 | 本任务复用 |
|
||||
|--------|------|-----------|
|
||||
| 任务二 health | 工程健康度 | 复用其指标采集 + 加科研维度 |
|
||||
| 任务三 pr-guard | 端到端工作流 | 复用"多步串联 + Skill + 脚本"模式 |
|
||||
| **任务四 本工具** | 科研价值 | 综合复用,加可复现性/引用/知识图谱 |
|
||||
|
||||
---
|
||||
|
||||
## 八、交付清单
|
||||
|
||||
- [x] `skills/gitlink-research-insight/SKILL.md` — 科研洞悉 Skill(自定义,遵循规范)
|
||||
- [x] `demo/research-insight-workflow.sh` — 可复现脚本(5 步:fork 检测 + 采集 + 评分 + 图谱 + 报告)
|
||||
- [x] `demo/research-insight-guide.md` — 本文档(完整中文 + 架构 + 报告样例)
|
||||
- [x] 真实科研仓库验证 — 已对 `whale_hihihi/gitlink-cli` 跑通,见 `demo/research-insight-verification.md` + 报告 `demo/research-insight-report-whale_gitlink-cli.md`
|
||||
- [x] 命令缺陷修复 — SKILL.md 与脚本原用的 `repo +commits`/`+raw`/`+tags` 三条不存在命令,已改为 `file +get`/`release +list`/`git clone` 兜底并实测通过
|
||||
- [ ] 演示录屏/截图(脚本与 AI 评分均可现场复现)
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
# 🔬 科研仓库画像 — whale_hihihi/gitlink-cli
|
||||
|
||||
> **一句话定性**:这是官方 `Gitlink/gitlink-cli` 的一个 **fork**(团队/课程竞赛工作副本),是一个 **工程类 CLI 工具 + AI Agent Skills 集合**,并非科研产出(无数据集/实验/论文/CITATION)。**可高置信复现与构建,但不适合作为独立科研对象引用——引用应指向 upstream。**
|
||||
>
|
||||
> *由 gitlink-research-insight Skill 评估,数据采集于 2026-07-05(仓库最近提交 2026-07-02,仍活跃)。*
|
||||
|
||||
## 📊 综合评分:⭐ 6.5/10(作为工程仓库优秀;作为"科研引用对象"偏低)
|
||||
|
||||
| 维度 | 得分 | 评价 |
|
||||
|------|:---:|------|
|
||||
| 🔁 可复现性 | 8/10 | CI + go.sum + 一键 npm 安装 + 完整文档,构建复现性强 |
|
||||
| 📈 活跃度 | 9/10 | 近 3 个月 ~300 次提交、102 个 PR 合并,当日仍在提交 |
|
||||
| 📑 引用价值 | 3/10 | 是 fork、无 release、无 CITATION、0 星标,引用应指 upstream |
|
||||
| 🤝 协作健康 | 6/10 | 多人 PR 流程活跃,但核心维护者高度集中、无 Issue 追踪 |
|
||||
|
||||
## 📋 基础信息
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| 描述 | gitlink-cli 是 GitLink 平台的官方 CLI(fork 自 `Gitlink/gitlink-cli`,project_id 1513956) |
|
||||
| 主要语言 | Go 97.3% · JavaScript 2.2% · Shell 0.4% · Makefile 0.1% |
|
||||
| 许可证 | Mulan PSL v2(木兰宽松许可证,清晰开源) |
|
||||
| 贡献者数 | 20(按提交计);GitLink 注册用户 3 |
|
||||
| 版本归档 | ⚠️ 1 个 tag(`v0.1.14`),但 **0 个正式 release** |
|
||||
| 规模 / 关注 | 26 MB · ⭐ 0 · 👁 0 · fork 0 |
|
||||
|
||||
## 🔁 可复现性详情(科研核心)
|
||||
|
||||
| 检查项 | 判定 | 依据 |
|
||||
|--------|------|------|
|
||||
| CI 配置 | ✅ 有 | `.gitea/workflows/ci.yml`(GitLink)+ `.github/workflows/{ci,release,test}.yml`,跑 Build/Lint/Test/fmt(Go 1.22) |
|
||||
| 依赖锁定 | ✅ go.sum | Go 模块校验文件齐全 |
|
||||
| 数据说明 | ❌ N/A | 是 CLI 工具,本无数据集(评分表工程类适配,不扣分) |
|
||||
| 运行/环境文档 | ✅ 完整 | README 33KB + zh-CN,含 Requirements(Node 14+/Go 1.26+)、Installation、Quick Start、Makefile、Dockerfile |
|
||||
| 版本归档 | ⚠️ 部分 | 有 tag `v0.1.14` 可 pin,但无 release 产物 |
|
||||
|
||||
> **可复现性结论**:**构建/运行层面极易复现**——`npm install -g @gitlink-ai/cli` 一键装、CI 全绿、文档完备。"复现实验结果"不适用(非实验型科研)。
|
||||
|
||||
## 📈 活跃度详情
|
||||
|
||||
- 提交分布:2026-04 → ~16 次,2026-05 → ~140 次,2026-06 → ~133 次,持续走高
|
||||
- 近 3 月提交 ~300 次,**最近一次提交 2026-07-02**(评估前 3 天)
|
||||
- 102 个 PR 合并提交,20 名贡献者——非常活跃,但仓库仅诞生 ~2.5 个月,属**新兴-高活跃**
|
||||
|
||||
## 🤝 协作网络图
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph 核心["核心圈(提交+评审)"]
|
||||
T[wbtiger<br/>~38% commits / 主评审]
|
||||
S[Surponess<br/>提交+评审]
|
||||
W[whale<br/>提交+评审]
|
||||
X[wauxing<br/>重构/合并 upstream]
|
||||
end
|
||||
subgraph 外围["PR 贡献者(fork/branch 提交)"]
|
||||
Y[wangyue111]
|
||||
M[muel]
|
||||
G[yangsai/Mengz]
|
||||
O[其余 10+ 人]
|
||||
end
|
||||
R((whale_hihihi/gitlink-cli))
|
||||
T -->|主提交+主评审| R
|
||||
S -->|提交+评审| R
|
||||
W -->|提交+评审| R
|
||||
X -->|重构/合并| R
|
||||
Y & M & G & O -.->|PR| R
|
||||
T -.评审/合并.-> Y
|
||||
T -.评审/合并.-> M
|
||||
```
|
||||
|
||||
- **巴士因子**:top1 `wbtiger` 占 ~38% 提交;评审更集中——`wbtiger` 一人合并了多数 PR。
|
||||
- ⚠️ **单点风险**:未越过"单人 >50%"红线,但核心维护者高度依赖 `wbtiger` 一人,且**无 Issue 追踪**(issues_count=0),协作瓶颈与知识单点并存。
|
||||
|
||||
## 💡 给科研工作者的建议
|
||||
|
||||
1. **引用**:❌ **不建议引用本 fork**。它是 `Gitlink/gitlink-cli` 的工作副本,无独立学术贡献、无 CITATION.cff、无 release、0 社区关注。如需引用该工具,**请引用 upstream `Gitlink/gitlink-cli`**,按 Mulan PSL v2 协议、pin 到 `v0.1.14` tag。
|
||||
2. **复现/构建**:✅ **强烈可复现**。`npm install -g @gitlink-ai/cli` 或 `make build`(Go 1.22+),配合 CI 与 README 几分钟可跑通。这是该仓库最大优点。
|
||||
3. **合作**:⚠️ 协作网络活跃但**核心圈封闭**——PR 走 fork 流程规范,但评审权集中在 `wbtiger`;新贡献者门槛中等。
|
||||
|
||||
---
|
||||
|
||||
### 执行备注(skill 命令缺陷,已修复)
|
||||
|
||||
按 skill 工作流执行时,原 SKILL.md/脚本的 `repo +commits`/`+tags`/`+raw` 三条命令在本二进制中不存在,GitLink 的 commits/pulls API 路径亦返回 HTML。本次评估改用 `file +get`(读文件)、`release +list`(版本)、`git clone`(提交时间线)完成采集,缺陷已同步修复进 SKILL.md 与脚本。详见 `research-insight-verification.md`。
|
||||
|
||||
---
|
||||
*由 gitlink-research-insight 科研画像生成 · 评估对象 whale_hihihi/gitlink-cli · 2026-07-05*
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
# 任务四 · research-insight 真实验证记录
|
||||
|
||||
> 对应 Skill:`skills/gitlink-research-insight/SKILL.md`;脚本:`demo/research-insight-workflow.sh`。
|
||||
> 验证对象:`whale_hihihi/gitlink-cli`(GitLink 上的真实仓库)。
|
||||
> 验证时间:2026-07-05。方法:对照模板法(见《验证剧本.md》三层证据)。
|
||||
> 配套报告原件:`demo/research-insight-report-whale_gitlink-cli.md`。
|
||||
|
||||
---
|
||||
|
||||
## 一、验证对象与结论
|
||||
|
||||
| 项 | 值 |
|
||||
|----|----|
|
||||
| 验证仓库 | `whale_hihihi/gitlink-cli`(公开、真实、有 LICENSE/CI/贡献者) |
|
||||
| 关键发现 | 是 `Gitlink/gitlink-cli` 的 **fork** —— 触发 SKILL.md「Step 0 fork 检测」,引用价值改评 upstream |
|
||||
| 总体结论 | ✅ **达标**:命令层 / 编排层 / 输出层 三层证据齐备,报告符合 SKILL.md 模板 |
|
||||
| 附带产出 | 发现并修复 SKILL.md + 脚本 3 条不存在命令(见第四节) |
|
||||
|
||||
---
|
||||
|
||||
## 二、三层证据(对照《验证剧本.md》模型)
|
||||
|
||||
| 层 | 证明什么 | 证据 | 结果 |
|
||||
|----|---------|------|:---:|
|
||||
| ① 命令层 | 命令真实执行、返回真实数据 | `repo +info/+languages/+contributors`、`file +get LICENSE`、`repo +tree .gitea/workflows`、`release +list`、`issue/pr +list` 均返回真实 JSON(非 404/unknown) | ✅ |
|
||||
| ② 编排层 | AI 按 SKILL.md **顺序**调命令 | 触发语后,AI 严格走 Step 0(fork 检测) → Step 1(采集) → Step 2(四维评分) → Step 3(协作图谱) → Step 4(报告) | ✅ |
|
||||
| ③ 输出层 | 输出**符合 SKILL.md 模板** | 报告含:综合评分 + 四维得分表 + 可复现性逐项 + mermaid 协作图 + 引用/复现/合作建议 + fork 改评 upstream 结论 | ✅ |
|
||||
|
||||
> ③ 是判定「达到效果」的硬标准——AI 实际输出与 SKILL.md「输出模板」并排对照,关键字段全部命中。
|
||||
|
||||
---
|
||||
|
||||
## 三、命令层证据(采集到的真实数据片段)
|
||||
|
||||
```bash
|
||||
# fork 检测(Step 0):脚本正确识别为 fork
|
||||
⚠️ whale_hihihi/gitlink-cli 是 Gitlink/gitlink-cli 的 fork —— 引用价值应改评 upstream Gitlink/gitlink-cli
|
||||
|
||||
# repo +info 关键字段
|
||||
contributor_users_count: 3 # GitLink 注册贡献者
|
||||
pull_requests_count: 21 # PR 计数
|
||||
version_releases_count: 0 # 无正式 release
|
||||
fork_info.fork_project_user_login: "Gitlink" # ← fork 来源
|
||||
|
||||
# repo +languages
|
||||
Go 97.3% / JavaScript 2.2% / Shell 0.4% / Makefile 0.1%
|
||||
|
||||
# repo +contributors(巴士因子输入)
|
||||
wbtiger 131 38.42% ← 核心,单人 <50%,未越红线
|
||||
wauxing 35 10.x %
|
||||
whale 34 10.x %
|
||||
...
|
||||
|
||||
# file +get LICENSE → Mulan PSL v2(木兰宽松许可证)
|
||||
# repo +tree .gitea/workflows → ci.yml(CI 配置存在)
|
||||
# release +list → releases: [](确认 0 release,与 info 一致)
|
||||
|
||||
# git 兜底(repo +commits 不存在)
|
||||
近 3 月提交: 300 次 | 最近提交: 2026-07-02(评估前 3 天) | tag: v0.1.14 | PR 合并数: 102
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证中发现的命令缺陷与修复(重要)
|
||||
|
||||
验证过程暴露 SKILL.md 与脚本原用了 **3 条 gitlink-cli 不存在的命令**,脚本 Step 1 会连续报错、采不到数据。已全部修复并实测通过:
|
||||
|
||||
| 原命令(不存在) | 现象 | 修复后(可用) | 实测 |
|
||||
|------------------|------|----------------|:---:|
|
||||
| `repo +commits --limit` | `unknown flag`,无此子命令 | `git clone --depth 100` + `git log`(API 无 commits JSON 端点) | ✅ 300 次/3 月 |
|
||||
| `repo +raw --path` | 无此子命令 | `file +get --path`(自动解码 base64)+ `repo +tree --path` 列目录 | ✅ 读到 LICENSE |
|
||||
| `repo +tags` | 无此子命令 | `release +list` + `repo +info.version_releases_count` | ✅ 确认 0 release |
|
||||
|
||||
**同步修复的文件**:
|
||||
- `skills/gitlink-research-insight/SKILL.md` —— Step 1 命令清单 + 降级方案 + 工程类仓库评分适配(已由前期协作完成)
|
||||
- `demo/research-insight-workflow.sh` —— Step 1 采集命令 + 新增 Step 0 fork 检测
|
||||
- `demo/research-insight-guide.md` —— 架构图 + 交付清单
|
||||
|
||||
**另发现的设计缺口(已在 SKILL.md 补)**:
|
||||
- 原 Skill 无 **fork 检测**步骤 → 对 fork 仓库会给出错误引用结论;已加 Step 0。
|
||||
- 评分表「数据/数据获取说明」项面向数据科学项目,套到 CLI 工具会扣冤枉分 → 已加「工程类 N/A」适配。
|
||||
|
||||
---
|
||||
|
||||
## 五、脚本一键复现
|
||||
|
||||
```bash
|
||||
# 登录后对任意 GitLink 仓库复现本次验证
|
||||
bash demo/research-insight-workflow.sh whale_hihihi gitlink-cli
|
||||
# AI 层(四维评分 + 协作图谱 + 报告)在 Claude Code 触发:
|
||||
# 读 skills/gitlink-research-insight/SKILL.md,评估 whale_hihihi/gitlink-cli 值不值得引用和复现
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、对照《验证剧本.md》结论表(任务四行)
|
||||
|
||||
| 任务 | 验证项 | 命令层 | 编排层 | 输出层 | 结论 |
|
||||
|------|--------|:---:|:---:|:---:|------|
|
||||
| 四 | research-insight | ✅ | ✅ | ✅ | 四维画像达标(含 fork 检测 + 命令缺陷修复) |
|
||||
|
||||
---
|
||||
|
||||
*本验证记录于 2026-07-05 生成,数据为当日实采。仓库活跃(最近提交 2026-07-02),数字可能随时间增长。*
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# 科研仓库画像 · 端到端工作流脚本(子任务四)
|
||||
# 4 步:采集数据 → 四维评分 → 协作图谱 → 科研画像报告
|
||||
# 用法:bash demo/research-insight-workflow.sh <owner> <repo>
|
||||
# 例:bash demo/research-insight-workflow.sh someresearch awesome-paper-code
|
||||
# 前置:gitlink-cli auth login(只读分析,不改数据)
|
||||
# 说明:采集命令真实执行;四维评分 + 协作图谱由 AI(读 research-insight/SKILL.md)完成
|
||||
# ============================================================
|
||||
|
||||
_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLI="$_DIR/../gitlink-cli.exe"; [ -f "$CLI" ] || CLI="$_DIR/../gitlink-cli" # Win→.exe,Linux→无后缀
|
||||
OWNER="${1:-}"; REPO="${2:-}"
|
||||
|
||||
G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m'; R='\033[0;31m'; B='\033[1m'; N='\033[0m'
|
||||
banner() { echo -e "\n${B}══════════════════════════════════════════════════════${N}"; echo -e "${B} $1${N}"; echo -e "${B}══════════════════════════════════════════════════════${N}"; }
|
||||
step() { echo -e "\n${C}━━━ Step $1 ━━━ ${B}$2${N}"; }
|
||||
ai() { echo -e "🤖 ${G}AI(读 research-insight/SKILL.md 后):${N} $1"; }
|
||||
run() { echo -e "${Y}❯ $1${N}"; eval "$1" 2>&1 | head -14; echo; }
|
||||
|
||||
# ---------- 前置检查 ----------
|
||||
banner "🔬 科研仓库画像 · $OWNER/${REPO:-?}"
|
||||
[ -f "$CLI" ] || { echo -e "${R}✗ 找不到 gitlink-cli${N}"; exit 1; }
|
||||
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
|
||||
echo -e "${R}用法: bash $0 <owner> <repo>${N}"
|
||||
echo -e "${R}例 : bash $0 someresearch awesome-paper-code${N}"; exit 1
|
||||
fi
|
||||
echo -e "${G}✓${N} 分析对象: ${B}$OWNER/$REPO${N}(只读,不改数据)"
|
||||
|
||||
# ---------- Step 0:fork 检测(避免给 fork 错评) ----------
|
||||
step 0 "fork 检测(引用价值要改评 upstream)"
|
||||
ai "先看 repo +info 的 fork_info。是 fork 则引用价值/活跃度改评 upstream。"
|
||||
INFO="$("$CLI" repo +info --owner "$OWNER" --repo "$REPO" --format json 2>/dev/null)"
|
||||
UPSTREAM="$(printf '%s' "$INFO" | grep -o '"fork_project_user_login": *"[^"]*"' | head -1 | sed 's/.*: *"//;s/"$//')"
|
||||
# fork_project_user_login 缺失或为 null → 非空才算 fork
|
||||
[ "$UPSTREAM" = "null" ] && UPSTREAM=""
|
||||
if [ -n "$UPSTREAM" ]; then
|
||||
echo -e " ${R}⚠️ $OWNER/$REPO 是 ${B}$UPSTREAM/$REPO${N}${R} 的 fork —— 引用价值应改评 upstream ${B}$UPSTREAM/$REPO${N}"
|
||||
else
|
||||
echo -e " ${G}✓${N} 独立仓库(非 fork),正常评估"
|
||||
fi
|
||||
|
||||
# ---------- Step 1:采集科研仓库数据 ----------
|
||||
step 1 "采集科研仓库数据(repo / file / issue / pr + 本地 git 兜底)"
|
||||
ai "拉基础画像、活跃度、合规复现性数据,作为科研评估输入。"
|
||||
echo -e "${Y}❯ 基础画像(repo +info / +languages / +contributors)${N}"
|
||||
"$CLI" repo +info --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -14
|
||||
"$CLI" repo +languages --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -8
|
||||
"$CLI" repo +contributors --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -12
|
||||
echo -e "\n${Y}❯ 复现性文件(file +get 读 LICENSE / CI —— 替代不存在的 repo +raw)${N}"
|
||||
"$CLI" file +get --owner "$OWNER" --repo "$REPO" --path LICENSE --format json 2>&1 | head -3
|
||||
"$CLI" repo +tree --owner "$OWNER" --repo "$REPO" --path .gitea/workflows --format json 2>&1 | head -6
|
||||
echo -e "\n${Y}❯ 版本归档(release +list —— 替代不存在的 repo +tags)${N}"
|
||||
"$CLI" release +list --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -6
|
||||
echo -e "\n${Y}❯ 活跃度(issue/pr + git 兜底 —— repo +commits 不存在)${N}"
|
||||
"$CLI" issue +list --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -8
|
||||
"$CLI" pr +list --owner "$OWNER" --repo "$REPO" --format json 2>&1 | head -8
|
||||
echo -e " ${C}repo +commits 不存在 → 用 git clone 兜底读提交时间线${N}"
|
||||
TMP="/tmp/${OWNER}-${REPO}-analyze"; rm -rf "$TMP"
|
||||
if git clone --quiet --depth 100 "https://gitlink.org.cn/$OWNER/$REPO.git" "$TMP" 2>/dev/null; then
|
||||
echo -e " 近 3 月提交: $(git -C "$TMP" log --oneline --since='3 months ago' 2>/dev/null | wc -l) 次"
|
||||
echo -e " 最近提交 : $(git -C "$TMP" log -1 --format='%ci %an' 2>/dev/null)"
|
||||
echo -e " tag 列表 : $(git -C "$TMP" tag 2>/dev/null | tr '\n' ' ')"
|
||||
echo -e " PR 合并数 : $(git -C "$TMP" log --merges --oneline 2>/dev/null | wc -l)"
|
||||
else
|
||||
echo -e " ${R}✗ git clone 失败(无 git 或无网络)→ 活跃度改用 repo +info 计数近似${N}"
|
||||
fi
|
||||
|
||||
# ---------- Step 2:四维科研评分 ----------
|
||||
step 2 "四维科研评分(AI 按指标体系打分)"
|
||||
ai "对采集数据按科研四维评分。这一步由 AI 完成(指标体系见 SKILL.md)。"
|
||||
echo -e " ${C}🔁 可复现性${N}(科研核心,满分10):CI(+2) / 依赖锁定(+2) / 数据说明(+2) / 运行文档(+2) / 版本归档(+2)"
|
||||
echo -e " ${C}📈 活跃度${N}:近3月提交频率 + Issue/PR 活跃 + 贡献者趋势"
|
||||
echo -e " ${C}📑 引用价值${N}:LICENSE + 版本归档 + 文档完整 + 星标"
|
||||
echo -e " ${C}🤝 协作健康${N}:Issue响应 + PR合并率 + 巴士因子(核心贡献者占比)"
|
||||
echo -e " ${C}Agent 在此输出各维度得分 + 判定依据${N}"
|
||||
|
||||
# ---------- Step 3:协作知识图谱 ----------
|
||||
step 3 "协作知识图谱(贡献者协作网络)"
|
||||
ai "从贡献者 + PR 协作数据生成 mermaid 协作网络,呼应『知识图谱』要求。"
|
||||
cat <<'MERMAID'
|
||||
graph LR
|
||||
A[核心贡献者1] -->|主提交| P((项目))
|
||||
B[核心贡献者2] -->|主提交| P
|
||||
C[偶发贡献者] -->|贡献| P
|
||||
A -.评审.-> C
|
||||
B -.评审.-> C
|
||||
MERMAID
|
||||
echo -e " ${C}巴士因子${N}:核心贡献者提交占比 → <健康 / 单点风险>"
|
||||
|
||||
# ---------- Step 4:科研画像报告 ----------
|
||||
step 4 "生成科研画像报告"
|
||||
ai "组装成《科研仓库画像报告》:一句话定性 + 综合评分 + 四维详情 + 协作图 + 引用/复现/合作建议。"
|
||||
echo -e " ${C}报告含${N}:🔬综合评分 / 📋基础信息 / 🔁可复现性详情 / 🤝协作网络图 / 💡给科研工作者建议"
|
||||
echo -e " ${C}模板见 SKILL.md「输出模板」+ research-insight-guide.md${N}"
|
||||
|
||||
# ---------- 总结 ----------
|
||||
banner "分析完成"
|
||||
echo -e "${B}科研仓库画像${N} 串联 ${B}5 步${N}(fork 检测 + 采集 + 评分 + 图谱 + 报告),覆盖 CLI 域(只读):"
|
||||
echo -e " repo / file / release / issue / pr + 本地 git(提交时间线兜底,repo +commits 不存在)"
|
||||
echo -e "\n${C}科研视角创新${N}:可复现性评分 + 引用价值 + 协作知识图谱(区别于普通 health 工程视角)"
|
||||
echo -e "${C}真实验证${N}:登录后对 GitLink 一个科研类仓库跑此脚本,由 AI 完成评分 → 产出报告 + 截图"
|
||||
echo -e "详见 ${Y}research-insight-guide.md${N}(完整中文使用文档 + 报告样例)"
|
||||
read -p "按回车键继续..."
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# GitLink Skills 功能演示 —— snippet 完整闭环
|
||||
# 核心卖点:AI 读取 SKILL.md → 自动编排 gitlink-cli 命令 → 完成完整场景
|
||||
# 特点:snippet 是本地功能,无需登录,可安全现场实演
|
||||
# 用法:bash demo/snippet-live-demo.sh
|
||||
# ============================================================
|
||||
|
||||
# 不用 set -e:保证演示连续性,关键步骤手动检查
|
||||
_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLI="$_DIR/../gitlink-cli.exe"; [ -f "$CLI" ] || CLI="$_DIR/../gitlink-cli" # Win→.exe,Linux→无后缀
|
||||
|
||||
# ANSI 颜色
|
||||
G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m'; R='\033[0;31m'; B='\033[1m'; N='\033[0m'
|
||||
|
||||
banner() { echo -e "\n${B}══════════════════════════════════════════════════════${N}"; echo -e "${B} $1${N}"; echo -e "${B}══════════════════════════════════════════════════════${N}"; }
|
||||
scene() { echo -e "\n${C}━━━ 场景 $1 ━━━ ${B}$2${N}"; }
|
||||
user() { echo -e "🧑 ${B}用户:${N} $1"; }
|
||||
ai() { echo -e "🤖 ${G}AI(读 snippet/SKILL.md 后):${N} $1"; }
|
||||
show() { echo -e "${Y}❯ $1${N}"; }
|
||||
|
||||
# ---------- 前置检查 ----------
|
||||
banner "GitLink Skills 演示 · snippet 闭环"
|
||||
[ -f "$CLI" ] || { echo -e "${R}✗ 找不到 gitlink-cli: $CLI${N}"; exit 1; }
|
||||
echo -e "${G}✓${N} gitlink-cli 就绪"
|
||||
echo -e "${G}✓${N} snippet 为本地功能(${B}无需登录${N}),可安全现场演示"
|
||||
echo -e "${G}✓${N} 演示数据用完即删,不污染环境"
|
||||
|
||||
# ---------- 场景 1:创建 ----------
|
||||
scene 1 "保存一段常用代码"
|
||||
user "帮我存一段快速排序代码,语言 python,标签 algorithm"
|
||||
ai "决策 → \`snippet +create\`(⚠️ Write)。SKILL.md 规则:--title 必填、--tags 逗号分隔、--language 标注。"
|
||||
show "gitlink-cli snippet +create --title '快速排序(演示)' --language python --tags algorithm,demo --content '...'"
|
||||
OUT=$("$CLI" snippet +create --title '快速排序(演示)' --language python --tags algorithm,demo \
|
||||
--content 'def qs(a): return a if len(a)<2 else qs([x for x in a[1:] if x<=a[0]])+[a[0]]+qs([x for x in a[1:] if x>a[0]])' \
|
||||
--format json 2>&1)
|
||||
echo "$OUT" | head -12
|
||||
DEMO_ID=$(echo "$OUT" | grep -oE '"id":[[:space:]]*"[0-9a-f]+"' | head -1 | grep -oE '[0-9a-f]{8}')
|
||||
echo -e "${G}✓${N} 已创建,id = ${B}$DEMO_ID${N}"
|
||||
|
||||
# ---------- 场景 2:列表 ----------
|
||||
scene 2 "浏览片段库"
|
||||
user "我存了哪些片段?"
|
||||
ai "决策 → \`snippet +list\`(Read)。可按 --tag / --language / --keyword 过滤。"
|
||||
show "gitlink-cli snippet +list --format json"
|
||||
"$CLI" snippet +list --format json 2>&1 | head -14
|
||||
|
||||
# ---------- 场景 3:搜索 ----------
|
||||
scene 3 "全文检索"
|
||||
user "帮我找包含 '排序' 的片段"
|
||||
ai "决策 → \`snippet +search\`(Read,全文匹配 title + content)。"
|
||||
show "gitlink-cli snippet +search --query '排序' --format json"
|
||||
"$CLI" snippet +search --query '排序' --format json 2>&1 | head -10
|
||||
|
||||
# ---------- 场景 4:查看详情 ----------
|
||||
scene 4 "查看指定片段"
|
||||
user "看看 id=$DEMO_ID 这个的详情"
|
||||
ai "决策 → \`snippet +view --id\`(Read)。"
|
||||
show "gitlink-cli snippet +view --id $DEMO_ID --format json"
|
||||
"$CLI" snippet +view --id "$DEMO_ID" --format json 2>&1 | head -12
|
||||
|
||||
# ---------- 场景 5:导出 ----------
|
||||
scene 5 "导出到文件复用"
|
||||
user "把它导出成文件,我要贴到项目里"
|
||||
ai "决策 → \`snippet +export --output\`(Read)。SKILL.md:默认输出到 stdout,-o 写文件。"
|
||||
TMP="$PWD/.demo_export_$$.py"
|
||||
show "gitlink-cli snippet +export --id $DEMO_ID --output $TMP"
|
||||
"$CLI" snippet +export --id "$DEMO_ID" --output "$TMP" >/dev/null 2>&1
|
||||
echo -e "${G}✓${N} 已导出,文件内容:"; cat "$TMP"; rm -f "$TMP"
|
||||
|
||||
# ---------- 场景 6:更新 ----------
|
||||
scene 6 "更新片段字段"
|
||||
user "给这个片段补个 tag 'sort'"
|
||||
ai "决策 → \`snippet +update\`(⚠️ Write)。--id 必填,至少一个字段。"
|
||||
show "gitlink-cli snippet +update --id $DEMO_ID --tags algorithm,demo,sort"
|
||||
"$CLI" snippet +update --id "$DEMO_ID" --tags algorithm,demo,sort --format json 2>&1 | head -8
|
||||
|
||||
# ---------- 场景 7:删除(清理)----------
|
||||
scene 7 "删除演示片段(清理)"
|
||||
user "演示结束,删掉刚才的测试片段"
|
||||
ai "决策 → \`snippet +delete\`(🔴 Destructive)。SKILL.md:删除不可逆,建议先 view 确认。"
|
||||
show "gitlink-cli snippet +delete --id $DEMO_ID"
|
||||
"$CLI" snippet +delete --id "$DEMO_ID" --format json 2>&1 | head -4
|
||||
echo -e "${G}✓${N} 演示数据已清理"
|
||||
|
||||
# ---------- 总结 ----------
|
||||
banner "演示完成"
|
||||
echo -e "${B}gitlink-snippet${N} Skill 的 7 个命令全部实测通过:"
|
||||
echo -e " create / list / search / view / export / update / delete"
|
||||
echo ""
|
||||
echo -e "${B}核心价值${N}:AI 读取 SKILL.md 后,能自动编排 gitlink-cli 命令完成完整场景,"
|
||||
echo -e "输出严格符合 SKILL.md 定义的 envelope 格式 {\"ok\":true,\"data\":{...}}。"
|
||||
echo -e "\n${C}其他 Skill(onboarding / digest / todo)涉及平台 API,登录后可按其 SKILL.md 的「工作流」演示。${N}"
|
||||
read -p "按回车键继续..."
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# GitLink CLI 智能化能力展示(演示网页)
|
||||
|
||||
一个**可交互的演示站**:点动词/敲命令 → 真跑 gitlink-cli → 显示真实输出,配合 25 域命令浏览器、48 Skill 卡片墙、pr-guard 流程、科研四维雷达,全面展示子任务一~四的成果。
|
||||
|
||||
> 位置:仓库内 `demo/web/`(`server.py` + `index.html`)。后端零依赖(仅 Python 标准库)。
|
||||
|
||||
## 架构(访客自带 token,零凭据上云)
|
||||
|
||||
```
|
||||
浏览器 index.html ──fetch──▶ server.py(Python 标准库)
|
||||
顶栏 token + owner/repo │ GET /api/skill 读 SKILL.md
|
||||
命令域 / 终端 / Skill 墙 │ POST /api/run 真跑 CLI(token 透传给子进程)
|
||||
pr-guard / 科研雷达 │ POST /api/analyze 四维评分 + 巴士因子
|
||||
▼
|
||||
gitlink-cli(仓库根 ../../gitlink-cli[.exe])
|
||||
```
|
||||
|
||||
- 访客 token 仅存在**访客自己的浏览器**(localStorage),按请求传后端 → 注入子进程 `GITLINK_TOKEN` → 用完即弃,**不落服务端、不写日志**。
|
||||
- 本地命令(`snippet`/`auth`)免 token 即可真跑;平台命令(`repo`/`issue`/`pr`…)需访客填自己的 token。
|
||||
|
||||
## 本地启动(3 步)
|
||||
|
||||
```bash
|
||||
# 1. 在仓库根编译 CLI(已有可跳过)
|
||||
cd gitlink-cli # 仓库根(含 go.mod)
|
||||
go build -o gitlink-cli . # Windows 会生成 gitlink-cli.exe
|
||||
|
||||
# 2. 启动后端(零依赖)
|
||||
cd demo/web
|
||||
python server.py # → http://0.0.0.0:8000
|
||||
|
||||
# 3. 浏览器打开 http://localhost:8000
|
||||
# 顶栏粘自己的 GitLink token(auth login --token 拿)→ 平台命令即可真跑
|
||||
```
|
||||
|
||||
> 服务端会自动探测二进制:`GITLINK_BIN` 环境变量 > 仓库根 `gitlink-cli`/`gitlink-cli.exe` > PATH。
|
||||
> 端口/主机可设:`PORT=9000 HOST=127.0.0.1 python server.py`。
|
||||
|
||||
## 展示区
|
||||
|
||||
| 区块 | 内容 |
|
||||
|------|------|
|
||||
| ① 命令全域浏览器 | 25 域 160+ 动词,按子任务分组 + 搜索;点动词填终端真跑 |
|
||||
| ② Skill 全集 | 48 个 Skill 卡片(按 全部/新增/科研/质量 筛选),点开读 SKILL.md 全文 |
|
||||
| ③ pr-guard | 5 步门禁动画 + 「用真实 PR 跑」(填 token) |
|
||||
| ④ 科研画像 | 「实拉分析」目标仓库 → 四维雷达 + 协作网络 + 巴士因子 |
|
||||
| ⑤ 验证 | 命令层 / 编排层 / 输出层 三层证据 |
|
||||
|
||||
## 安全
|
||||
|
||||
- 后端白名单(仅 30 个 gitlink-cli 顶层域)+ subprocess 列表参数(不经 shell)+ 30s 超时。
|
||||
- 访客 token 不落服务端。公网部署也**不烘焙任何团队 token**。
|
||||
|
||||
## 云端部署
|
||||
|
||||
见上级 [`demo/README.md`](../README.md)(Dockerfile + `.devops` 流水线 + 服务器部署说明)。
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GitLink CLI · 智能化能力展示</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
:root { --bg:#0d1117; --panel:#161b22; --border:#30363d; --txt:#c9d1d9; --acc:#58a6ff; --green:#3fb950; --red:#f85149; --yellow:#d29922; }
|
||||
body { background:var(--bg); color:var(--txt); font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; }
|
||||
.panel { background:var(--panel); border:1px solid var(--border); }
|
||||
.mono { font-family: "Cascadia Code", Consolas, "Courier New", monospace; }
|
||||
#term-out { background:#010409; min-height:340px; max-height:460px; overflow-y:auto; padding:14px; }
|
||||
.l-cmd { color:var(--acc); } .l-out { color:var(--txt); white-space:pre-wrap; word-break:break-all; }
|
||||
.l-err { color:var(--red); } .l-muted { color:#6e7681; font-style:italic; }
|
||||
#term-input { background:#010409; border-top:1px solid var(--border); }
|
||||
#term-input:focus-within { outline:none; }
|
||||
.skill-card { transition:.2s; }
|
||||
.skill-card:hover { border-color:var(--acc); transform:translateY(-2px); }
|
||||
.step { transition:.3s; }
|
||||
.step.active { border-color:var(--green); background:#1c2820; }
|
||||
.btn-cmd { transition:.15s; }
|
||||
.btn-cmd:hover { background:#21262d; border-color:var(--acc); }
|
||||
.verb { font-size:11px; padding:2px 7px; border-radius:4px; border:1px solid var(--border); background:#0d1117; cursor:pointer; transition:.15s; }
|
||||
.verb:hover { border-color:var(--acc); color:var(--acc); }
|
||||
.chip { background:#21262d; border:1px solid var(--border); }
|
||||
.fbtn { font-size:12px; padding:4px 12px; border-radius:9999px; border:1px solid var(--border); cursor:pointer; transition:.15s; }
|
||||
.fbtn.active { background:var(--acc); color:#000; border-color:var(--acc); }
|
||||
#skill-modal-body h1,#skill-modal-body h2,#skill-modal-body h3,#skill-modal-body h4 { color:#fff; margin:.7em 0 .35em; font-weight:600; }
|
||||
#skill-modal-body h1 { font-size:1.3em; border-bottom:1px solid var(--border); padding-bottom:.2em; }
|
||||
#skill-modal-body h2 { font-size:1.15em; } #skill-modal-body h3 { font-size:1.02em; }
|
||||
#skill-modal-body p { margin:.4em 0; line-height:1.6; }
|
||||
#skill-modal-body table { border-collapse:collapse; margin:.5em 0; display:block; overflow-x:auto; }
|
||||
#skill-modal-body th,#skill-modal-body td { border:1px solid var(--border); padding:4px 8px; text-align:left; font-size:.85em; }
|
||||
#skill-modal-body th { background:#21262d; }
|
||||
#skill-modal-body code { background:#010409; padding:1px 5px; border-radius:3px; color:var(--green); font-size:.85em; }
|
||||
#skill-modal-body pre { background:#010409; padding:10px; border-radius:6px; overflow-x:auto; margin:.5em 0; border:1px solid var(--border); }
|
||||
#skill-modal-body pre code { background:none; padding:0; color:var(--txt); }
|
||||
#skill-modal-body strong { color:#fff; }
|
||||
#skill-modal-body blockquote { border-left:3px solid var(--acc); padding-left:10px; color:#8b949e; margin:.5em 0; }
|
||||
#skill-modal-body ul,#skill-modal-body ol { padding-left:1.4em; margin:.4em 0; }
|
||||
#skill-modal-body hr { border-color:var(--border); margin:.8em 0; }
|
||||
#skill-modal-body a { color:var(--acc); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
|
||||
<!-- Header -->
|
||||
<header class="border-b border-[var(--border)] panel">
|
||||
<div class="max-w-7xl mx-auto px-6 py-5 flex items-center justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-white">🚀 GitLink CLI · 智能化能力展示</h1>
|
||||
<p class="text-sm text-[#8b949e] mt-1">从「开发者工具」升级为「AI 可驱动平台」—— 命令 · Skills · 工作流 · 科研辅助</p>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap text-xs">
|
||||
<span class="chip px-3 py-1.5 rounded-full">🛠 25 域 / 160+ 命令</span>
|
||||
<span class="chip px-3 py-1.5 rounded-full">🧠 48 Skills</span>
|
||||
<span class="chip px-3 py-1.5 rounded-full">🚪 pr-guard 工作流</span>
|
||||
<span class="chip px-3 py-1.5 rounded-full">🔬 科研四维画像</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 顶栏:访客 token + 目标仓库 -->
|
||||
<div class="max-w-7xl mx-auto px-6 pb-4 flex items-center gap-3 flex-wrap text-sm">
|
||||
<span class="text-[#8b949e] whitespace-nowrap">🔑 GitLink Token</span>
|
||||
<input id="token-input" type="password" placeholder="粘贴你的 GitLink 个人访问令牌(仅存本机浏览器)" class="flex-1 min-w-[220px] bg-[#0d1117] border border-[var(--border)] rounded px-3 py-1.5 text-xs mono">
|
||||
<span class="text-[#8b949e] whitespace-nowrap">📦 目标仓库</span>
|
||||
<input id="owner-input" value="Gitlink" class="w-28 bg-[#0d1117] border border-[var(--border)] rounded px-2 py-1.5 text-xs mono" placeholder="owner">
|
||||
<span class="text-[#6e7681]">/</span>
|
||||
<input id="repo-input" value="gitlink-cli" class="w-36 bg-[#0d1117] border border-[var(--border)] rounded px-2 py-1.5 text-xs mono" placeholder="repo">
|
||||
<span id="token-state" class="text-xs text-[#6e7681]"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="max-w-7xl mx-auto px-6 py-8 space-y-10">
|
||||
|
||||
<!-- ① 命令全域浏览器 + 终端 -->
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-white mb-1">▶ 命令全域浏览器 <span class="text-sm text-[#8b949e] font-normal">(25 个命令域 · 点动词填命令 · 真跑 gitlink-cli)</span></h2>
|
||||
<p class="text-sm text-[#8b949e] mb-4">点域里的动词自动填入终端;或在终端自由输入 <code class="text-[var(--green)]">gitlink-cli ...</code> 回车。平台命令需在顶栏填 token;snippet 等本地命令免登录。</p>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<div class="panel rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<h3 class="font-semibold text-white">命令域</h3>
|
||||
<input id="cmd-search" placeholder="🔍 搜索域/动词" class="flex-1 bg-[#0d1117] border border-[var(--border)] rounded px-2 py-1 text-xs mono">
|
||||
</div>
|
||||
<div id="cmd-groups" class="space-y-3 text-sm max-h-[460px] overflow-y-auto pr-1"></div>
|
||||
</div>
|
||||
<div class="panel rounded-lg lg:col-span-2 flex flex-col">
|
||||
<div class="flex items-center gap-2 px-4 py-2 border-b border-[var(--border)] text-xs text-[#8b949e]">
|
||||
<span class="w-3 h-3 rounded-full bg-[var(--red)]"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-[var(--yellow)]"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-[var(--green)]"></span>
|
||||
<span class="ml-2">gitlink-cli @ web-demo</span>
|
||||
<span id="term-target" class="ml-auto text-[var(--acc)]"></span>
|
||||
</div>
|
||||
<div id="term-out" class="mono text-sm flex-1"></div>
|
||||
<div class="flex items-center px-4 py-2 mono text-sm" id="term-input">
|
||||
<span class="text-[var(--green)] mr-2">$</span>
|
||||
<input id="cmd-input" class="flex-1 bg-transparent text-[var(--green)] mono" placeholder="输入命令,如 snippet +list 然后回车" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ② Skill 卡片墙 -->
|
||||
<section>
|
||||
<div class="flex items-center justify-between flex-wrap gap-3 mb-4">
|
||||
<h2 class="text-xl font-bold text-white">🧠 Skill 全集 <span class="text-sm text-[#8b949e] font-normal">(48 个 · 点卡片读 SKILL.md 全文)</span></h2>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button class="fbtn active" data-filter="all" onclick="filterSkill('all',this)">全部 48</button>
|
||||
<button class="fbtn" data-filter="new" onclick="filterSkill('new',this)">✨ 本次新增 7</button>
|
||||
<button class="fbtn" data-filter="research" onclick="filterSkill('research',this)">🔬 科研</button>
|
||||
<button class="fbtn" data-filter="quality" onclick="filterSkill('quality',this)">🚪 代码质量</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3" id="skill-wall"></div>
|
||||
</section>
|
||||
|
||||
<!-- ③ pr-guard 流程 -->
|
||||
<section class="panel rounded-lg p-6">
|
||||
<h2 class="text-xl font-bold text-white mb-1">🚪 任务三:代码质量看门人(pr-guard)</h2>
|
||||
<p class="text-sm text-[#8b949e] mb-5">PR 提交后自动跑完 5 步质量门禁 ——「模拟流水线」看流程;填 token + PR 号可拉真实 PR 数据</p>
|
||||
<div class="flex items-center justify-between gap-2 mb-5 flex-wrap" id="pr-steps"></div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button onclick="runPipeline()" class="bg-[var(--acc)] text-black px-4 py-2 rounded font-semibold text-sm hover:opacity-90">▶ 模拟流水线</button>
|
||||
<button onclick="livePR()" class="btn-cmd border border-[var(--border)] text-[var(--txt)] px-4 py-2 rounded text-sm">🔌 用真实 PR 跑(需 token)</button>
|
||||
</div>
|
||||
<div id="pr-verdict" class="mt-4 text-sm mono"></div>
|
||||
</section>
|
||||
|
||||
<!-- ④ 科研画像 -->
|
||||
<section class="panel rounded-lg p-6">
|
||||
<h2 class="text-xl font-bold text-white mb-1">🔬 任务四:科研仓库画像(research-insight)</h2>
|
||||
<p class="text-sm text-[#8b949e] mb-5">四维科研评分 + 协作网络 —— 实拉实算目标仓库,回答「能不能复现/引用/合作」</p>
|
||||
<div class="flex items-center gap-2 mb-5 flex-wrap">
|
||||
<span class="text-sm text-[#8b949e]">分析对象即顶栏 owner/repo</span>
|
||||
<button onclick="analyze()" class="bg-[var(--green)] text-black px-4 py-2 rounded font-semibold text-sm hover:opacity-90">🔍 实拉分析(需 token)</button>
|
||||
<span id="analyze-status" class="text-xs text-[#8b949e]"></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div><canvas id="radar"></canvas>
|
||||
<div id="repro-detail" class="text-xs text-[#8b949e] mt-3 mono"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-white font-semibold mb-2">协作网络图 <span id="bus-info" class="text-xs text-[#8b949e] font-normal">(点「实拉分析」用真实贡献者重绘)</span></h4>
|
||||
<svg id="collab-svg" viewBox="0 0 360 260" class="w-full panel rounded border border-[var(--border)]"></svg>
|
||||
<div id="repo-meta" class="text-xs text-[#8b949e] mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ⑤ 验证 -->
|
||||
<section>
|
||||
<h2 class="text-xl font-bold text-white mb-4">✅ 验证:三层证据(对照模板法)</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div class="panel rounded-lg p-4"><div class="text-[var(--green)] text-2xl mb-1">①</div><h4 class="text-white font-semibold">命令层</h4><p class="text-sm text-[#8b949e] mt-1">命令真实执行,返回真实数据(非 unknown/401)</p></div>
|
||||
<div class="panel rounded-lg p-4"><div class="text-[var(--yellow)] text-2xl mb-1">②</div><h4 class="text-white font-semibold">编排层</h4><p class="text-sm text-[#8b949e] mt-1">AI 按 SKILL.md 的顺序调命令</p></div>
|
||||
<div class="panel rounded-lg p-4"><div class="text-[var(--acc)] text-2xl mb-1">③</div><h4 class="text-white font-semibold">输出层</h4><p class="text-sm text-[#8b949e] mt-1">输出符合 SKILL.md 模板(判定/评分/清单)</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="text-center text-xs text-[#6e7681] py-6 border-t border-[var(--border)]">
|
||||
GitLink CLI 智能化能力展示 · 子任务一~四 + DevOps · 后端 demo/web/server.py(访客自带 token,零凭据上云)
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ===== token & owner/repo(localStorage 持久化)=====
|
||||
const tokenInput = document.getElementById('token-input');
|
||||
const ownerInput = document.getElementById('owner-input');
|
||||
const repoInput = document.getElementById('repo-input');
|
||||
tokenInput.value = localStorage.getItem('gl_token') || '';
|
||||
const tokenState = document.getElementById('token-state');
|
||||
function syncTokenState(){ tokenState.textContent = tokenInput.value ? '✅ 已提供(仅存本机)' : '⚠️ 未提供,平台命令需 token'; tokenState.style.color = tokenInput.value ? 'var(--green)' : 'var(--yellow)'; }
|
||||
tokenInput.addEventListener('input', ()=>{ localStorage.setItem('gl_token', tokenInput.value); syncTokenState(); });
|
||||
syncTokenState();
|
||||
function getToken(){ return tokenInput.value.trim(); }
|
||||
function getOwner(){ return ownerInput.value.trim() || 'Gitlink'; }
|
||||
function getRepo(){ return repoInput.value.trim() || 'gitlink-cli'; }
|
||||
function targetStr(){ return getOwner() + '/' + getRepo(); }
|
||||
document.getElementById('term-target').textContent = targetStr();
|
||||
[ownerInput, repoInput].forEach(el => el.addEventListener('input', ()=> document.getElementById('term-target').textContent = targetStr()));
|
||||
|
||||
// ===== 命令全域数据(25 域,按子任务分组)=====
|
||||
const DOMAIN_GROUPS = [
|
||||
{ g:'资源管理(仓库/文件/版本)', domains:[
|
||||
{d:'repo', v:['info','languages','contributors','contributor-stats','code-stats','readme','tree','list','create','delete','fork','follow','like','stargazers','watchers','unfollow','unlike']},
|
||||
{d:'file', v:['get','browse','create','update','delete']},
|
||||
{d:'branch', v:['list','create','delete','protect','unprotect']},
|
||||
{d:'release', v:['list','view','create','update','delete','edit','download']},
|
||||
{d:'search', v:['issues','repos','users']},
|
||||
{d:'compare', v:['view','files']},
|
||||
]},
|
||||
{ g:'Issue 跟踪', domains:[
|
||||
{d:'issue', v:['list','view','create','update','close','comment','assigners','authors','priorities','statuses','tags','batch-create','batch-update','batch-close','batch-open','batch-assign','batch-label','batch-delete']},
|
||||
{d:'label', v:['list','create','update','delete']},
|
||||
{d:'milestone', v:['list','view','create','close','delete']},
|
||||
]},
|
||||
{ g:'PR 与代码审查', domains:[
|
||||
{d:'pr', v:['list','view','create','merge','refuse','reopen','diff','files','versions','version-diff','comment','review','reviews','branches','check-merge']},
|
||||
]},
|
||||
{ g:'CI/CD · 流水线 · 工作流', domains:[
|
||||
{d:'ci', v:['builds','logs','restart','stop','enable','disable','authorize']},
|
||||
{d:'pipeline', v:['list','runs','run','view','logs','results','save-yaml','enable','disable','delete']},
|
||||
{d:'workflow', v:['triage','health','pr-summary','repo-report']},
|
||||
]},
|
||||
{ g:'项目管理 · 协作', domains:[
|
||||
{d:'pm', v:['boards','sprints','weekly','tags','pipelines','actions']},
|
||||
{d:'org', v:['list','info','members','create']},
|
||||
{d:'member', v:['list','add','remove']},
|
||||
{d:'webhook', v:['list','view','create','update','delete','history','test']},
|
||||
{d:'wiki', v:['list','view','create','update','delete','mkdir','rmdir','rename','renamedir']},
|
||||
]},
|
||||
{ g:'用户与画像', domains:[
|
||||
{d:'user', v:['me','info','headmaps','stats-activity','stats-develop','stats-major','stats-role','trends']},
|
||||
{d:'profile', v:['activity','ability','contribution','role','major']},
|
||||
]},
|
||||
{ g:'数据 · 合规 · 健康', domains:[
|
||||
{d:'dataset', v:['list','view','create','update','delete-attachment']},
|
||||
{d:'license', v:['list']},
|
||||
{d:'ignore', v:['list']},
|
||||
{d:'health', v:['fetch']},
|
||||
]},
|
||||
{ g:'本地工具(免登录)', domains:[
|
||||
{d:'snippet', v:['create','list','view','search','update','delete','export']},
|
||||
{d:'auth', v:['login','status','logout'], raw:true},
|
||||
]},
|
||||
];
|
||||
|
||||
const LOCAL_DOMAINS = new Set(['snippet','auth','config','version','doctor']);
|
||||
|
||||
// ===== 渲染命令域 =====
|
||||
const input = document.getElementById('cmd-input');
|
||||
function renderDomains(q=''){
|
||||
let html='';
|
||||
const Q = q.trim().toLowerCase();
|
||||
DOMAIN_GROUPS.forEach(grp => {
|
||||
const doms = grp.domains.filter(dm => {
|
||||
if(!Q) return true;
|
||||
return dm.d.includes(Q) || dm.v.some(x=>x.includes(Q));
|
||||
});
|
||||
if(!doms.length) return;
|
||||
html += `<div><div class="text-xs text-[var(--yellow)] mb-1.5">${grp.g}</div><div class="space-y-1.5">`;
|
||||
doms.forEach(dm => {
|
||||
const local = LOCAL_DOMAINS.has(dm.d);
|
||||
const tag = local ? '<span class="text-[10px] text-[var(--green)] ml-1">本地</span>' : '<span class="text-[10px] text-[#6e7681] ml-1">需token</span>';
|
||||
const verbs = dm.v.filter(x=> !Q || x.includes(Q) || dm.d.includes(Q)).map(v=>{
|
||||
const c = dm.raw ? `${dm.d} ${v}` : `${dm.d} +${v}`;
|
||||
return `<span class="verb" data-cmd="${c}">+${v}</span>`;
|
||||
}).join(' ');
|
||||
html += `<div class="panel rounded p-2"><div class="mono text-xs text-[var(--acc)] mb-1">${dm.d}${tag}</div><div class="flex flex-wrap gap-1">${verbs}</div></div>`;
|
||||
});
|
||||
html += `</div></div>`;
|
||||
});
|
||||
document.getElementById('cmd-groups').innerHTML = html || '<p class="text-xs text-[#6e7681]">无匹配</p>';
|
||||
document.querySelectorAll('.verb').forEach(el => el.onclick = ()=>{
|
||||
// 平台命令自动带 owner/repo(auth/snippet 除外)
|
||||
let c = el.dataset.cmd;
|
||||
const dom = c.split(' ')[0];
|
||||
if(!LOCAL_DOMAINS.has(dom) && !c.includes('--owner')) c += ` --owner ${getOwner()} --repo ${getRepo()}`;
|
||||
input.value = c; input.focus();
|
||||
});
|
||||
}
|
||||
renderDomains();
|
||||
document.getElementById('cmd-search').addEventListener('input', e=>renderDomains(e.target.value));
|
||||
|
||||
// ===== 终端 =====
|
||||
const out = document.getElementById('term-out');
|
||||
let history=[], hidx=0;
|
||||
function appendOut(text, cls='l-out'){
|
||||
const div=document.createElement('div'); div.className='mono text-sm '+cls; div.textContent=text;
|
||||
out.appendChild(div); out.scrollTop=out.scrollHeight;
|
||||
}
|
||||
async function runCmd(){
|
||||
let cmd=input.value.trim();
|
||||
if(!cmd||cmd.startsWith('#')){ if(cmd) appendOut('$ '+cmd,'l-cmd'); input.value=''; return; }
|
||||
history.push(cmd); hidx=history.length;
|
||||
appendOut('$ '+cmd,'l-cmd'); input.value='';
|
||||
const m=document.createElement('div'); m.className='mono text-sm l-muted'; m.textContent='⏳ 运行中...';
|
||||
out.appendChild(m); out.scrollTop=out.scrollHeight;
|
||||
try{
|
||||
const r=await fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({cmd,token:getToken()})});
|
||||
const d=await r.json(); m.remove();
|
||||
if(d.ok){
|
||||
appendOut((d.stdout||'')+(d.stderr?'\n[stderr] '+d.stderr:'')||'(无输出)','l-out');
|
||||
if(d.code!==0) appendOut(`[退出码 ${d.code}]`,'l-err');
|
||||
} else {
|
||||
if(d.needs_token && !d.token_provided) appendOut('⚠️ '+d.error+'(此为平台命令,请在顶栏填 GitLink token)','l-err');
|
||||
else appendOut('❌ '+d.error,'l-err');
|
||||
}
|
||||
}catch(e){ m.remove(); appendOut('❌ 请求失败:'+e+'(后端 server.py 是否启动?)','l-err'); }
|
||||
}
|
||||
input.addEventListener('keydown',e=>{
|
||||
if(e.key==='Enter') runCmd();
|
||||
else if(e.key==='ArrowUp'){ if(hidx>0){hidx--;input.value=history[hidx]||'';e.preventDefault();} }
|
||||
else if(e.key==='ArrowDown'){ if(hidx<history.length){hidx++;input.value=history[hidx]||'';e.preventDefault();} }
|
||||
});
|
||||
|
||||
// ===== Skill 全集(48)=====
|
||||
const SKILLS = ["auth","branch","ci","ci-health","code-review","commit-quality","compare","compliance","contributor-insight","dataset","digest","file","gatekeeper","health","insight","issue","issueops","issue-tag","issue-triage","label","license-compliance","member","milestone","notification-digest","onboarding","org","pipeline","pipeline-guardian","pm","pr","pr-guard","release","release-auto","repo","research-fork-impact","research-insight","research-tracker","scholar-profile","search","shared","snippet","stale-issue-manager","todo","user","webhook","webhook-sentinel","wiki","wiki-builder","workflow"];
|
||||
const NEW_SKILLS = new Set(['onboarding','auth','snippet','digest','todo','pr-guard','research-insight']);
|
||||
const SKILL_ICON = {onboarding:'🚀',auth:'🔑',snippet:'📦',digest:'📰',todo:'📋','pr-guard':'🚪','research-insight':'🔬','code-review':'👁','commit-quality':'✨','health':'❤','release-auto':'🏷','workflow':'🔁','compliance':'✅','license-compliance':'📜','wiki-builder':'📚','pipeline-guardian':'🛡','webhook-sentinel':'📡',stale:'💤'};
|
||||
function skillCat(n){
|
||||
if(NEW_SKILLS.has(n)) return 'new';
|
||||
if(/research|scholar|contributor-insight|insight|fork-impact|tracker/.test(n)) return 'research';
|
||||
if(/code-review|commit-quality|gatekeeper|pr-guard|compliance|license-compliance/.test(n)) return 'quality';
|
||||
return 'other';
|
||||
}
|
||||
function skillEmoji(n){ return SKILL_ICON[n] || (/research|scholar/.test(n)?'🔬':/wiki/.test(n)?'📚':/pipeline|ci|webhook/.test(n)?'🛠':'📁'); }
|
||||
let SKILL_FILTER='all';
|
||||
function filterSkill(f,btn){
|
||||
SKILL_FILTER=f;
|
||||
document.querySelectorAll('.fbtn').forEach(b=>b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
renderSkills();
|
||||
}
|
||||
function renderSkills(){
|
||||
const list = SKILLS.filter(n => SKILL_FILTER==='all' ? true : (SKILL_FILTER==='new'?NEW_SKILLS.has(n):skillCat(n)===SKILL_FILTER));
|
||||
document.getElementById('skill-wall').innerHTML = list.map(n=>{
|
||||
const tag = NEW_SKILLS.has(n) ? '<span class="text-[10px] text-[var(--green)]">✨新增</span>' : (skillCat(n)==='research'?'<span class="text-[10px] text-[var(--acc)]">科研</span>':skillCat(n)==='quality'?'<span class="text-[10px] text-[var(--yellow)]">质量</span>':'');
|
||||
return `<div class="skill-card panel rounded-lg p-3 cursor-pointer" onclick="openSkill('${n}')">
|
||||
<div class="text-xl mb-1">${skillEmoji(n)}</div>
|
||||
<div class="text-white text-sm font-semibold mono">gitlink-${n}</div>
|
||||
<div class="mt-1">${tag}</div>
|
||||
<div class="text-[10px] text-[var(--green)] mt-2">📖 SKILL.md →</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
renderSkills();
|
||||
|
||||
// ===== pr-guard 流程 =====
|
||||
const STEPS=[{n:'① 采集',d:'pr +diff/+files',c:'#8b949e'},{n:'② AI Review',d:'分级找问题',c:'#d29922'},{n:'③ CI 检查',d:'ci +builds',c:'#58a6ff'},{n:'④ 汇总评论',d:'api reviews',c:'#a371f7'},{n:'⑤ 质量判定',d:'门禁规则',c:'#3fb950'}];
|
||||
document.getElementById('pr-steps').innerHTML = STEPS.map((s,i)=>`<div class="step panel rounded p-3 text-center flex-1 min-w-[120px]" id="step-${i}"><div class="text-sm font-semibold text-white">${s.n}</div><div class="text-xs mono mt-1" style="color:${s.c}">${s.d}</div></div>${i<STEPS.length-1?'<span class="text-[#6e7681]">→</span>':''}`).join('');
|
||||
function runPipeline(){
|
||||
document.getElementById('pr-verdict').textContent='';
|
||||
STEPS.forEach((_,i)=>document.getElementById('step-'+i).classList.remove('active'));
|
||||
let i=0; const tick=setInterval(()=>{
|
||||
if(i>0) document.getElementById('step-'+(i-1)).classList.remove('active');
|
||||
if(i>=STEPS.length){ clearInterval(tick); document.getElementById('pr-verdict').innerHTML='<span style="color:var(--green)">✅ 质量判定:通过</span> | 0 Critical + CI success → 建议合并'; return; }
|
||||
document.getElementById('step-'+i).classList.add('active'); i++;
|
||||
},600);
|
||||
}
|
||||
async function livePR(){
|
||||
if(!getToken()){ appendOut('⚠️ 请先在顶栏填 token,再用真实 PR 跑','l-err'); return; }
|
||||
document.getElementById('pr-verdict').textContent='⏳ 拉取真实 PR 列表...';
|
||||
try{
|
||||
const r=await fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({cmd:`pr +list --owner ${getOwner()} --repo ${getRepo()} --format json`,token:getToken()})});
|
||||
const d=await r.json();
|
||||
if(d.ok){ document.getElementById('pr-verdict').innerHTML=`<span style="color:var(--acc)">🔌 真实数据:</span>${getOwner()}/${getRepo()} 共 ${d.stdout.includes('"id"')?(d.stdout.match(/"id"/g)||[]).length:'?'} 个 PR(原始 JSON 已在上方终端)`; appendOut('$ pr +list --owner '+getOwner()+' --repo '+getRepo(),'l-cmd'); appendOut(d.stdout||'(空)','l-out'); }
|
||||
else document.getElementById('pr-verdict').textContent='❌ '+d.error;
|
||||
}catch(e){ document.getElementById('pr-verdict').textContent='❌ '+e; }
|
||||
}
|
||||
|
||||
// ===== 雷达图 + 协作图 =====
|
||||
let radarChart = new Chart(document.getElementById('radar'),{
|
||||
type:'radar',
|
||||
data:{labels:['🔁 可复现性','📈 活跃度','📑 引用价值','🤝 协作健康'],datasets:[{label:'示例(点「实拉分析」换真实数据)',data:[6,5,5,6],fill:true,backgroundColor:'rgba(88,166,255,0.18)',borderColor:'#58a6ff',pointBackgroundColor:'#58a6ff'}]},
|
||||
options:{plugins:{legend:{labels:{color:'#c9d1d9',font:{size:11}}}},scales:{r:{min:0,max:10,ticks:{color:'#6e7681',backdropColor:'transparent',stepSize:2},grid:{color:'#30363d'},pointLabels:{color:'#c9d1d9',font:{size:12}},angleLines:{color:'#30363d'}}}}
|
||||
});
|
||||
function drawCollab(contribs, repoName){
|
||||
const svg=document.getElementById('collab-svg'); svg.innerHTML='';
|
||||
const cx=180, cy=130, R=95;
|
||||
// 中心项目节点
|
||||
svg.innerHTML += `<circle cx="${cx}" cy="${cy}" r="24" fill="#1f6feb"/><text x="${cx}" y="${cy+3}" text-anchor="middle" fill="#fff" font-size="9">${(repoName||'repo').slice(0,8)}</text>`;
|
||||
const top = (contribs||[]).slice(0,6);
|
||||
if(!top.length){ svg.innerHTML+='<text x="180" y="250" text-anchor="middle" fill="#6e7681" font-size="10">(点「实拉分析」用真实贡献者重绘)</text>'; return; }
|
||||
const max = top[0].contributions||1;
|
||||
top.forEach((c,i)=>{
|
||||
const a = (-Math.PI/2) + i*(2*Math.PI/top.length);
|
||||
const r = R;
|
||||
const x=cx+Math.cos(a)*r, y=cy+Math.sin(a)*r;
|
||||
const rad = Math.max(8, 18*(c.contributions/max));
|
||||
const core = i<2;
|
||||
svg.innerHTML += `<line x1="${cx}" y1="${cy}" x2="${x}" y2="${y}" stroke="${core?'#3fb950':'#30363d'}" stroke-width="${core?2:1}" stroke-dasharray="${core?'':'4'}"/>`;
|
||||
svg.innerHTML += `<circle cx="${x}" cy="${y}" r="${rad}" fill="${core?'#238636':'#6e7681'}"/>`;
|
||||
svg.innerHTML += `<text x="${x}" y="${y+3}" text-anchor="middle" fill="#fff" font-size="9">${(c.name||'?').slice(0,8)}</text>`;
|
||||
svg.innerHTML += `<text x="${x}" y="${y+rad+12}" text-anchor="middle" fill="#8b949e" font-size="8">${c.perc||''}</text>`;
|
||||
});
|
||||
}
|
||||
drawCollab(null);
|
||||
|
||||
async function analyze(){
|
||||
if(!getToken()){ document.getElementById('analyze-status').textContent='⚠️ 请先填 token'; return; }
|
||||
document.getElementById('analyze-status').textContent='⏳ 采集中...';
|
||||
try{
|
||||
const r=await fetch('/api/analyze',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({owner:getOwner(),repo:getRepo(),token:getToken()})});
|
||||
const d=await r.json();
|
||||
if(!d.ok){ document.getElementById('analyze-status').textContent='❌ '+d.error; return; }
|
||||
const s=d.scores;
|
||||
radarChart.data.datasets[0].label = `${d.owner}/${d.repo}${d.is_fork?'(⚠️ fork of '+d.fork_from+')':''}`;
|
||||
radarChart.data.datasets[0].data = [s.repro, s.activity, s.citation, s.collab];
|
||||
radarChart.options.scales.r.max = 10;
|
||||
radarChart.update();
|
||||
document.getElementById('analyze-status').innerHTML = `✅ 完成 · ${d.contributor_count} 贡献者 · ${d.release_count} release · 协议 ${d.license}`;
|
||||
document.getElementById('repo-meta').innerHTML = `${d.is_fork?'⚠️ <b style="color:var(--yellow)">是 fork</b>(引用应指 upstream '+d.fork_from+')':'✅ 独立仓库'} · 巴士因子 <b style="color:${d.bus_risk==='低'?'var(--green)':d.bus_risk==='中'?'var(--yellow)':'var(--red)'}">${d.bus_factor}%(${d.bus_risk} 风险)</b>`;
|
||||
document.getElementById('repro-detail').innerHTML = '可复现性 '+s.repro+'/'+d.repro_max+':'+d.repro_detail.map(x=>x[1]?'✅'+x[0]:'❌'+x[0]).join(' · ');
|
||||
drawCollab(d.contributors, d.name);
|
||||
}catch(e){ document.getElementById('analyze-status').textContent='❌ '+e; }
|
||||
}
|
||||
|
||||
// 初始提示
|
||||
appendOut('💡 点左上方「命令域」里的动词自动填命令;或在下方输入 gitlink-cli 命令回车。','l-muted');
|
||||
appendOut(' 本地命令(snippet/auth)免登录即可跑;平台命令(repo/issue/pr…)请在顶栏填 token。','l-muted');
|
||||
appendOut(' 下方「Skill 全集」48 个卡片可读 SKILL.md 全文;科研画像区可对目标仓库实拉四维评分。','l-muted');
|
||||
|
||||
// ===== Skill 详情模态框 =====
|
||||
async function openSkill(name){
|
||||
const modal=document.getElementById('skill-modal');
|
||||
document.getElementById('skill-modal-title').textContent='📖 gitlink-'+name;
|
||||
const body=document.getElementById('skill-modal-body');
|
||||
body.innerHTML='<p class="text-[#8b949e]">⏳ 加载 SKILL.md...</p>';
|
||||
modal.classList.remove('hidden');
|
||||
try{
|
||||
const r=await fetch('/api/skill?name='+encodeURIComponent(name));
|
||||
const d=await r.json();
|
||||
if(!d.ok){ body.innerHTML='<p class="l-err">❌ '+d.error+'</p>'; return; }
|
||||
body.innerHTML=marked.parse(d.content);
|
||||
}catch(e){ body.innerHTML='<p class="l-err">❌ '+e+'</p>'; }
|
||||
}
|
||||
function closeSkill(){ document.getElementById('skill-modal').classList.add('hidden'); }
|
||||
document.addEventListener('keydown',e=>{ if(e.key==='Escape') closeSkill(); });
|
||||
</script>
|
||||
|
||||
<!-- Skill 详情模态框 -->
|
||||
<div id="skill-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4" onclick="closeSkill()" style="background:rgba(0,0,0,.75)">
|
||||
<div class="panel rounded-lg max-w-4xl w-full max-h-[88vh] flex flex-col" onclick="event.stopPropagation()">
|
||||
<div class="flex items-center justify-between px-5 py-3 border-b border-[var(--border)]">
|
||||
<h3 id="skill-modal-title" class="text-white font-bold text-lg"></h3>
|
||||
<button onclick="closeSkill()" class="text-[#8b949e] hover:text-white text-3xl leading-none">×</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto text-sm" id="skill-modal-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
gitlink-cli 演示网页 · 后端(零依赖,仅 Python 标准库)
|
||||
作用:接收前端命令 + 访客 token,真跑 gitlink-cli,返回真实输出。
|
||||
启动:python server.py → http://0.0.0.0:$PORT (默认 8000)
|
||||
|
||||
位置:仓库内 demo/web/server.py。CLI 在仓库根 ../../gitlink-cli[.exe]。
|
||||
安全:本地/演示用。已做白名单(只允许 gitlink-cli 子命令)、subprocess 列表参数(不经 shell)、
|
||||
30s 超时。访客 token 仅在请求内存中传给子进程,不写日志、不落盘。
|
||||
"""
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socketserver
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
# Windows GBK 终端兼容
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
PORT = int(os.getenv("PORT", "8000"))
|
||||
HOST = os.getenv("HOST", "0.0.0.0") # 云端需 0.0.0.0;只听本机设 HOST=127.0.0.1
|
||||
ROOT = Path(__file__).resolve().parent # .../demo/web
|
||||
REPO_ROOT = ROOT.parent.parent # 仓库根 .../gitlink-cli
|
||||
|
||||
|
||||
def _find_cli():
|
||||
"""CLI 位置:GITLINK_BIN > 仓库内 > PATH。Linux=gitlink-cli,Windows=gitlink-cli.exe。"""
|
||||
env = os.getenv("GITLINK_BIN")
|
||||
if env and Path(env).exists():
|
||||
return Path(env)
|
||||
for name in ("gitlink-cli", "gitlink-cli.exe"):
|
||||
p = REPO_ROOT / name
|
||||
if p.exists():
|
||||
return p
|
||||
for d in os.getenv("PATH", "").split(os.pathsep):
|
||||
p = Path(d) / "gitlink-cli"
|
||||
if p.exists():
|
||||
return p
|
||||
return REPO_ROOT / "gitlink-cli" # 占位
|
||||
|
||||
|
||||
CLI = _find_cli()
|
||||
CWD = str(REPO_ROOT) # 让 --owner/--repo 可从 git remote 自动解析
|
||||
|
||||
ALLOWED_DOMAINS = { # 全部 30 个顶层域
|
||||
"api", "auth", "branch", "ci", "compare", "config", "dataset", "doctor",
|
||||
"file", "health", "ignore", "issue", "label", "license", "member",
|
||||
"milestone", "org", "pipeline", "pm", "pr", "profile", "release", "repo",
|
||||
"search", "snippet", "user", "version", "webhook", "wiki", "workflow",
|
||||
}
|
||||
NEEDS_TOKEN_DOMAINS = { # 平台域(访客需填 token)
|
||||
"repo", "issue", "pr", "branch", "release", "search", "label", "member",
|
||||
"milestone", "webhook", "wiki", "org", "user", "ci", "compare", "dataset",
|
||||
"health", "license", "pipeline", "pm", "profile", "workflow", "api",
|
||||
}
|
||||
LOCAL_DOMAINS = {"snippet", "auth", "config", "version", "doctor"}
|
||||
|
||||
|
||||
def _run_cli(args, token="", timeout=30):
|
||||
env = dict(os.environ)
|
||||
if token:
|
||||
env["GITLINK_TOKEN"] = token
|
||||
r = subprocess.run(
|
||||
[str(CLI)] + args, capture_output=True, text=True, timeout=timeout,
|
||||
cwd=CWD, encoding="utf-8", errors="replace", env=env,
|
||||
)
|
||||
return r.stdout, r.stderr, r.returncode
|
||||
|
||||
|
||||
def _parse_json(stdout):
|
||||
s = stdout.strip()
|
||||
i, j = s.find("{"), s.rfind("}")
|
||||
if i < 0 or j < 0:
|
||||
return None
|
||||
try:
|
||||
return json.loads(s[i:j + 1])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _license_name(text):
|
||||
t = (text or "").lower()
|
||||
if "mulan" in t: return "Mulan PSL v2"
|
||||
if t.startswith("mit") or "mit license" in t: return "MIT"
|
||||
if "apache" in t: return "Apache 2.0"
|
||||
if "gpl" in t: return "GPL"
|
||||
if "bsd" in t: return "BSD"
|
||||
return "有 LICENSE" if text else "无"
|
||||
|
||||
|
||||
def analyze_repo(owner, repo, token):
|
||||
"""采集 + 按 research-insight 评分表算四维 + 巴士因子。"""
|
||||
def cli(*a):
|
||||
return _run_cli(list(a), token=token, timeout=30)
|
||||
|
||||
info_o, _, _ = cli("repo", "+info", "--owner", owner, "--repo", repo, "--format", "json")
|
||||
info = (_parse_json(info_o) or {}).get("data") or {}
|
||||
|
||||
contrib_o, _, _ = cli("repo", "+contributors", "--owner", owner, "--repo", repo, "--format", "json")
|
||||
contribs = ((_parse_json(contrib_o) or {}).get("data") or {}).get("list") or []
|
||||
contribs_sorted = sorted(contribs, key=lambda c: -(c.get("contributions") or 0))
|
||||
|
||||
rel_o, _, _ = cli("release", "+list", "--owner", owner, "--repo", repo, "--format", "json")
|
||||
releases = ((_parse_json(rel_o) or {}).get("data") or {}).get("releases") or []
|
||||
|
||||
lic_o, _, _ = cli("file", "+get", "--owner", owner, "--repo", repo, "--path", "LICENSE", "--format", "json")
|
||||
lic_text = ""
|
||||
lic_parsed = _parse_json(lic_o)
|
||||
if lic_parsed:
|
||||
d = lic_parsed.get("data") or {}
|
||||
entries = d.get("entries") if isinstance(d, dict) else None
|
||||
if isinstance(entries, dict):
|
||||
lic_text = entries.get("content") or ""
|
||||
elif isinstance(d, str):
|
||||
lic_text = d
|
||||
|
||||
ci_o, _, _ = cli("repo", "+tree", "--owner", owner, "--repo", repo, "--path", ".gitea/workflows", "--format", "json")
|
||||
ci_entries = ((_parse_json(ci_o) or {}).get("data") or {}).get("entries") or []
|
||||
has_ci = bool(ci_entries)
|
||||
|
||||
tree_o, _, _ = cli("repo", "+tree", "--owner", owner, "--repo", repo, "--format", "json")
|
||||
root_files = [str(e.get("name", "")) for e in ((_parse_json(tree_o) or {}).get("data") or {}).get("entries") or []]
|
||||
lock_files = {"go.sum", "package-lock.json", "yarn.lock", "Cargo.lock", "requirements.txt", "poetry.lock", "pom.xml"}
|
||||
has_lock = any(f in lock_files for f in root_files)
|
||||
has_readme = any(f.lower().startswith("readme") for f in root_files)
|
||||
|
||||
# 可复现性(工程类,满分 8:数据项 N/A)
|
||||
repro, repro_detail = 0, []
|
||||
repro += 2 if has_ci else 0; repro_detail.append(("CI 配置", has_ci))
|
||||
repro += 2 if has_lock else 0; repro_detail.append(("依赖锁定", has_lock))
|
||||
repro += 2 if has_readme else 0; repro_detail.append(("运行文档", has_readme))
|
||||
ver = bool(releases or info.get("version_releases_count"))
|
||||
repro += 2 if ver else 0; repro_detail.append(("版本归档", ver))
|
||||
|
||||
n_contrib = len(contribs)
|
||||
activity = min(10, round(n_contrib / 3)) if n_contrib else 2 # 无 commits API,用贡献者规模近似
|
||||
|
||||
citation = 0
|
||||
citation += 3 if lic_text else 0
|
||||
citation += 3 if ver else 0
|
||||
citation += 2 if has_readme else 0
|
||||
citation += 2 if (info.get("fork_info") or {}).get("fork_project_user_login") else 0
|
||||
citation = min(10, citation)
|
||||
|
||||
top_perc = 0.0
|
||||
if contribs_sorted:
|
||||
try:
|
||||
top_perc = float(re.sub(r"[^\d.]", "", str(contribs_sorted[0].get("contribution_perc", "0"))))
|
||||
except Exception:
|
||||
top_perc = 0.0
|
||||
collab = 10 if top_perc < 33 else (6 if top_perc < 50 else 3)
|
||||
|
||||
fork_from = (info.get("fork_info") or {}).get("fork_project_user_login")
|
||||
return {
|
||||
"ok": True, "owner": owner, "repo": repo,
|
||||
"is_fork": bool(fork_from), "fork_from": fork_from,
|
||||
"name": info.get("name", repo),
|
||||
"license": _license_name(lic_text),
|
||||
"contributor_count": n_contrib,
|
||||
"release_count": len(releases),
|
||||
"version_releases_count": info.get("version_releases_count", 0),
|
||||
"contributors": [
|
||||
{"name": c.get("name") or c.get("login") or "?",
|
||||
"contributions": c.get("contributions", 0),
|
||||
"perc": c.get("contribution_perc", "")}
|
||||
for c in contribs_sorted[:8]
|
||||
],
|
||||
"scores": {"repro": repro, "activity": activity, "citation": citation, "collab": collab},
|
||||
"repro_max": 8, "repro_detail": repro_detail,
|
||||
"bus_factor": top_perc,
|
||||
"bus_risk": "低" if top_perc < 33 else ("中" if top_perc < 50 else "高"),
|
||||
}
|
||||
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def _cors(self):
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
def do_OPTIONS(self):
|
||||
self.send_response(204); self._cors(); self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
path = urlparse(self.path).path
|
||||
if path in ("/", "/index.html"):
|
||||
self._serve_file("index.html", "text/html")
|
||||
elif path == "/api/cli":
|
||||
self._json({"ok": True, "cli": str(CLI), "exists": CLI.exists()})
|
||||
elif path == "/api/domains":
|
||||
self._json({"ok": True, "domains": sorted(ALLOWED_DOMAINS), "local": sorted(LOCAL_DOMAINS)})
|
||||
elif path == "/api/health":
|
||||
self._json({"ok": True, "cli_exists": CLI.exists()})
|
||||
elif path == "/api/skill":
|
||||
self._handle_skill()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def _handle_skill(self):
|
||||
q = parse_qs(urlparse(self.path).query)
|
||||
name = (q.get("name") or [""])[0].strip()
|
||||
if not name:
|
||||
self._json({"ok": False, "error": "缺少 ?name="}); return
|
||||
skill_md = REPO_ROOT / "skills" / f"gitlink-{name}" / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
self._json({"ok": False, "error": f"找不到 SKILL.md:gitlink-{name}"}); return
|
||||
self._json({"ok": True, "name": name, "content": skill_md.read_text(encoding="utf-8")})
|
||||
|
||||
def do_POST(self):
|
||||
path = urlparse(self.path).path
|
||||
body = self._read_body()
|
||||
if path == "/api/run":
|
||||
self._handle_run(body)
|
||||
elif path == "/api/analyze":
|
||||
owner = (body.get("owner") or "").strip()
|
||||
repo = (body.get("repo") or "").strip()
|
||||
token = (body.get("token") or "").strip()
|
||||
if not owner or not repo:
|
||||
self._json({"ok": False, "error": "缺少 owner/repo"}); return
|
||||
try:
|
||||
self._json(analyze_repo(owner, repo, token))
|
||||
except subprocess.TimeoutExpired:
|
||||
self._json({"ok": False, "error": "采集超时(>30s)"})
|
||||
except Exception as e:
|
||||
self._json({"ok": False, "error": str(e)})
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def _handle_run(self, body):
|
||||
cmd = (body.get("cmd") or "").strip()
|
||||
token = (body.get("token") or "").strip()
|
||||
if not cmd:
|
||||
self._json({"ok": False, "error": "空命令"}); return
|
||||
args = cmd.split()
|
||||
while args and args[0] in ("gitlink-cli", "gitlink-cli.exe", "./gitlink-cli.exe"):
|
||||
args = args[1:]
|
||||
if not args:
|
||||
self._json({"ok": False, "error": "缺少子命令"}); return
|
||||
domain = args[0]
|
||||
if domain not in ALLOWED_DOMAINS:
|
||||
self._json({"ok": False, "error": f"不允许的命令:{domain}(仅限 gitlink-cli 子命令)"}); return
|
||||
needs_token = domain in NEEDS_TOKEN_DOMAINS
|
||||
try:
|
||||
out, err, code = _run_cli(args, token=token, timeout=30)
|
||||
self._json({
|
||||
"ok": code == 0, "cmd": f"gitlink-cli {' '.join(args)}",
|
||||
"stdout": out, "stderr": err, "code": code,
|
||||
"needs_token": needs_token, "token_provided": bool(token),
|
||||
})
|
||||
except subprocess.TimeoutExpired:
|
||||
self._json({"ok": False, "error": "命令超时(>30s),可能涉及交互输入"})
|
||||
except Exception as e:
|
||||
self._json({"ok": False, "error": str(e)})
|
||||
|
||||
def _read_body(self):
|
||||
length = int(self.headers.get("Content-Length", 0) or 0)
|
||||
raw = self.rfile.read(length) if length else b"{}"
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _json(self, obj):
|
||||
data = json.dumps(obj, ensure_ascii=False).encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self._cors()
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def _serve_file(self, name, mime):
|
||||
p = ROOT / name
|
||||
if not p.exists():
|
||||
self.send_error(404, f"{name} 不存在"); return
|
||||
data = p.read_bytes()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", f"{mime}; charset=utf-8")
|
||||
self._cors()
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
|
||||
class ReuseTCPServer(socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not CLI.exists():
|
||||
print(f"[!] 找不到 gitlink-cli 二进制:{CLI}")
|
||||
print(" 请先编译:cd <仓库根> && go build -o gitlink-cli . (Linux)")
|
||||
print(" 或设环境变量 GITLINK_BIN 指向已有二进制。")
|
||||
with ReuseTCPServer((HOST, PORT), Handler) as httpd:
|
||||
print(f"[OK] gitlink-cli 演示后端已启动:http://{HOST}:{PORT}")
|
||||
print(f" CLI:{CLI}(exists={CLI.exists()})")
|
||||
print(f" 访客在网页顶栏填自己的 GitLink token 即可跑平台命令。Ctrl+C 停止。")
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n已停止")
|
||||
Binary file not shown.
Loading…
Reference in New Issue