关于 issue-fields-migration
用于将 GitHub 元数据批量迁移到组织级议题字段的技能,支持映射仓库标签或 GitHub Project V2 字段值,并在写入前预览及处理冲突。
主要功能
- 01将标签映射到议题字段
- 02复制 Project V2 字段值
- 03建议字段和选项映射
- 04检测标签值冲突
- 05迁移前预览变更
适用场景
- 01用结构化字段替换优先级标签
- 02删除旧字段前转移项目值
- 03字段迁移后清理标签
MCP 相关
使用 GitHub MCP 工具读取项目字段和条目;议题字段的读取与写入则通过 GitHub CLI 和 REST API 命令完成。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill issue-fields-migration安装后在 Claude Code 中输入/issue-fields-migration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/labels-api.mdissue-fields-api.mdprojects-api.md详细介绍
Issue Fields 迁移
Issue fields 是组织级类型化元数据(单选、文本、数字、日期),用结构化、可搜索、跨仓库的字段取代了基于标签的变通方案。每个组织都会预配置 Priority、Effort、Start date 和 Target date,并支持最多 25 个自定义字段。
本技能可从两个来源将现有元数据批量迁移到 issue fields:
- 仓库标签:将
p0、p1、priority/high等标签转换为结构化的 issue field 值(例如 Priority 字段)。支持一次迁移多个标签,并可在迁移后选择是否移除这些标签。 - Project V2 字段:将字段值(单选、文本、数字、日期、迭代)从 GitHub Project 复制到对应的组织级 issue fields。
何时使用
- 用户添加了与现有项目字段重叠的组织级 issue fields
- 用户希望在删除旧的项目字段之前,将项目字段的值复制到 issue fields
- 用户询问如何将项目字段数据“迁移”、“转移”或“复制”到 issue fields
- 用户希望将仓库标签(例如 p0、p1、p2、p3)转换为 issue field 值(例如 Priority 字段)
- 用户询问如何用 issue fields 取代标签,或在采用 issue fields 后清理标签
前提条件
- 目标组织必须已启用 issue fields
- issue fields 必须已在组织级存在
- 对于项目字段迁移:issue fields 必须已添加到该项目
- 对于标签迁移:目标仓库上必须存在这些标签
- 用户必须对相关仓库(以及项目,如果迁移项目字段)具有写入权限
ghCLI 必须已通过身份验证并具有相应权限范围
可用工具
MCP 工具(读取操作)
| 工具 | 用途 |
|---|---|
mcp__github__projects_list | 列出项目字段(list_project_fields),列出带值的项目条目(list_project_items) |
mcp__github__projects_get | 获取特定项目字段或条目的详细信息 |
CLI / REST API
| 操作 | 命令 |
|---|---|
| 列出组织 issue fields | gh api /orgs/{org}/issue-fields -H "X-GitHub-Api-Version: 2026-03-10" |
| 读取 issue field 值 | gh api /repos/{owner}/{repo}/issues/{number}/issue-field-values -H "X-GitHub-Api-Version: 2026-03-10" |
| 写入 issue field 值 | gh api /repositories/{repo_id}/issues/{number}/issue-field-values -X POST -H "X-GitHub-Api-Version: 2026-03-10" --input - |
| 获取仓库 ID | gh api /repos/{owner}/{repo} --jq .id |
| 列出仓库标签 | gh label list -R {owner}/{repo} --limit 1000 --json name,color,description |
| 按标签列出 issue | gh issue list -R {owner}/{repo} --label "{name}" --state all --json number,title,labels --limit 1000 |
| 从 issue 移除标签 | gh api /repos/{owner}/{repo}/issues/{number}/labels/{label_name} -X DELETE |
完整 API 详情请参阅 references/issue-fields-api.md、references/projects-api.md 和 references/labels-api.md。
工作流
步骤 0:迁移来源
询问用户要迁移什么:
-
“您要迁移标签还是项目字段?”
-
如果用户回答标签:
- 询问:“这些标签在哪个组织和哪些仓库中?”
- 询问:“您想迁移哪些标签?”(他们可以指定名称,或说“先给我看看有哪些标签”)
-
如果用户回答项目字段:
- 询问:“您能分享项目的链接,或者告诉我组织名称和项目编号吗?”
- 询问:“您想迁移哪个字段?”
标签迁移流程
当用户希望将仓库标签转换为议题字段值时,使用此流程。标签只能映射到 single_select 议题字段(每个标签名称映射到一个选项值)。
阶段 L1:输入与标签发现
- 询问用户:要迁移的组织名称和仓库。
- 从每个仓库获取标签:
gh label list -R {owner}/{repo} --limit 1000 --json name,color,description
- 获取组织议题字段:
gh api /orgs/{org}/issue-fields \
-H "X-GitHub-Api-Version: 2026-03-10" \
--jq '.[] | {id, name, content_type, options: [.options[]?.name]}'
-
筛选(针对标签数量较多的仓库):如果仓库有 50+ 个标签,按常见前缀(例如
priority-*、team-*、type-*)或颜色分组。让用户通过“显示匹配 priority 的标签”或“显示蓝色标签”来筛选,然后再进行映射。切勿一次性列出 100+ 个标签。 -
询问用户哪些标签映射到哪个议题字段和选项。支持以下模式:
- 单个标签映射到单个字段:例如,标签 "bug" → Type 字段,"Bug" 选项
- 多个标签映射到一个字段(批量):例如,标签 p0、p1、p2、p3 → Priority 字段及匹配的选项
- 多个标签映射到多个字段:例如,p1 → Priority + frontend → Team。作为单独的映射组处理。
-
自动建议映射:对于每个标签,尝试使用以下模式(按顺序)匹配议题字段选项:
- 精确匹配(不区分大小写):标签
Bug→ 选项Bug - 前缀-数字(
{prefix}-{n}→{P}{n}):标签priority-1→ 选项P1 - 去除分隔符(连字符、下划线、空格):标签
good_first_issue→ 选项Good First Issue - 子字符串包含:标签
type: bug→ 选项Bug
一次性呈现所有建议,供用户确认、更正或跳过。
- 精确匹配(不区分大小写):标签
示例输出:
Labels in github/my-repo (showing relevant ones):
p0, p1, p2, p3, bug, enhancement, frontend, backend
Org issue fields (single_select):
Priority: Critical, P0, P1, P2, P3
Type: Bug, Feature, Task
Team: Frontend, Backend, Design
Suggested mappings:
Label "p0" → Priority "P0"
Label "p1" → Priority "P1"
Label "p2" → Priority "P2"
Label "p3" → Priority "P3"
Label "bug" → Type "Bug"
Label "frontend" → Team "Frontend"
Label "backend" → Team "Backend"
Label "enhancement" → (no auto-match; skip or map manually)
Confirm, adjust, or add more mappings?
阶段 L2:冲突检测
在确定标签到选项的映射后,检查冲突。当一个议题有多个标签映射到同一个议题字段时,就会发生冲突(因为 single_select 字段只能保存一个值)。
- 按目标议题字段对标签映射进行分组。
- 对于有多个标签来源的每个字段,注意潜在的冲突。
- 询问用户冲突解决策略:
- 首次匹配:使用找到的第一个匹配标签(按标签映射列表的顺序)
- 跳过:跳过具有冲突标签的议题并报告它们
- 手动:逐个呈现每个冲突,由用户决定
示例:
Potential conflict: labels "p0" and "p1" both map to the Priority field.
If an issue has both labels, which value should win?
Options:
1. First match (use "p0" since it appears first in the mapping)
2. Skip conflicting issues
3. I'll decide case by case
阶段 L3:预检与数据扫描
- 对于每个仓库,验证写入权限并缓存
repository_id:
gh api /repos/{owner}/{repo} --jq '{full_name, id, permissions: .permissions}'
- 对于映射中的每个标签,获取匹配的议题:
gh issue list -R {owner}/{repo} --label "{label_name}" --state all \
--json number,title,labels,type --limit 1000
警告:--limit 1000 会静默截断结果。如果你预计某个标签可能有超过 1000 个议题,请手动分页或先验证总数(例如,gh issue list --label "X" --state all --json number | jq length)。
PR 筛选:gh issue list 同时返回议题和 PR。在 --json 输出中包含 type,如果用户只想迁移议题,则筛选 type == "Issue"。
-
如果所有选定的标签都返回 0 个议题,停止并告知用户。建议:尝试不同的标签、检查拼写或尝试不同的仓库。不要继续进行空迁移。
-
对于多仓库迁移,在所有指定的仓库中重复上述步骤。
-
对于找到的每个议题:
- 检查议题是否已有目标议题字段的值(如果已设置则跳过)。
- 检测多标签冲突(议题有两个标签映射到同一字段)。
- 应用阶段 L2 中选择的冲突解决策略。
- 分类:迁移、跳过(已设置)、跳过(冲突)或跳过(无匹配标签)。
阶段 L4:预览 / 试运行
在任何写入之前呈现摘要。
示例预览:
Label Migration Preview
Source: labels in github/my-repo
Target fields: Priority, Type, Team
| Category | Count |
|-------------------------|-------|
| Issues to migrate | 156 |
| Already set (skip) | 12 |
| Conflicting labels (skip)| 3 |
| Total issues with labels| 171 |
Label breakdown:
"p1" → Priority "P1": 42 issues
"p2" → Priority "P2": 67 issues
"p3" → Priority "P3": 38 issues
"bug" → Type "Bug": 9 issues
Sample changes (first 5):
github/my-repo#101: Priority → "P1"
github/my-repo#203: Priority → "P2", Type → "Bug"
github/my-repo#44: Priority → "P3"
github/my-repo#310: Priority → "P1"
github/my-repo#7: Type → "Bug"
After migration, do you also want to remove the migrated labels from issues? (optional)
Estimated time: ~24s (156 API calls at 0.15s each)
Proceed?
阶段 L5:执行
- 对于每个要迁移的 issue,写入 issue 字段值(与 project 字段迁移使用相同的端点):
echo '{"issue_field_values": [{"field_id": FIELD_ID, "value": "OPTION_NAME"}]}' | \
gh api /repositories/{repo_id}/issues/{number}/issue-field-values \
-X POST \
-H "X-GitHub-Api-Version: 2026-03-10" \
--input -
将 FIELD_ID 替换为整数字段 ID(例如 1),将 OPTION_NAME 替换为选项名称字符串。
- 如果用户选择移除标签,则在字段写入成功后,移除每个已迁移的标签:
gh api /repos/{owner}/{repo}/issues/{number}/labels/{label_name} -X DELETE
对包含空格或特殊字符的标签名进行 URL 编码。
- 节奏控制:调用之间延迟 100ms。遇到 HTTP 429 时使用指数退避(1s、2s、4s,最长 30s)。
- 进度:每 25 项报告一次(例如 "Migrated 75/156 issues...")。
- 错误处理:记录失败但继续执行。将标签移除失败单独记录。
- 最终汇总:
Label Migration Complete
| Result | Count |
|-----------------------|-------|
| Fields set | 153 |
| Labels removed | 153 |
| Skipped | 15 |
| Failed (field write) | 2 |
| Failed (label remove) | 1 |
Failed items:
github/my-repo#501: 403 Forbidden (insufficient permissions)
github/my-repo#88: 422 Validation failed (field not available on repo)
github/my-repo#120: label removal failed (404, label already removed)
Project 字段迁移流程
当用户希望将 GitHub Project V2 字段中的值复制到对应的组织级 issue 字段时,使用此流程。
按顺序执行以下六个阶段。执行前始终先预览。
阶段 P1:输入与发现
- 向用户询问:组织名称和 project 编号(或 project URL)。
- 获取 project 字段:
# Use MCP tool
mcp__github__projects_list(owner: "{org}", project_number: {n}, method: "list_project_fields")
- 获取组织 issue 字段:
gh api /orgs/{org}/issue-fields \
-H "X-GitHub-Api-Version: 2026-03-10" \
--jq '.[] | {id, name, content_type, options: [.options[]?.name]}'
-
过滤代理字段:在 project 上启用 issue 字段后,某些 project 字段会以 "proxy" 条目形式出现,其单选类型的
options: []为空。这些条目镜像了真实的 issue 字段,应被忽略。仅与具有实际选项值的 project 字段进行匹配。 -
按名称(不区分大小写)自动匹配类型兼容的字段:
| Project 字段类型 | Issue 字段类型 | 是否兼容? |
|---|---|---|
| TEXT | text | 是,直接复制 |
| SINGLE_SELECT | single_select | 是,需要选项映射 |
| NUMBER | number | 是,直接复制 |
| DATE | date | 是,直接复制 |
| ITERATION | (无) | 无对应类型;跳过并发出警告 |
- 以表格形式展示建议的字段映射。让用户确认、调整或跳过字段。
示例输出:
Found 3 potential field mappings:
| # | Project Field | Type | Issue Field | Status |
|---|-------------------|---------------|--------------------|------------|
| 1 | Priority (renamed) | SINGLE_SELECT | Priority | Auto-match |
| 2 | Due Date | DATE | Due Date | Auto-match |
| 3 | Sprint | ITERATION | (no equivalent) | Skipped |
Proceed with fields 1 and 2? You can also add manual mappings.
阶段 P2:选项映射(仅限单选字段)
对于每一对匹配的单选字段:
- 比较 project 字段与 issue 字段之间的选项名称(不区分大小写)。
- 自动匹配名称相同的选项。
- 对于任何未映射的 project 字段选项,在单个汇总中展示所有未映射的选项,并要求用户一次性为所有选项提供映射。不要逐条提示;将它们合并为一次交互。
- 展示最终选项映射表以供确认。
示例输出:
Option mapping for "Release - Target":
Auto-matched (case-insensitive):
"GA" → "GA"
"Private Preview" → "Private Preview"
"Public Preview" → "Public Preview"
Unmapped project options (need your input):
1. "Internal Only" → which issue field option? (or skip)
2. "Retired" → which issue field option? (or skip)
3. "Beta" → which issue field option? (or skip)
4. "Deprecated" → which issue field option? (or skip)
Available issue field options not yet mapped: "Internal", "Sunset", "Beta Testing", "End of Life"
Please provide mappings for all 4 options above (e.g., "1→Internal, 2→Sunset, 3→Beta Testing, 4→skip").
阶段 P3:飞行前检查
在扫描条目之前,验证对每个可能被触及的仓库的写入权限:
- 从 project 条目(第一页)中,收集
{owner}/{repo}值的唯一集合。 - 对于每个唯一的仓库,验证已认证用户具有 Issues 写入权限:
gh api /repos/{owner}/{repo} --jq '{full_name, permissions: .permissions}'
- 如果任何仓库显示
push: false或triage: false,则在继续之前警告用户。这些仓库中的条目将在写入时失败。 - 现在缓存每个仓库的
repository_id(整数);你在阶段 P6 中会需要它:
gh api /repos/{owner}/{repo} --jq .id
阶段 P4:数据扫描
- 使用 MCP 获取所有项目条目。重要:对于条目超过约 200 个的项目,
gh api graphql --paginate不可靠(它会拼接 JSON 响应而不添加适当的分隔符,并且可能超时)。请使用在内部处理分页的 MCP 工具,或使用显式的基于游标的分页:
# Preferred: use MCP tool (handles pagination automatically)
mcp__github__projects_list(owner: "{org}", project_number: {n}, method: "list_project_items")
# Fallback for large projects: manual cursor-based pagination
# Fetch 100 items per page, advancing the cursor each time.
# Process each page before fetching the next to avoid memory issues.
# Save progress (page number or last cursor) so you can resume if interrupted.
- 对于每个条目:
- 如果是草稿条目(不是真实 issue),则跳过。
- 提取源项目字段的值。
- 如果源值为空,则跳过。
- 检查该 issue 是否已为目标 issue 字段设置了值:
gh api /repos/{owner}/{repo}/issues/{number}/issue-field-values \
-H "X-GitHub-Api-Version: 2026-03-10"
- 如果该 issue 字段已有值,则跳过(保留现有数据)。
- 将每个条目归入以下类别之一:
- 迁移:有源值,且没有现有的目标值
- 跳过(已设置):目标 issue 字段已有值
- 跳过(无源值):该条目的项目字段为空
- 跳过(草稿):条目是草稿,不是真实 issue
- 跳过(未映射选项):单选值未映射
阶段 P5:预览 / 试运行
在任何写入之前先呈现摘要。
如果用户请求试运行:显示完整的详细报告(每个 issue、其当前值、建议的新值以及跳过原因)并停止。不要执行。
否则(预览模式):显示汇总计数和变更示例,然后请求确认。
示例预览:
Migration Preview for Project #42
Fields to migrate: Priority, Due Date
| Category | Count |
|------------------------|-------|
| Items to migrate | 847 |
| Already set (skip) | 23 |
| No source value (skip) | 130 |
| Draft items (skip) | 12 |
| Total project items | 1,012 |
Sample changes (first 5):
github/repo-a#101: Priority → "High"
github/repo-a#203: Priority → "Medium", Due Date → "2025-03-15"
github/repo-b#44: Priority → "Low"
github/repo-a#310: Due Date → "2025-04-01"
github/repo-c#7: Priority → "Critical"
Estimated time: ~127s (847 API calls at 0.15s each)
Proceed with migration? This will update 847 issues across 3 repositories.
阶段 P6:执行
-
使用在阶段 3 中缓存的
repository_id值。 -
对于每个要迁移的条目,写入 issue 字段值:
echo '{"issue_field_values": [{"field_id": FIELD_ID, "value": "VALUE"}]}' | \
gh api /repositories/{repo_id}/issues/{number}/issue-field-values \
-X POST \
-H "X-GitHub-Api-Version: 2026-03-10" \
--input -
将 FIELD_ID 替换为整数字段 ID(例如 1),将 VALUE 替换为值字符串。
- 节奏控制:在 API 调用之间添加 100ms 延迟。对于 HTTP 429 响应,使用指数退避(1s、2s、4s,最多 30s)。
- 进度:每 25 个条目报告一次状态(例如“Migrated 75/847 items...”)。
- 错误处理:记录失败,但继续处理剩余条目。
- 最终摘要:
Migration Complete
| Result | Count |
|---------|-------|
| Success | 842 |
| Skipped | 165 |
| Failed | 5 |
Failed items:
github/repo-a#501: 403 Forbidden (insufficient permissions)
github/repo-b#88: 422 Validation failed (field not available on repo)
...
重要说明
- 写入端点的小陷阱:用于写入 issue 字段值的 REST API 使用
repository_id(整数),而不是owner/repo。务必先用gh api /repos/{owner}/{repo} --jq .id查询仓库 ID。 - 单选值:REST API 接受选项名称字符串(而非选项 ID)。这使得项目字段和标签的映射都变得直接明了。
- 读回值:从 API 响应中读取 issue 字段值时,使用
.single_select_option.name获取人类可读的值。.value属性返回的是内部选项 ID(例如1201这样的整数),而不是显示名称。 - API 版本标头:所有 issue 字段端点都需要
X-GitHub-Api-Version: 2026-03-10。 - 跨仓库条目:一个项目可以包含来自多个仓库的 issue。按仓库缓存仓库 ID,以避免重复查询。
- 保留现有值:绝不覆盖已设置的 issue 字段值。跳过这些条目。
- 迭代字段:没有对应的 issue 字段。始终警告用户并跳过。
- 草稿条目:未关联真实 issue 的项目条目不能有 issue 字段值。跳过并注明。
- 标签是仓库作用域的:与项目字段不同,标签是按仓库存在的。同一标签名可能存在于多个仓库中;迁移需分别对每个仓库执行。
- 标签冲突:一个 issue 可能有多个标签映射到同一个 single_select 字段。务必在执行前检测并解决这些冲突。
- 标签移除是可选的:迁移后,用户可能希望保留标签作为备份,或将其移除。移除前务必询问。
- 对标签名进行 URL 编码:含有空格或特殊字符的标签在用于 REST API 路径时必须进行 URL 编码(例如
good%20first%20issue)。 - 为规模化生成脚本:对于 100+ 个 issue 的迁移,生成独立的 shell 脚本,而不是通过 Agent 逐个执行 API 调用。这样更快、可恢复,并避免 Agent 超时问题。
- 幂等迁移:重新运行迁移是安全的。已设置目标字段值的 issue 将被跳过。这意味着你可以安全地恢复部分完成的迁移,而不会重复工作。
--limit 1000截断:gh issue list --limit 1000会在 1000 条结果处静默停止。对于 issue 数量更多的标签,使用--jq和基于游标的分页进行分页,或运行多个带过滤条件的查询(例如按日期范围)。- macOS bash 版本:macOS 自带 bash 3.x,不支持
declare -A(关联数组)。生成的脚本应使用 POSIX 兼容的构造,或注明不兼容并建议brew install bash。 - Issue 与 PR 的区别:
gh issue list同时返回 issue 和拉取请求。如果迁移只应针对 issue,请在--json输出中包含type,并过滤type == "Issue"。
示例
示例 1:完整迁移
用户:"我需要把项目中的 Priority 值迁移到新的组织级 Priority issue 字段"
操作:遵循阶段 P1-P6。发现字段、映射选项、检查权限、扫描条目、预览、执行。
示例 2:仅试运行
用户:"给我看看如果从项目 #42 迁移字段会发生什么,但不要真的执行"
操作:仅遵循阶段 P1-P5。呈现包含每个条目在内的完整试运行报告。不要执行。
示例 3:多个字段
用户:"把项目 #15 的 Priority 和 Due Date 迁移到 issue 字段"
操作:相同的工作流,但在一次处理中同时处理两个字段。在数据扫描期间,为每个条目收集所有已映射字段的值。对每个 issue 在一次 API 调用中写入所有字段值。
示例 4:将单个标签迁移到 Issue 字段
用户:"我想把 'bug' 标签迁移到 Type issue 字段"
操作:路由到 Label Migration Flow。询问 org/repo,列出标签,确认映射:标签 "bug" → Type 字段的 "Bug" 选项。扫描带有该标签的 issue,预览,执行。询问迁移后是否删除该标签。
示例 5:将多个标签迁移到一个字段(批量)
用户:"我们有 p0、p1、p2、p3 标签,想把它们转换为 Priority issue 字段"
操作:路由到 Label Migration Flow。将全部四个标签映射到 Priority 字段选项(p0→P0、p1→P1、p2→P2、p3→P3)。检查冲突(同时带有多个优先级标签的 issue)。在一个汇总中预览所有更改。一次性执行。可选地从已迁移的 issue 中删除全部四个标签。
示例 6:跨仓库标签迁移并删除标签
用户:"把 'frontend' 和 'backend' 标签迁移到 Team issue 字段,涉及 github/issues、github/memex 和 github/mobile,然后删除旧标签"
操作:路由到 Label Migration Flow。确认仓库和标签映射:"frontend"→Team "Frontend","backend"→Team "Backend"。扫描全部三个仓库中带有这些标签的 issue。检测冲突(同时带有两个标签的 issue)。跨仓库预览。执行字段写入,然后从已迁移的 issue 中删除标签。按仓库报告统计数据。