项
项目看板强约束
作者:鹿Sir开发工具v1
把 GitHub Projects 看板作为项目唯一事实源,提供基于缓存的看板校验门禁、状态流转规则与状态查询函数,未入板、未设状态或非法流转的工作一律拦截。用 GitHub Projects 管理任务、执行议题驱动开发、强制团队遵循看板状态机、优化 gh CLI API 调用成本时使用。当用户提到项目看板、issue 状态流转、GitHub Projects、看板门禁、任务状态管理时触发。触发词:项目看板、GitHub Projects、状态流转、门禁校验、issue 管理。
下载量
400
点赞
98
价格
免费
技能文档
---
name: majiayu000-project-board-enforcement-troykelly-claude-skills
title: 项目看板强约束
category: 开发工具
description: 把 GitHub Projects 看板作为项目唯一事实源,提供基于缓存的看板校验门禁、状态流转规则与状态查询函数,未入板、未设状态或非法流转的工作一律拦截。用 GitHub Projects 管理任务、执行议题驱动开发、强制团队遵循看板状态机、优化 gh CLI API 调用成本时使用。当用户提到项目看板、issue 状态流转、GitHub Projects、看板门禁、任务状态管理时触发。触发词:项目看板、GitHub Projects、状态流转、门禁校验、issue 管理。
---
# 项目看板强约束
GitHub Projects 看板是所有工作状态的唯一事实源。不是标签,不是评论,不是记忆——是看板。
**核心原则**:没有以正确字段进入项目看板的工作,即视为不存在。
## 技能工作流
### 步骤1:初始化环境与缓存
先确认项目环境变量已配置:
```bash
echo $GITHUB_PROJECT # 完整 URL: https://github.com/users/USER/projects/N
echo $GITHUB_PROJECT_NUM # 仅编号: N
echo $GH_PROJECT_OWNER # 属主: @me 或组织名
```
任一缺失时先补齐,否则停止。随后初始化读缓存(后续所有读操作 0 API 调用):
```bash
init_project_cache() {
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
export GH_CACHE_FIELDS=$(gh project field-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
export GH_PROJECT_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.project.id // empty')
export GH_STATUS_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .id')
# 缓存 Status 各选项 ID
for opt in Backlog Ready "In Progress" "In Review" Done Blocked; do
local id=$(echo "$GH_CACHE_FIELDS" | jq -r ".fields[] | select(.name == \"Status\") | .options[] | select(.name == \"$opt\") | .id")
case "$opt" in
"Backlog") export GH_STATUS_BACKLOG_ID="$id" ;;
"Ready") export GH_STATUS_READY_ID="$id" ;;
"In Progress") export GH_STATUS_IN_PROGRESS_ID="$id" ;;
"In Review") export GH_STATUS_IN_REVIEW_ID="$id" ;;
"Done") export GH_STATUS_DONE_ID="$id" ;;
"Blocked") export GH_STATUS_BLOCKED_ID="$id" ;;
esac
done
}
```
### 步骤2:检查项目字段配置
每个项目必须配置以下字段,缺失时先建字段再开工:
| 字段 | 类型 | 必选值 |
|-------|------|-----------------|
| Status | 单选 | Backlog, Ready, In Progress, In Review, Done, Blocked |
| Type | 单选 | Feature, Bug, Chore, Research, Spike, Epic, Initiative |
| Priority | 单选 | Critical, High, Medium, Low |
推荐字段:
| 字段 | 类型 | 用途 |
|-------|------|--------|
| Verification | 单选 | Not Verified, Failing, Partial, Passing |
| Criteria Met | 数字 | 已完成的验收标准数 |
| Criteria Total | 数字 | 验收标准总数 |
| Last Verified | 日期 | 最近一次验证时间 |
| Epic | 文本 | 父 Epic 议题编号 |
| Initiative | 文本 | 父 Initiative 议题编号 |
### 步骤3:开工前门禁校验
**规则:每个 issue、epic、initiative 必须先入项目看板,才允许开工。这不是建议,是硬性门禁。**
校验议题已入板(读缓存,0 API 调用):
```bash
verify_issue_in_project() {
local issue=$1
ITEM_ID=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue) | .id")
if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then
echo "BLOCKED: Issue #$issue is not in the project board."
echo ""
echo "Add it with:"
echo " gh project item-add $GITHUB_PROJECT_NUM --owner $GH_PROJECT_OWNER --url \$(gh issue view $issue --json url -q .url)"
return 1
fi
echo "$ITEM_ID"
return 0
}
```
校验状态字段已设置(读缓存,0 API 调用):
```bash
verify_status_set() {
local issue=$1
local item_id=$2
STATUS=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.id == \"$item_id\") | .status.name")
if [ -z "$STATUS" ] || [ "$STATUS" = "null" ]; then
echo "BLOCKED: Issue #$issue has no Status set in project board."
return 1
fi
echo "$STATUS"
return 0
}
```
议题创建后加入看板(1 次写调用 + 刷新缓存):
```bash
add_issue_to_project() {
local issue_url=$1
gh project item-add "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --url "$issue_url"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to add issue to project."
return 1
fi
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
local issue_num=$(echo "$issue_url" | grep -oE '[0-9]+$')
ITEM_ID=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .id")
echo "$ITEM_ID"
return 0
}
```
### 步骤4:状态流转
合法流转路径:
```text
Backlog → Ready → In Progress → In Review → Done
↓ ↓ ↓ ↓
└────────┴──────────┴────────────┴──→ Blocked
↓
(回到前一状态)
```
流转合法性校验:
```bash
validate_transition() {
local current=$1
local target=$2
case "$current→$target" in
"Backlog→Ready"|"Ready→In Progress"|"In Progress→In Review"|"In Review→Done")
return 0 ;;
*"→Blocked")
return 0 ;;
"Blocked→Backlog"|"Blocked→Ready"|"Blocked→In Progress")
return 0 ;;
*)
echo "INVALID_TRANSITION: $current → $target"
return 1 ;;
esac
}
```
更新状态(用缓存 ID,1 次写调用):
```bash
set_project_status() {
local item_id=$1
local new_status=$2
local option_id
case "$new_status" in
"Backlog") option_id="$GH_STATUS_BACKLOG_ID" ;;
"Ready") option_id="$GH_STATUS_READY_ID" ;;
"In Progress") option_id="$GH_STATUS_IN_PROGRESS_ID" ;;
"In Review") option_id="$GH_STATUS_IN_REVIEW_ID" ;;
"Done") option_id="$GH_STATUS_DONE_ID" ;;
"Blocked") option_id="$GH_STATUS_BLOCKED_ID" ;;
*)
option_id=$(echo "$GH_CACHE_FIELDS" | jq -r ".fields[] | select(.name == \"Status\") | .options[] | select(.name == \"$new_status\") | .id")
;;
esac
if [ -z "$option_id" ] || [ "$option_id" = "null" ]; then
echo "ERROR: Status '$new_status' not found in project."
return 1
fi
gh project item-edit --project-id "$GH_PROJECT_ID" --id "$item_id" \
--field-id "$GH_STATUS_FIELD_ID" --single-select-option-id "$option_id"
return $?
}
```
设置 Type 字段(创建议题时调用,1 次写调用):
```bash
set_project_type() {
local item_id=$1
local type=$2
local type_field_id=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Type") | .id')
local option_id=$(echo "$GH_CACHE_FIELDS" | jq -r ".fields[] | select(.name == \"Type\") | .options[] | select(.name == \"$type\") | .id")
if [ -z "$option_id" ] || [ "$option_id" = "null" ]; then
echo "ERROR: Type '$type' not found in project."
return 1
fi
gh project item-edit --project-id "$GH_PROJECT_ID" --id "$item_id" \
--field-id "$type_field_id" --single-select-option-id "$option_id"
}
```
### 步骤5:状态查询(全部读缓存,0 API 调用)
```bash
# 按状态取议题号
get_issues_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.status.name == \"$status\") | .content.number"
}
# 按类型取议题号
get_issues_by_type() {
local type=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.type.name == \"$type\") | .content.number"
}
# 取 Epic 的子议题
get_epic_children() {
local epic_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.epic == \"#$epic_num\") | .content.number"
}
# 按状态计数
count_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq "[.items[] | select(.status.name == \"$status\")] | length"
}
```
### 步骤6:分支与看板同步核验
对比 git 分支与看板状态发现漂移:有分支的议题应处于 In Progress 或 In Review;In Progress 的议题应有活跃分支。
```bash
status=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue) | .status.name")
```
## 门禁点
以下工作流节点必须执行看板校验:
| 工作流节点 | 门禁动作 |
|----------------|------|
| 任何工作开始前 | 校验议题已入板 |
| 议题创建后 | 加入看板并设置字段 |
| 开工时 | Status → In Progress |
| 创建分支 | 校验看板成员资格 |
| PR 创建后 | Status → In Review |
| 工作完成 | Status → Done |
| 遇阻 | Status → Blocked |
| Epic 创建后 | 入板并设 Type=Epic |
| 子议题创建后 | 入板并关联父级 |
## 标签 vs 看板
- **错误**:用标签管状态(`status:in-progress`)
- **正确**:用看板 Status 字段管状态
标签只承载补充信息:`epic`、`epic-[name]`、`spawned-from:#N`、`review-finding`。
## 错误消息
所有看板错误都给出可操作的修复方式:
| 错误码 | 含义 | 修复 |
|------------|---------|-----|
| NOT_IN_PROJECT | 议题未入板 | `gh project item-add ...` |
| NO_STATUS | Status 未设置 | 更新 Status 字段 |
| INVALID_TRANSITION | 非法状态变更 | 使用合法流转路径 |
| PROJECT_NOT_FOUND | 项目不可访问 | 核对 GITHUB_PROJECT_NUM |
## 过关检查清单
通过任一门禁前逐项确认:
- [ ] GitHub API 缓存已初始化(GH_CACHE_ITEMS、GH_CACHE_FIELDS 已设置)
- [ ] 议题已入板(从缓存确认,不调 API)
- [ ] Status 字段已设置
- [ ] Type 字段已设置
- [ ] Priority 字段已设置(新议题)
- [ ] Epic 关联已设置(作为子议题时)
- [ ] 状态变更走合法流转
## API 成本对比
| 操作 | 无缓存 | 有缓存 |
|-----------|----------------|---------------|
| verify_issue_in_project | 1 次 | 0 次 |
| verify_status_set | 1 次 | 0 次 |
| add_issue_to_project | 2 次 | 2 次 |
| set_project_status | 4 次 | 1 次 |
| set_project_type | 3 次 | 1 次 |
| get_issues_by_status | 1 次 | 0 次 |
| count_by_status | 1 次 | 0 次 |
| verify_project_sync(10 分支) | 10 次 | 0 次 |使用说明
# 项目看板强约束 把 GitHub Projects 看板作为唯一事实源:未入板不开工、状态流转强校验、读操作全走缓存零 API 成本。 ## 使用 ```text 帮我检查这个仓库的 issue 是否都已入板、状态字段是否完整 ``` ```text 把 #42 议题加入项目看板并流转到 In Progress ``` ## 工作原理 先用 `init_project_cache` 一次性拉取看板条目与字段定义到环境变量缓存,此后所有读操作(入板校验、状态查询、计数、Epic 子项)0 API 调用;写操作(入板、状态/类型变更)各 1 次调用。内置 Backlog→Ready→In Progress→In Review→Done(旁路 Blocked)状态机校验与分支-看板漂移核验,任一门禁不通过即阻断并输出修复命令。
支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手