diff --git a/README.md b/README.md
index 6db9d89..410fd93 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](https://golang.org)
[](https://www.npmjs.com/package/@gitlink-ai/cli)
-The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/).
+The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans and AI Agents. Supports **macOS, Linux, and Windows**. Covers repository management, issue tracking, pull requests, webhooks, member collaboration, CI/CD, and AI-powered workflows, with 40+ commands and AI Agent [Skills](./skills/README.md).
**[中文文档](./README.zh-CN.md)**
@@ -78,11 +78,19 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
jiangtx
+
+
## Why gitlink-cli?
-- **Agent-Native Design** — Structured [Skills](./skills/) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
+- **Agent-Native Design** — Structured [Skills](./skills/README.md) out of the box, compatible with Claude Code, OpenClaw, and other AI platforms — Agents can operate GitLink with zero extra setup
- **Wide Coverage** — Repository, Issue, PR, Webhook, Member, Branch, Release, CI, Pipeline, Org, Search, and User workflows are covered by high-level commands
- **AI-Friendly & Optimized** — Every command is tested with real Agents, featuring concise parameters, smart defaults, and structured output
- **Cross-Platform** — Runs on macOS, Linux, and Windows (x64/arm64), install via `npm install -g @gitlink-ai/cli` in one command, binary auto-downloaded
@@ -96,7 +104,7 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
| Category | Capabilities |
|----------|-------------|
| 📦 Repo | List, create, fork, delete repositories, view repo info, insights, and interactions |
-| 🐛 Issue | Create, update, close, batch close, comment on issues |
+| 🐛 Issue | Create, update, close, batch close/update/delete, comment on issues |
| 🔖 Label | Create, list, update, delete issue labels |
| 🔀 PR | Create, merge, review pull requests, view changed files |
| 👥 Member | List, add, remove repository members, change roles, create and accept invite links |
@@ -106,8 +114,12 @@ The official [GitLink](https://www.gitlink.org.cn) CLI tool — built for humans
| 🔧 CI | View builds, logs, CI/CD operations |
| ⚙️ Pipeline | Run, inspect, enable, disable, delete pipeline workflows and logs |
| 🔔 Webhook | Manage repo webhooks and test deliveries |
+| 📖 Wiki | List, view, create, update, and delete wiki pages |
+| 🧭 Trace | Initialize code trace analysis, start scans, list results, fetch reports |
| 🔍 Search | Search repositories, users |
+| 📊 Dataset | Query research datasets by project |
| 👤 User | View user profiles and info |
+| 📊 Profile | User ability, role, major, activity, and contribution statistics |
| 📋 PM | Sprint management, kanban boards, weekly reports |
| 🤖 Workflow | AI-powered issue triage, PR review, release notes |
@@ -263,6 +275,46 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
```
+### Wiki Management
+
+```bash
+# List wiki pages (table of contents)
+gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
+
+# View a wiki page by page name
+gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
+
+# Create a wiki page
+gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
+ -n getting-started -t "Getting Started" -c "# Getting Started Guide"
+
+# Update a wiki page title and/or content
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title"
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# Updated content"
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "New Title" -c "New content"
+
+# Delete a wiki page
+gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
+```
+
+### Code Trace Analysis
+
+```bash
+# Initialize code trace analysis for the current account
+gitlink-cli trace +init
+
+# Start a trace scan for a repository branch
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master --dry-run
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master
+
+# List scan results and fetch a generated report
+gitlink-cli trace +results --owner Gitlink --repo forgeplus --page 1 --limit 20
+gitlink-cli trace +report --owner Gitlink --repo forgeplus --task-id 12345
+
+# Re-run analysis for an existing project result
+gitlink-cli trace +rescan --owner Gitlink --repo forgeplus --project-id 67890 --dry-run
+```
+
### Member Management
```bash
@@ -312,22 +364,17 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
# Batch close issues from a CSV file
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
+# Preview batch metadata update by API issue IDs
+# Note: --ids uses API issue IDs, not web URL issue numbers.
+gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --ids 101,102 --status-id 3 --priority-id 2 --dry-run
+
+# Destructive batch delete requires both dry-run first and --yes for real execution
+gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --dry-run
+gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --yes
+
# Add a comment
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "Fixed"
-# Reply to a comment with attachments and mentions
-gitlink-cli issue +comment --owner Gitlink --repo forgeplus --number 123 -b "Thanks, please check the log" --parent-id 456 --reply-id 456 --attachment-ids 7,8 --receivers alice,bob
-
-# List comments only, or include operation records with --category all
-gitlink-cli issue +comments --owner Gitlink --repo forgeplus --number 123 --category comment --keyword fixed
-
-# Update or delete a comment
-gitlink-cli issue +comment-update --owner Gitlink --repo forgeplus --number 123 --comment-id 456 -b "Updated comment"
-gitlink-cli issue +comment-delete --owner Gitlink --repo forgeplus --number 123 --comment-id 456
-
-# List replies under a comment
-gitlink-cli issue +comment-replies --owner Gitlink --repo forgeplus --number 123 --comment-id 456
-
# List issue assigners
gitlink-cli issue +assigners --owner Gitlink --repo forgeplus
@@ -480,6 +527,16 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
```
+### Ignore File Templates
+
+```bash
+# List all available .gitignore templates
+gitlink-cli ignore +list
+
+# Filter templates by name
+gitlink-cli ignore +list --name Go
+```
+
### Search
```bash
@@ -490,6 +547,29 @@ gitlink-cli search +repos -k "machine learning"
gitlink-cli search +users -k "zhangsan"
```
+### User Profile
+
+`profile` surfaces GitLink's native user statistics (ability, role, major, activity,
+contribution). When `--user` is omitted it defaults to the authenticated user.
+
+```bash
+# Development ability scores + language breakdown
+gitlink-cli profile +ability --user zhangsan
+
+# Role positioning / major (discipline) categories
+gitlink-cli profile +role --user zhangsan
+gitlink-cli profile +major --user zhangsan
+
+# Ability within a time window (Unix timestamps)
+gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
+
+# Recent activity (issues / PRs / commits per day) for the current user
+gitlink-cli profile +activity
+
+# Contribution heatmap for a given year
+gitlink-cli profile +contribution --user zhangsan --year 2025
+```
+
### Workflow Agent Commands
`workflow` provides rule-based repository analysis for maintainers and AI Agents. It currently supports:
@@ -498,11 +578,9 @@ gitlink-cli search +users -k "zhangsan"
- `workflow +health`
- `workflow +pr-summary`
- `workflow +repo-report`
-- `workflow +release-notes`
`workflow +pr-summary` defaults to `table` when `--format` is omitted.
`workflow +repo-report` defaults to `markdown` when `--format` is omitted.
-`workflow +release-notes` defaults to `markdown` when `--format` is omitted.
Examples:
@@ -575,12 +653,6 @@ gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format ma
# Repository workflow report from a local JSON file
gitlink-cli workflow +repo-report --from shortcuts/workflow/testdata/repo_report.json --format json
-
-# Release notes from merged pull requests fetched in read-only mode
-gitlink-cli workflow +release-notes --owner Gitlink --repo gitlink-cli --version v1.2.0 --limit 20 --format markdown
-
-# Release notes from a local JSON file
-gitlink-cli workflow +release-notes --from release-prs.json --from-ref v1.1.0 --to-ref v1.2.0 --format json
```
Output formats:
@@ -596,7 +668,32 @@ Safety:
- They do not depend on LLM APIs.
- `workflow +pr-summary` does not comment, approve, reject, or merge pull requests.
- `workflow +repo-report` aggregates health, issue triage, and PR review summary signals without remote writes.
-- `workflow +release-notes` reads merged PR metadata and generates local release notes only.
+
+### Dataset
+
+`dataset` manages and queries GitLink research datasets (title, description,
+paper content, license, owning project).
+
+```bash
+# List datasets for one or more projects (by numeric project ID)
+gitlink-cli dataset +list --ids 5988
+
+# View a repository's dataset and attachments
+gitlink-cli dataset +view --owner Gitlink --repo forgeplus
+
+# Create / update a repository's dataset (preview first with --dry-run)
+gitlink-cli dataset +create --owner me --repo proj -t "My dataset" -d "..." --license-id 359 --dry-run
+gitlink-cli dataset +update --owner me --repo proj -t "My dataset" -d "updated"
+
+# Delete a dataset attachment (destructive: preview, then confirm with --yes)
+gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --dry-run
+gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --yes
+```
+
+> Note: `dataset +list` (platform dataset query) is verified on production
+> gitlink.org.cn. The per-repo `+view`/`+create`/`+update` routes follow the
+> published OpenAPI contract but are not yet deployed on production (they return
+> 404 there); they will work once the platform enables them.
### Raw API
@@ -654,19 +751,20 @@ git push gitlink
The `skills/` directory contains Agent Skill files for AI-automated GitLink operations.
-See [skills/README.md](skills/README.md) for details.
+See [skills/README.md](./skills/README.md) for details.
| Skill | Description |
|-------|-------------|
| `gitlink-shared` | Authentication, global parameters, safety rules, API notes |
| `gitlink-repo` | Repository operations (create, view, delete, fork, insights, etc.) |
-| `gitlink-issue` | Issue operations (create, update, close, comment, etc.) |
+| `gitlink-issue` | Issue operations (create, update, close, batch update/delete, comment, etc.) |
| `gitlink-pr` | Pull request operations (create, merge, review, etc.) |
| `gitlink-member` | Repository member and invite link management |
| `gitlink-branch` | Branch management (create, delete, list, protect, unprotect) |
| `gitlink-release` | Release management (create, edit, update, view, delete, etc.) |
| `gitlink-ci` | CI/CD operations (builds, logs, etc.) |
| `gitlink-pipeline` | Pipeline workflow operations (runs, logs, enable, disable, delete, etc.) |
+| `gitlink-trace` | Code trace analysis (initialize account, start scans, results, reports) |
| `gitlink-search` | Search (repositories, users, etc.) |
| `gitlink-org` | Organization management (members, teams, etc.) |
| `gitlink-user` | User management (profile info, etc.) |
@@ -701,6 +799,7 @@ gitlink-cli/
│ ├── org/ # Organization shortcuts
│ ├── ci/ # CI shortcuts
│ ├── pipeline/ # Pipeline shortcuts
+│ ├── trace/ # Code trace analysis shortcuts
│ ├── search/ # Search shortcuts
│ ├── user/ # User shortcuts
│ └── register.go # Registration entry point
@@ -724,7 +823,7 @@ gitlink-cli/
## Documentation
-- [Skills Guide](skills/README.md) — AI Agent Skills detailed documentation
+- [Skills Guide](./skills/README.md) — AI Agent Skills detailed documentation
- [Design Document](doc/design.md) — Architecture design and development plan
## FAQ
@@ -789,7 +888,7 @@ gitlink-cli uses Windows Credential Manager for secure token storage. If Credent
### Q: Where can I find the full API reference?
-See [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md).
+See [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md).
## License
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 6ee0a8d..d4f84f9 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -5,7 +5,7 @@
[](https://golang.org)
[](https://www.npmjs.com/package/@gitlink-ai/cli)
-[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/)。
+[GitLink(确实开源)](https://www.gitlink.org.cn) 官方 CLI 工具 — 为人类和 AI Agent 双重设计。支持 **macOS、Linux、Windows**,覆盖仓库管理、Issue 追踪、Pull Request、Webhook、成员协作、CI/CD 和 AI 自动化工作流,包含 40+ 命令和 AI Agent [Skills](./skills/README.md)。
**[English](./README.md)**
@@ -78,11 +78,19 @@
jiangtx
+
+
## 为什么选择 gitlink-cli?
-- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/),兼容 Claude Code — Agent 零配置即可操作 GitLink
+- **Agent-Native 设计** — 开箱即用结构化 [Skills](./skills/README.md),兼容 Claude Code — Agent 零配置即可操作 GitLink
- **广泛覆盖** — 仓库、Issue、PR、Webhook、成员、分支、Release、CI、Pipeline、组织、搜索、用户等常用工作流均提供高层命令
- **AI 友好 & 优化** — 每条命令都经过真实 Agent 测试,简洁参数、智能默认值、结构化输出
- **跨平台** — macOS、Linux、Windows (x64/arm64) 全支持,`npm` 一条命令安装
@@ -96,7 +104,7 @@
| 分类 | 能力 |
|------|------|
| 📦 仓库 | 列出、创建、Fork、删除仓库,查看仓库信息、洞察数据和互动状态 |
-| 🐛 Issue | 创建、更新、关闭、批量关闭、评论 Issue |
+| 🐛 Issue | 创建、更新、关闭、批量关闭/更新/删除、评论 Issue |
| 🔖 标签 | 创建、列出、更新、删除 Issue 标签 |
| 🔀 PR | 创建、合并、Review Pull Request,查看变更文件 |
| 👥 成员 | 列出、添加、移除仓库成员,调整角色,生成和接受邀请链接 |
@@ -105,8 +113,12 @@
| 🏢 组织 | 管理组织、成员、团队 |
| 🔧 CI | 查看构建、日志、CI/CD 操作 |
| ⚙️ Pipeline | 运行、查看、启停、删除流水线工作流并查询日志 |
+| 📖 Wiki | 列出、查看、创建、更新、删除 Wiki 页面 |
+| 🧭 代码溯源 | 初始化代码溯源分析、发起扫描、查看结果并获取报告 |
| 🔍 搜索 | 搜索仓库、用户 |
+| 📊 数据集 | 按项目查询科研数据集 |
| 👤 用户 | 查看用户资料和信息 |
+| 📊 画像 | 用户开发能力、角色定位、专业定位、近期活动、贡献热力图统计 |
| 📋 项目管理 | Sprint 管理、看板、周报 |
| 🤖 工作流 | AI 驱动的 Issue 分类、PR Review、Release Notes |
@@ -274,6 +286,46 @@ gitlink-cli webhook +test --owner Gitlink --repo forgeplus --id 68
gitlink-cli webhook +tasks --owner Gitlink --repo forgeplus --id 68
```
+### Wiki 管理
+
+```bash
+# 列出 Wiki 页面(目录结构)
+gitlink-cli wiki +list --owner Gitlink --repo forgeplus --project-id 12345
+
+# 查看 Wiki 页面
+gitlink-cli wiki +view --owner Gitlink --repo forgeplus --project-id 12345 -n home
+
+# 创建 Wiki 页面
+gitlink-cli wiki +create --owner Gitlink --repo forgeplus --project-id 12345 \
+ -n getting-started -t "快速开始" -c "# 快速开始指南"
+
+# 更新 Wiki 页面标题和/或内容
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题"
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -c "# 更新后的内容"
+gitlink-cli wiki +update --owner Gitlink --repo forgeplus --project-id 12345 -n home -t "新标题" -c "新内容"
+
+# 删除 Wiki 页面
+gitlink-cli wiki +delete --owner Gitlink --repo forgeplus --project-id 12345 -n old-page
+```
+
+### 代码溯源分析
+
+```bash
+# 初始化当前账号的代码溯源分析能力
+gitlink-cli trace +init
+
+# 对仓库分支发起代码溯源扫描
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master --dry-run
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master
+
+# 查看扫描结果并获取报告
+gitlink-cli trace +results --owner Gitlink --repo forgeplus --page 1 --limit 20
+gitlink-cli trace +report --owner Gitlink --repo forgeplus --task-id 12345
+
+# 对已有项目结果重新扫描
+gitlink-cli trace +rescan --owner Gitlink --repo forgeplus --project-id 67890 --dry-run
+```
+
### 成员管理
```bash
@@ -323,22 +375,17 @@ gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --numbers 123,12
# 从 CSV 文件批量关闭 Issue
gitlink-cli issue +batch-close --owner Gitlink --repo forgeplus --from issues.csv
+# 按 API issue id 预览批量更新元数据
+# 注意:--ids 是 API issue id,不是网页 URL 中的 Issue 编号。
+gitlink-cli issue +batch-update --owner Gitlink --repo forgeplus --ids 101,102 --status-id 3 --priority-id 2 --dry-run
+
+# 危险批量删除必须先 dry-run,真实执行还要显式 --yes
+gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --dry-run
+gitlink-cli issue +batch-delete --owner Gitlink --repo forgeplus --ids 101,102 --yes
+
# 添加评论
gitlink-cli issue +comment --owner Gitlink --repo forgeplus -i 123 -b "已修复"
-# 回复评论并携带附件和 @ 用户
-gitlink-cli issue +comment --owner Gitlink --repo forgeplus --number 123 -b "请查看日志" --parent-id 456 --reply-id 456 --attachment-ids 7,8 --receivers alice,bob
-
-# 列出评论;需要操作记录时可传 --category all
-gitlink-cli issue +comments --owner Gitlink --repo forgeplus --number 123 --category comment --keyword fixed
-
-# 更新或删除评论
-gitlink-cli issue +comment-update --owner Gitlink --repo forgeplus --number 123 --comment-id 456 -b "更新后的评论"
-gitlink-cli issue +comment-delete --owner Gitlink --repo forgeplus --number 123 --comment-id 456
-
-# 列出评论下的回复
-gitlink-cli issue +comment-replies --owner Gitlink --repo forgeplus --number 123 --comment-id 456
-
# 列出 Issue 负责人
gitlink-cli issue +assigners --owner Gitlink --repo forgeplus
@@ -458,6 +505,16 @@ gitlink-cli pipeline +disable --owner Gitlink --repo forgeplus --id 7 --workflow
gitlink-cli pipeline +delete --owner Gitlink --repo forgeplus --id 7 --dry-run
```
+### 忽略文件模板
+
+```bash
+# 列出所有可用的 .gitignore 模板
+gitlink-cli ignore +list
+
+# 按名称筛选模板
+gitlink-cli ignore +list --name Go
+```
+
### 搜索
```bash
@@ -468,29 +525,50 @@ gitlink-cli search +repos -k "machine learning"
gitlink-cli search +users -k "zhangsan"
```
-### Workflow 自动化
+### 用户画像
-`workflow` 提供只读的规则化分析命令,适合维护者和 AI Agent 在不写入远端数据的情况下整理 Issue、PR、仓库健康度和发布材料。
+`profile` 暴露 GitLink 原生的用户画像统计(开发能力、角色定位、专业定位、近期活动、贡献热力图)。
+省略 `--user` 时默认使用当前认证用户。
```bash
-# 根据本地参数分析 Issue
-gitlink-cli workflow +triage --title "Install failed on Windows" --body "go install failed with error" --format table
+# 开发能力评分 + 语言分布
+gitlink-cli profile +ability --user zhangsan
-# 只读拉取远端数据生成 PR 审查摘要
-gitlink-cli workflow +pr-summary --owner Gitlink --repo gitlink-cli --number 1 --format markdown
+# 角色定位 / 专业(学科)定位
+gitlink-cli profile +role --user zhangsan
+gitlink-cli profile +major --user zhangsan
-# 只读拉取远端数据生成仓库工作流报告
-gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format markdown
+# 指定时间范围的开发能力(Unix 时间戳)
+gitlink-cli profile +ability --user zhangsan --start-time 1704067200 --end-time 1735689600
-# 根据已合并 PR 生成发布说明
-gitlink-cli workflow +release-notes --owner Gitlink --repo gitlink-cli --version v1.2.0 --limit 20 --format markdown
+# 当前用户的近期活动(每日 疑修 / 合并请求 / 提交)
+gitlink-cli profile +activity
-# 根据本地 JSON 生成发布说明
-gitlink-cli workflow +release-notes --from release-prs.json --from-ref v1.1.0 --to-ref v1.2.0 --format json
+# 指定年份的贡献热力图
+gitlink-cli profile +contribution --user zhangsan --year 2025
```
-`workflow +pr-summary` 默认输出 table,`workflow +repo-report` 和 `workflow +release-notes` 默认输出 markdown。当前 workflow 命令只做本地分析或只读拉取,不会评论、审批、关闭、合并或修改远端数据。
+### 数据集
+`dataset` 管理并查询 GitLink 科研数据集(标题、描述、论文内容、许可证、所属项目)。
+
+```bash
+# 按数字项目 ID 列出一个或多个项目的数据集
+gitlink-cli dataset +list --ids 5988
+
+# 查看仓库的数据集及其附件
+gitlink-cli dataset +view --owner Gitlink --repo forgeplus
+
+# 创建 / 更新仓库数据集(先用 --dry-run 预览)
+gitlink-cli dataset +create --owner me --repo proj -t "我的数据集" -d "..." --license-id 359 --dry-run
+gitlink-cli dataset +update --owner me --repo proj -t "我的数据集" -d "更新"
+
+# 删除数据集附件(破坏性:先预览,再用 --yes 确认)
+gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --dry-run
+gitlink-cli dataset +delete-attachment --owner me --repo proj --uuid --yes
+```
+
+> 注意:`dataset +list`(平台数据集查询)已在生产 gitlink.org.cn 验证可用。按仓库的 `+view`/`+create`/`+update` 遵循已发布的 OpenAPI 契约,但生产环境尚未部署(当前返回 404),待平台上线后即可生效。
### Raw API
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:
@@ -547,19 +625,20 @@ git push gitlink
`skills/` 目录包含 Claude Code Agent Skill 文件,支持 AI 自动化操作 GitLink 平台。
-详见 [skills/README.md](skills/README.md)
+详见 [skills/README.md](./skills/README.md)
| Skill | 说明 |
|-------|------|
| `gitlink-shared` | 认证、全局参数、安全规则、API 注意事项 |
| `gitlink-repo` | 仓库操作(创建、查看、删除、Fork、洞察数据等) |
-| `gitlink-issue` | Issue 操作(创建、更新、关闭、评论等) |
+| `gitlink-issue` | Issue 操作(创建、更新、关闭、批量更新/删除、评论等) |
| `gitlink-pr` | Pull Request 操作(创建、合并、Review 等) |
| `gitlink-member` | 仓库成员与邀请链接管理 |
| `gitlink-release` | 发布管理(创建、编辑、更新、查看、删除等) |
| `gitlink-org` | 组织管理(成员、团队等) |
| `gitlink-ci` | CI/CD 操作(构建、日志等) |
| `gitlink-pipeline` | 流水线工作流操作(运行、日志、启停、删除等) |
+| `gitlink-trace` | 代码溯源分析(初始化、发起扫描、查看结果、获取报告) |
| `gitlink-search` | 搜索功能(仓库、用户等) |
| `gitlink-user` | 用户管理(个人信息等) |
| `gitlink-pm` | 项目管理(Sprint、看板、周报等) |
@@ -592,6 +671,7 @@ gitlink-cli/
│ ├── org/ # 组织 shortcuts
│ ├── ci/ # CI shortcuts
│ ├── pipeline/ # Pipeline shortcuts
+│ ├── trace/ # 代码溯源 shortcuts
│ ├── search/ # 搜索 shortcuts
│ ├── user/ # 用户 shortcuts
│ └── register.go # 注册入口
@@ -615,7 +695,7 @@ gitlink-cli/
## 文档
-- [Skills 使用指南](skills/README.md) — AI Agent Skills 详细说明
+- [Skills 使用指南](./skills/README.md) — AI Agent Skills 详细说明
- [设计文档](doc/design.md) — 架构设计和开发计划
## 常见问题
@@ -678,7 +758,7 @@ gitlink-cli 使用 Windows Credential Manager 安全存储 Token。如果 Creden
### Q: 如何查看完整的 API 参考?
-查看 [skills/gitlink-shared/REFERENCE.md](skills/gitlink-shared/REFERENCE.md)
+查看 [skills/gitlink-shared/references/api-reference.md](./skills/gitlink-shared/references/api-reference.md)
## 许可证
diff --git a/doc/changes/trace-shortcuts.md b/doc/changes/trace-shortcuts.md
new file mode 100644
index 0000000..8e4f1a6
--- /dev/null
+++ b/doc/changes/trace-shortcuts.md
@@ -0,0 +1,13 @@
+# Trace Shortcuts
+
+新增 `trace` Shortcut 组,封装 GitLink 代码溯源分析相关接口,支持:
+
+- `trace +init`
+- `trace +results`
+- `trace +start`
+- `trace +rescan`
+- `trace +report`
+
+该能力覆盖账号初始化、按分支发起分析、分页查看分析结果、按项目重新扫描以及获取分析报告。`trace +start` 和 `trace +rescan` 支持 `--dry-run`,便于在自动化脚本或 AI Agent 执行前预览实际请求,降低误触发平台任务的风险。
+
+同时补充了单元测试、README 示例、AI Agent Skill 文档和 i18n 文案,确保命令帮助、中文/英文提示和自动化使用场景保持一致。
diff --git a/internal/i18n/locales/en-US.json b/internal/i18n/locales/en-US.json
index 44afd07..b7e6b85 100644
--- a/internal/i18n/locales/en-US.json
+++ b/internal/i18n/locales/en-US.json
@@ -104,6 +104,12 @@
"cmd.search.repos.short": "Search repositories",
"cmd.search.short": "Search operations",
"cmd.search.users.short": "Search users",
+ "cmd.trace.init.short": "Initialize code trace analysis account",
+ "cmd.trace.report.short": "Fetch a code trace analysis report",
+ "cmd.trace.rescan.short": "Rescan a code trace analysis result",
+ "cmd.trace.results.short": "List code trace analysis results",
+ "cmd.trace.short": "Code trace analysis operations",
+ "cmd.trace.start.short": "Start code trace analysis for a branch",
"cmd.user.info.short": "Show user profile",
"cmd.user.me.short": "Show current authenticated user",
"cmd.user.short": "User operations",
@@ -244,6 +250,9 @@
"flag.search.keyword": "Search keyword",
"flag.sort_by": "Sort field",
"flag.sort_direction": "Sort direction: asc, desc",
+ "flag.trace.branch": "Branch name to analyze",
+ "flag.trace.project_id": "project_id from trace analysis results",
+ "flag.trace.task_id": "task_id from trace analysis results",
"flag.user": "User login (default: current user)",
"flag.user.login": "User login name",
"flag.webhook.active": "Whether the webhook is active: true or false",
diff --git a/internal/i18n/locales/zh-CN.json b/internal/i18n/locales/zh-CN.json
index beb0233..795d747 100644
--- a/internal/i18n/locales/zh-CN.json
+++ b/internal/i18n/locales/zh-CN.json
@@ -104,6 +104,12 @@
"cmd.search.repos.short": "搜索仓库",
"cmd.search.short": "搜索操作",
"cmd.search.users.short": "搜索用户",
+ "cmd.trace.init.short": "初始化代码溯源分析账号",
+ "cmd.trace.report.short": "获取代码溯源分析报告",
+ "cmd.trace.rescan.short": "重新扫描代码溯源分析结果",
+ "cmd.trace.results.short": "列出代码溯源分析结果",
+ "cmd.trace.short": "代码溯源分析操作",
+ "cmd.trace.start.short": "对指定分支发起代码溯源分析",
"cmd.user.info.short": "显示用户资料",
"cmd.user.me.short": "显示当前认证用户",
"cmd.user.short": "用户操作",
@@ -244,6 +250,9 @@
"flag.search.keyword": "搜索关键词",
"flag.sort_by": "排序字段",
"flag.sort_direction": "排序方向:asc、desc",
+ "flag.trace.branch": "要分析的分支名称",
+ "flag.trace.project_id": "代码溯源分析结果中的 project_id",
+ "flag.trace.task_id": "代码溯源分析结果中的 task_id",
"flag.user": "用户登录名(默认:当前用户)",
"flag.user.login": "用户登录名",
"flag.webhook.active": "Webhook 是否启用:true 或 false",
diff --git a/shortcuts/register.go b/shortcuts/register.go
index e0f06b8..7c8be0c 100644
--- a/shortcuts/register.go
+++ b/shortcuts/register.go
@@ -15,9 +15,7 @@ import (
"github.com/gitlink-org/gitlink-cli/shortcuts/label"
"github.com/gitlink-org/gitlink-cli/shortcuts/license"
"github.com/gitlink-org/gitlink-cli/shortcuts/member"
- "github.com/gitlink-org/gitlink-cli/shortcuts/messagesetting"
"github.com/gitlink-org/gitlink-cli/shortcuts/milestone"
- "github.com/gitlink-org/gitlink-cli/shortcuts/notification"
"github.com/gitlink-org/gitlink-cli/shortcuts/org"
"github.com/gitlink-org/gitlink-cli/shortcuts/pipeline"
"github.com/gitlink-org/gitlink-cli/shortcuts/pr"
@@ -25,6 +23,7 @@ import (
"github.com/gitlink-org/gitlink-cli/shortcuts/release"
"github.com/gitlink-org/gitlink-cli/shortcuts/repo"
"github.com/gitlink-org/gitlink-cli/shortcuts/search"
+ "github.com/gitlink-org/gitlink-cli/shortcuts/trace"
"github.com/gitlink-org/gitlink-cli/shortcuts/user"
"github.com/gitlink-org/gitlink-cli/shortcuts/webhook"
"github.com/gitlink-org/gitlink-cli/shortcuts/wiki"
@@ -38,57 +37,55 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
tr = translators[0]
}
groups := map[string][]*common.Shortcut{
- "repo": repo.Shortcuts(tr),
- "issue": issue.Shortcuts(tr),
- "label": label.Shortcuts(),
- "license": license.Shortcuts(),
- "member": member.Shortcuts(),
- "message-settings": messagesetting.Shortcuts(tr),
- "milestone": milestone.Shortcuts(),
- "notification": notification.Shortcuts(tr),
- "pipeline": pipeline.Shortcuts(),
- "pr": pr.Shortcuts(tr),
- "profile": profile.Shortcuts(tr),
- "release": release.Shortcuts(tr),
- "branch": branch.Shortcuts(tr),
- "org": org.Shortcuts(tr),
- "user": user.Shortcuts(tr),
- "search": search.Shortcuts(tr),
- "ci": ci.Shortcuts(tr),
- "compare": compare.Shortcuts(),
- "dataset": dataset.Shortcuts(tr),
- "webhook": webhook.Shortcuts(tr),
- "wiki": wiki.Shortcuts(),
- "health": health.Shortcuts(tr),
- "ignore": ignore.Shortcuts(),
- "workflow": workflow.Shortcuts(),
+ "repo": repo.Shortcuts(tr),
+ "issue": issue.Shortcuts(tr),
+ "label": label.Shortcuts(),
+ "license": license.Shortcuts(),
+ "member": member.Shortcuts(),
+ "milestone": milestone.Shortcuts(),
+ "pipeline": pipeline.Shortcuts(),
+ "pr": pr.Shortcuts(tr),
+ "profile": profile.Shortcuts(tr),
+ "release": release.Shortcuts(tr),
+ "branch": branch.Shortcuts(tr),
+ "org": org.Shortcuts(tr),
+ "user": user.Shortcuts(tr),
+ "search": search.Shortcuts(tr),
+ "ci": ci.Shortcuts(tr),
+ "compare": compare.Shortcuts(),
+ "dataset": dataset.Shortcuts(tr),
+ "webhook": webhook.Shortcuts(tr),
+ "wiki": wiki.Shortcuts(),
+ "health": health.Shortcuts(tr),
+ "ignore": ignore.Shortcuts(),
+ "trace": trace.Shortcuts(tr),
+ "workflow": workflow.Shortcuts(),
}
descriptions := map[string]string{
- "repo": tr.T("cmd.repo.short"),
- "issue": tr.T("cmd.issue.short"),
- "label": "Issue label operations",
- "license": "License operations",
- "member": "Repository member operations",
- "message-settings": tr.T("cmd.message_settings.short"),
- "milestone": "Milestone operations",
- "notification": tr.T("cmd.notification.short"),
- "pipeline": "Pipeline operations",
- "pr": tr.T("cmd.pr.short"),
- "profile": tr.T("cmd.profile.short"),
- "release": tr.T("cmd.release.short"),
- "branch": tr.T("cmd.branch.short"),
- "org": tr.T("cmd.org.short"),
- "user": tr.T("cmd.user.short"),
- "search": tr.T("cmd.search.short"),
- "ci": tr.T("cmd.ci.short"),
- "compare": "Compare branches, tags, or commits",
- "dataset": tr.T("cmd.dataset.short"),
- "webhook": tr.T("cmd.webhook.short"),
- "wiki": "Wiki page management",
- "health": "Project health data collection",
- "ignore": tr.T("cmd.ignore.short"),
- "workflow": "AI agent workflow analysis",
+ "repo": tr.T("cmd.repo.short"),
+ "issue": tr.T("cmd.issue.short"),
+ "label": "Issue label operations",
+ "license": "License operations",
+ "member": "Repository member operations",
+ "milestone": "Milestone operations",
+ "pipeline": "Pipeline operations",
+ "pr": tr.T("cmd.pr.short"),
+ "profile": tr.T("cmd.profile.short"),
+ "release": tr.T("cmd.release.short"),
+ "branch": tr.T("cmd.branch.short"),
+ "org": tr.T("cmd.org.short"),
+ "user": tr.T("cmd.user.short"),
+ "search": tr.T("cmd.search.short"),
+ "ci": tr.T("cmd.ci.short"),
+ "compare": "Compare branches, tags, or commits",
+ "dataset": tr.T("cmd.dataset.short"),
+ "webhook": tr.T("cmd.webhook.short"),
+ "wiki": "Wiki page management",
+ "health": "Project health data collection",
+ "ignore": tr.T("cmd.ignore.short"),
+ "trace": tr.T("cmd.trace.short"),
+ "workflow": "AI agent workflow analysis",
}
for name, shortcuts := range groups {
diff --git a/shortcuts/register_test.go b/shortcuts/register_test.go
index a127c5f..f38624b 100644
--- a/shortcuts/register_test.go
+++ b/shortcuts/register_test.go
@@ -13,8 +13,8 @@ func TestRegisterAll(t *testing.T) {
expectedGroups := []string{
"repo", "issue", "label", "license", "pr", "profile", "release", "branch",
"org", "user", "search", "ci", "workflow",
- "compare", "member", "message-settings", "milestone", "pipeline", "webhook",
- "dataset", "health", "ignore", "wiki", "notification",
+ "compare", "member", "milestone", "pipeline", "webhook",
+ "dataset", "health", "ignore", "wiki", "trace",
}
groupSet := map[string]bool{}
diff --git a/shortcuts/trace/trace.go b/shortcuts/trace/trace.go
new file mode 100644
index 0000000..305b2df
--- /dev/null
+++ b/shortcuts/trace/trace.go
@@ -0,0 +1,176 @@
+package trace
+
+import (
+ "fmt"
+ "net/url"
+ "strings"
+
+ "github.com/gitlink-org/gitlink-cli/internal/i18n"
+ "github.com/gitlink-org/gitlink-cli/shortcuts/common"
+)
+
+// Shortcuts returns code trace analysis shortcuts.
+func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
+ tr := shortcutTranslator(translators...)
+ return []*common.Shortcut{
+ {
+ Name: "init",
+ Description: tr.T("cmd.trace.init.short"),
+ Run: func(ctx *common.RuntimeContext) error {
+ env, err := ctx.CallAPI("POST", "/api/traces/trace_users", nil)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+ },
+ },
+ {
+ Name: "results",
+ Description: tr.T("cmd.trace.results.short"),
+ Flags: []common.Flag{
+ {Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
+ {Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "15"},
+ },
+ Run: func(ctx *common.RuntimeContext) error {
+ if err := ctx.ResolveOwnerRepo(); err != nil {
+ return err
+ }
+ q := url.Values{}
+ setQueryIfPresent(q, "page", ctx.Arg("page"))
+ setQueryIfPresent(q, "limit", ctx.Arg("limit"))
+ env, err := ctx.CallAPIWithQuery("GET", traceRepoPath(ctx)+"/task_results", q)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+ },
+ },
+ {
+ Name: "start",
+ Description: tr.T("cmd.trace.start.short"),
+ Flags: []common.Flag{
+ {Name: "branch", Short: "b", Usage: tr.T("flag.trace.branch"), Required: true},
+ {Name: "dry-run", Usage: tr.T("flag.dry_run"), Bool: true, Default: "false"},
+ },
+ Run: runStart,
+ },
+ {
+ Name: "rescan",
+ Description: tr.T("cmd.trace.rescan.short"),
+ Flags: []common.Flag{
+ {Name: "project-id", Usage: tr.T("flag.trace.project_id"), Required: true},
+ {Name: "dry-run", Usage: tr.T("flag.dry_run"), Bool: true, Default: "false"},
+ },
+ Run: runRescan,
+ },
+ {
+ Name: "report",
+ Description: tr.T("cmd.trace.report.short"),
+ Flags: []common.Flag{
+ {Name: "task-id", Usage: tr.T("flag.trace.task_id"), Required: true},
+ },
+ Run: func(ctx *common.RuntimeContext) error {
+ if err := ctx.ResolveOwnerRepo(); err != nil {
+ return err
+ }
+ taskID, err := ctx.RequireArg("task-id")
+ if err != nil {
+ return err
+ }
+ q := url.Values{}
+ q.Set("task_id", taskID)
+ env, err := ctx.CallAPIWithQuery("GET", traceRepoPath(ctx)+"/task_pdf", q)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+ },
+ },
+ }
+}
+
+func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
+ if len(translators) > 0 && translators[0] != nil {
+ return translators[0]
+ }
+ return i18n.Default()
+}
+
+func runStart(ctx *common.RuntimeContext) error {
+ if err := ctx.ResolveOwnerRepo(); err != nil {
+ return err
+ }
+ branch, err := requiredTrimmedArg(ctx, "branch")
+ if err != nil {
+ return err
+ }
+ payload := map[string]interface{}{"branch_name": branch}
+ path := traceRepoPath(ctx) + "/tasks"
+ if ctx.Arg("dry-run") == "true" {
+ return ctx.OutputData(traceDryRun(ctx, "POST", path, payload, nil))
+ }
+ env, err := ctx.CallAPI("POST", path, payload)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+}
+
+func runRescan(ctx *common.RuntimeContext) error {
+ if err := ctx.ResolveOwnerRepo(); err != nil {
+ return err
+ }
+ projectID, err := requiredTrimmedArg(ctx, "project-id")
+ if err != nil {
+ return err
+ }
+ path := traceRepoPath(ctx) + "/reload_task"
+ q := url.Values{}
+ q.Set("project_id", projectID)
+ if ctx.Arg("dry-run") == "true" {
+ return ctx.OutputData(traceDryRun(ctx, "GET", path, nil, q))
+ }
+ env, err := ctx.CallAPIWithQuery("GET", path, q)
+ if err != nil {
+ return err
+ }
+ return ctx.Output(env)
+}
+
+func traceRepoPath(ctx *common.RuntimeContext) string {
+ return fmt.Sprintf("/api/traces/%s/%s", ctx.Owner, ctx.Repo)
+}
+
+func requiredTrimmedArg(ctx *common.RuntimeContext, name string) (string, error) {
+ value, err := ctx.RequireArg(name)
+ if err != nil {
+ return "", err
+ }
+ value = strings.TrimSpace(value)
+ if value == "" {
+ return "", fmt.Errorf("required flag --%s is missing", name)
+ }
+ return value, nil
+}
+
+func traceDryRun(ctx *common.RuntimeContext, method, path string, payload interface{}, query url.Values) map[string]interface{} {
+ result := map[string]interface{}{
+ "repository": fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
+ "dry_run": true,
+ "method": method,
+ "path": path,
+ }
+ if payload != nil {
+ result["payload"] = payload
+ }
+ if len(query) > 0 {
+ result["query"] = query.Encode()
+ }
+ return result
+}
+
+func setQueryIfPresent(q url.Values, key, value string) {
+ if strings.TrimSpace(value) != "" {
+ q.Set(key, strings.TrimSpace(value))
+ }
+}
diff --git a/shortcuts/trace/trace_test.go b/shortcuts/trace/trace_test.go
new file mode 100644
index 0000000..b9e08e4
--- /dev/null
+++ b/shortcuts/trace/trace_test.go
@@ -0,0 +1,200 @@
+package trace
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gitlink-org/gitlink-cli/internal/client"
+ "github.com/gitlink-org/gitlink-cli/shortcuts/common"
+)
+
+func runTraceShortcut(t *testing.T, server *httptest.Server, name string, args map[string]string) error {
+ t.Helper()
+ s := findTraceShortcut(t, name)
+ ctx := &common.RuntimeContext{
+ Client: &client.Client{
+ HTTP: server.Client(),
+ BaseURL: server.URL,
+ },
+ Owner: "owner",
+ Repo: "repo",
+ Format: "json",
+ Args: args,
+ }
+ if ctx.Args == nil {
+ ctx.Args = map[string]string{}
+ }
+ return s.Run(ctx)
+}
+
+func findTraceShortcut(t *testing.T, name string) *common.Shortcut {
+ t.Helper()
+ for _, s := range Shortcuts() {
+ if s.Name == name {
+ return s
+ }
+ }
+ t.Fatalf("shortcut %q not found", name)
+ return nil
+}
+
+func writeJSON(t *testing.T, w http.ResponseWriter, value interface{}) {
+ t.Helper()
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(value); err != nil {
+ t.Fatalf("write response: %v", err)
+ }
+}
+
+func decodeJSON(t *testing.T, r *http.Request) map[string]interface{} {
+ t.Helper()
+ var payload map[string]interface{}
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ t.Fatalf("decode request: %v", err)
+ }
+ return payload
+}
+
+func TestTraceInit(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ t.Fatalf("expected POST, got %s", r.Method)
+ }
+ if r.URL.Path != "/api/traces/trace_users.json" {
+ t.Fatalf("unexpected path: %s", r.URL.Path)
+ }
+ writeJSON(t, w, map[string]interface{}{"status": float64(0), "message": "success"})
+ }))
+ defer server.Close()
+
+ if err := runTraceShortcut(t, server, "init", nil); err != nil {
+ t.Fatalf("init failed: %v", err)
+ }
+}
+
+func TestTraceResults(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ t.Fatalf("expected GET, got %s", r.Method)
+ }
+ if r.URL.Path != "/api/traces/owner/repo/task_results.json" {
+ t.Fatalf("unexpected path: %s", r.URL.Path)
+ }
+ if r.URL.Query().Get("page") != "2" {
+ t.Fatalf("expected page=2, got %s", r.URL.Query().Get("page"))
+ }
+ if r.URL.Query().Get("limit") != "50" {
+ t.Fatalf("expected limit=50, got %s", r.URL.Query().Get("limit"))
+ }
+ writeJSON(t, w, map[string]interface{}{"data": []interface{}{map[string]interface{}{"task_id": "task-1"}}})
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "results", map[string]string{"page": "2", "limit": "50"})
+ if err != nil {
+ t.Fatalf("results failed: %v", err)
+ }
+}
+
+func TestTraceStart(t *testing.T) {
+ var payload map[string]interface{}
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ t.Fatalf("expected POST, got %s", r.Method)
+ }
+ if r.URL.Path != "/api/traces/owner/repo/tasks.json" {
+ t.Fatalf("unexpected path: %s", r.URL.Path)
+ }
+ payload = decodeJSON(t, r)
+ writeJSON(t, w, map[string]interface{}{"status": float64(0), "message": "success"})
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "start", map[string]string{"branch": " feature/scan "})
+ if err != nil {
+ t.Fatalf("start failed: %v", err)
+ }
+ if payload["branch_name"] != "feature/scan" {
+ t.Fatalf("unexpected branch_name: %#v", payload["branch_name"])
+ }
+}
+
+func TestTraceStartDryRunDoesNotCallAPI(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("dry-run should not call API: %s %s", r.Method, r.URL.Path)
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "start", map[string]string{"branch": "main", "dry-run": "true"})
+ if err != nil {
+ t.Fatalf("start dry-run failed: %v", err)
+ }
+}
+
+func TestTraceStartRejectsBlankBranch(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("blank branch should not call API")
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "start", map[string]string{"branch": " "})
+ if err == nil {
+ t.Fatal("expected blank branch error, got nil")
+ }
+}
+
+func TestTraceRescan(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ t.Fatalf("expected GET, got %s", r.Method)
+ }
+ if r.URL.Path != "/api/traces/owner/repo/reload_task.json" {
+ t.Fatalf("unexpected path: %s", r.URL.Path)
+ }
+ if r.URL.Query().Get("project_id") != "project-1" {
+ t.Fatalf("unexpected project_id: %s", r.URL.Query().Get("project_id"))
+ }
+ writeJSON(t, w, map[string]interface{}{"status": float64(0), "message": "success"})
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "rescan", map[string]string{"project-id": " project-1 "})
+ if err != nil {
+ t.Fatalf("rescan failed: %v", err)
+ }
+}
+
+func TestTraceRescanDryRunDoesNotCallAPI(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatalf("dry-run should not call API: %s %s", r.Method, r.URL.Path)
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "rescan", map[string]string{"project-id": "project-1", "dry-run": "true"})
+ if err != nil {
+ t.Fatalf("rescan dry-run failed: %v", err)
+ }
+}
+
+func TestTraceReport(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ t.Fatalf("expected GET, got %s", r.Method)
+ }
+ if r.URL.Path != "/api/traces/owner/repo/task_pdf.json" {
+ t.Fatalf("unexpected path: %s", r.URL.Path)
+ }
+ if r.URL.Query().Get("task_id") != "task-1" {
+ t.Fatalf("unexpected task_id: %s", r.URL.Query().Get("task_id"))
+ }
+ writeJSON(t, w, map[string]interface{}{"status": float64(0), "message": "success"})
+ }))
+ defer server.Close()
+
+ err := runTraceShortcut(t, server, "report", map[string]string{"task-id": "task-1"})
+ if err != nil {
+ t.Fatalf("report failed: %v", err)
+ }
+}
diff --git a/skills/README.md b/skills/README.md
index 931e498..d33ba4b 100644
--- a/skills/README.md
+++ b/skills/README.md
@@ -103,8 +103,8 @@ skills/
│ └── SKILL.md # Pipeline 操作指南
├── gitlink-wiki/ # Wiki 页面管理
│ └── SKILL.md # Wiki 操作指南
-├── gitlink-notification/ # 用户消息
-│ └── SKILL.md # 消息查询、已读和删除指南
+├── gitlink-trace/ # 代码溯源分析
+│ └── SKILL.md # 代码溯源分析操作指南
├── gitlink-pm/ # 项目管理
│ └── SKILL.md # PM 操作指南
├── gitlink-health/ # 项目健康度分析
@@ -146,7 +146,7 @@ skills/
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
| **gitlink-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
-| **gitlink-notification** | 用户消息 | `notification +list`, `notification +read`, `notification +delete` |
+| **gitlink-trace** | 代码溯源分析 | `trace +init`, `trace +start`, `trace +results`, `trace +report` |
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
diff --git a/skills/gitlink-trace/SKILL.md b/skills/gitlink-trace/SKILL.md
new file mode 100644
index 0000000..e1884cb
--- /dev/null
+++ b/skills/gitlink-trace/SKILL.md
@@ -0,0 +1,52 @@
+---
+name: gitlink-trace
+version: 1.0.0
+description: "代码溯源分析:初始化账号、发起分支扫描、查看分析结果、重新扫描并获取报告。"
+metadata:
+ requires:
+ bins: ["gitlink-cli"]
+ cliHelp: "gitlink-cli trace --help"
+---
+
+# gitlink-trace(代码溯源分析)
+
+Read [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) first for authentication, global flags, owner/repo resolution, and output formatting.
+
+**CRITICAL**: `trace +start` and `trace +rescan` trigger platform-side analysis jobs. Use `--dry-run` first when the user is not explicitly asking to start or rerun a scan.
+
+## Shortcuts
+
+| Shortcut | 说明 |
+|----------|------|
+| `trace +init` | 初始化当前账号的代码溯源分析能力 |
+| `trace +results` | 查看仓库代码溯源分析结果,支持 `--page` 和 `--limit` |
+| `trace +start` | 对指定分支发起代码溯源分析 |
+| `trace +rescan` | 按 `project_id` 重新扫描已有代码溯源结果 |
+| `trace +report` | 按 `task_id` 获取代码溯源分析报告 |
+
+## Usage
+
+```bash
+# 初始化当前账号
+gitlink-cli trace +init
+
+# 查看最近的分析结果
+gitlink-cli trace +results --owner Gitlink --repo forgeplus --page 1 --limit 20 --format json
+
+# 先预览,再对 master 分支发起扫描
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master --dry-run
+gitlink-cli trace +start --owner Gitlink --repo forgeplus --branch master
+
+# 重新扫描已有结果
+gitlink-cli trace +rescan --owner Gitlink --repo forgeplus --project-id 67890 --dry-run
+gitlink-cli trace +rescan --owner Gitlink --repo forgeplus --project-id 67890
+
+# 获取分析报告
+gitlink-cli trace +report --owner Gitlink --repo forgeplus --task-id 12345 --format json
+```
+
+## Agent Notes
+
+- Prefer `--format json` when consuming result IDs for a later `trace +report` or `trace +rescan` command.
+- Treat `task_id` and `project_id` as platform-provided identifiers from `trace +results`; do not invent them.
+- If owner/repo are omitted, the CLI can resolve them from the current Git remote, but explicit flags are safer in automation.