--- description: 'Use this skill when the user needs to manage Gitea projects/orgs: listing or creating issues (获取/创建工单), milestones (查看/创建/管理里程碑), labels, getting org members/people (获取人员), repo info/topics, or creating and managing kanban boards (创建和管理看板). Triggers on PineSound repo governance, Gitea API scripts, board/project operations, or "查工单/建工单/看板/里程碑". Provides ready-to-run scripts gitea_ops.py (API) and board_ops.py (Playwright for boards, which have no REST API in Gitea 1.26).' name: gitea-project-manage --- # Gitea 项目管理操作(gitea-project-manage) ## 概述 对 Gitea 组织/仓库做项目治理操作的完整工具箱:**工单(issue)、标签(label)、里程碑(milestone)、人员(组织成员)、组织/仓库信息、看板(project board)**。 一次写好的两个脚本,避免每次重复手写 curl/网页操作: | 脚本 | 定位 | 依赖 | |---|---|---| | `scripts/gitea_ops.py` | API 操作:工单/标签/里程碑/成员/组织/仓库(**看板除外**) | Python 3.10+ 标准库,零第三方依赖 | | `scripts/board_ops.py` | **看板网页操作**:新建/改名/删除看板、列管理、工单进看板、卡片拖拽、完成列关单 | playwright + chromium | > ⚠️ **为什么拆两个脚本**:Gitea 1.26 的看板(Projects)**没有 REST API**(swagger 里 300 个端点完全没有 project 相关),工单/标签/里程碑有 API 但看板必须走浏览器 DOM。这是版本事实,不是配置问题。 ## 环境与凭据 | 条目 | 内容 | |---|---| | Gitea Web | 内网 `http://192.168.1.3:10082`(API 前缀 `/api/v1`)· 外网 `code.infoepoch.cn` | | 组织 | `PineSound`(可 `--org` 覆盖) | | API Token | `/app/working.secret/gitea_token`(chmod 600,管理员权限,**不提交 git、不出现在文档**) | | 网页登录 | Gitea 1.26 登录页**无 CSRF**,直接表单提交;凭据用 `GITEA_USER` / `GITEA_PASS` 环境变量传入(**不写明文进脚本/文档**) | | SSH push | `ssh -i ~/.ssh/id_ed25519_gitea -p 2222 git@192.168.1.3`(Gitea 用户 Luci,可认证) | ## 快速开始 ```bash # gitea_ops.py:token 自动从 /app/working.secret/gitea_token 读取,直接跑 python3 scripts/gitea_ops.py org members python3 scripts/gitea_ops.py issue list PineSoundServer --state open # board_ops.py:首次需 playwright 环境 pip install playwright && playwright install chromium GITEA_USER=Pine GITEA_PASS='<密码>' python3 scripts/board_ops.py boards PineSoundServer # 登录态 cookie 缓存到 /tmp/gitea_board_state.json,之后同目录复用;密码只从环境变量读 ``` ## gitea_ops.py(API 操作) 全局参数:`--org <组织>`(默认 PineSound)、`--json`(原始 JSON,**必须放在子命令前**,如 `gitea_ops.py --json issue get ...`)。 ### 组织与人员 ```bash python3 gitea_ops.py config # 当前配置 python3 gitea_ops.py org repos # 组织仓库列表(名称/公私/描述) python3 gitea_ops.py org members # 组织成员(人员),含邮箱 ``` ### 工单(issue) ```bash python3 gitea_ops.py issue list [--state open|closed|all] [--milestone 标题] [--label 标签名] [--assignee 用户名] [--limit N] python3 gitea_ops.py issue get python3 gitea_ops.py issue create --title "标题" [--body] [--labels "标签1,标签2"] [--milestone "Beta-V1.1"] [--assignee "YangYi,Luci"] [--deadline 2026-08-31] python3 gitea_ops.py issue edit [--title] [--body] [--labels "标签1" | ""] [--assignee "用户" | ""] [--milestone 标题|none] [--state open|closed] [--deadline YYYY-MM-DD|none] python3 gitea_ops.py issue close [--comment "原因"] # 同理 reopen python3 gitea_ops.py issue comment --body "评论内容" python3 gitea_ops.py issue delete --yes # 危险,不可恢复 ``` 关键事实(实测): - `--labels` / `--milestone` / `--assignee` 都支持**名称**(自动解析 id),不必查 id。 - **换标签必须走 `PUT /issues/{index}/labels`**:`PATCH /issues/{index}` 里的 `labels` 字段**不生效**(实测 201 但标签不变),gitea_ops.py 内部已处理。 - 工单列表自动过滤 PR(issue 对象带 `"pull_request": null` 键,直接判断会把 PR 也算进来)。 - 关闭后工单仍在里程碑统计内,`--comment` 会顺带加关闭说明(团队习惯:为什么关)。 ### 里程碑(milestone) ```bash python3 gitea_ops.py milestone list [--state all|open|closed] python3 gitea_ops.py milestone create --title "Beta-V1.1" [--description] [--deadline 2026-08-31] python3 gitea_ops.py milestone edit [--title] [--description] [--deadline] [--state] python3 gitea_ops.py milestone delete --yes ``` ### 标签(label) 默认仓库级;加 `--org-level` 变组织级(组织级标签所有仓库共享)。 ```bash python3 gitea_ops.py label list # 仓库级 python3 gitea_ops.py label list --org-level # 组织级 python3 gitea_ops.py label create --name "🐛 缺陷(Bug)" --color "#d73a4a" [--description] python3 gitea_ops.py label edit [--name] [--color] [--description] python3 gitea_ops.py label delete --yes ``` ### 仓库信息 ```bash python3 gitea_ops.py repo info # 描述/topics/头像/默认分支 python3 gitea_ops.py repo topics --topics backend,api,go python3 gitea_ops.py repo avatar /path/logo.png ``` ## board_ops.py(看板网页操作) Gitea 1.26 看板无 API,用 Playwright 驱动真实浏览器。**依赖**:`pip install playwright && playwright install chromium`(浏览器在 `/usr/bin/chromium`)。 **登录**:`GITEA_USER` / `GITEA_PASS` 环境变量(或 `--user/--pass`)。登录态存 `/tmp/gitea_board_state.json` + profile 目录 `/tmp/gitea_board_profile`,同目录复跑不再要密码(换机器/清 /tmp 后重新登录)。调试可见模式:`BOARD_HEADED=1`。 ```bash python3 board_ops.py boards # 列出所有看板 + 每列(id/名/卡片数) python3 board_ops.py create --name "开发看板" # 新建(基础看板模板,4 列) python3 board_ops.py rename --board 3 --name "新名字" python3 board_ops.py delete --board 3 --yes python3 board_ops.py add-column --board 3 --name "🚧 测试列" python3 board_ops.py rename-column --board 3 --column 10 --name "新列名" # column 支持 id 或当前名 python3 board_ops.py add-issue --issue 8 --board 3 # 工单进看板(进"默认列"= 星标列) python3 board_ops.py move --issue 8 --board 3 --to "🛠 开发中" # 卡片拖拽(自动两步法) python3 board_ops.py done-close --board 3 --done "✅ 完成" # 完成列所有工单批量关闭(内部调 gitea_ops.py) ``` ## 关键经验(血泪教训,勿改) 1. **工单进看板必须 DOM click**:`fetch/requests` 模拟 `POST /issues/projects`、`/issues/milestone` 返回 `{"ok":true}` 但**实际不生效**(Gitea 无 CSRF meta、cookie httpOnly、API 不暴露 project 字段)。必须打开工单详情页 → 点侧栏「项目」下拉 → 点项目项。 2. **拖拽用两步法**:跨多列一次性拖拽不稳定,先拖到中间列再拖到目标列(脚本已自动)。 3. **JS 派发的事件无效**(`isTrusted=false`),必须 Playwright/浏览器真实鼠标点击与拖拽。 4. **卡片默认 `draggable=false`**,进看板页后先刷新一次再拖(脚本已处理)。 5. **列选择器用 `.project-column[data-id="X"]`**;`#board_X .ui.cards` 是后代选择器永远匹配不到(`#board_X` 本身就是 `.ui.cards`)。 6. **登录页按钮没有 `type=submit`**,用 `button.ui.primary.button` 定位。 7. **`wait_for_load_state("networkidle")` 会超时**(Gitea 有长轮询),一律用 `"load"` + 固定等待。 8. **移到「✅ 完成」列 = 顺手关闭工单**(治理惯例,根治"干完忘关");`done-close` 实现批量。 9. 标签体系(PineSound 标准):中文名 + 括号英文,如 `🐛 缺陷(Bug)`;打标签规则:新功能类🟠高、维护优化类🟡中、bug 加🐛缺陷。 ## 典型工作流(新需求从提出到上板) ```bash # 1. 建工单(带标签/里程碑/指派) python3 gitea_ops.py issue create PineSoundServer --title "音频向量化接口" \ --labels "✨ 新功能(Feature),🟠 高(High),🧠 AI算法(AI Algorithm)" \ --milestone "Beta-V1.1" --assignee YangYi # 2. 进看板(默认列=待办) python3 board_ops.py add-issue PineSoundServer --issue --board 3 # 3. 开发中 python3 board_ops.py move PineSoundServer --issue --board 3 --to "🛠 开发中" # 4. dev 测试 / 合并 main python3 board_ops.py move PineSoundServer --issue --board 3 --to "🧪 dev 测试" python3 board_ops.py move PineSoundServer --issue --board 3 --to "🚀 已合并 main" # 5. 完成 → 拖到 ✅ 完成 + 关单(两步可以合并:done-close) python3 board_ops.py move PineSoundServer --issue --board 3 --to "✅ 完成" python3 gitea_ops.py issue close PineSoundServer --comment "合入 main,关闭" ``` ## 失败模式与恢复 | 现象 | 原因 | 处理 | |---|---|---| | `issue edit --labels` 后标签没变 | PATCH 的 labels 字段不生效 | 已修复为 PUT /labels;确认在最新脚本 | | `board_ops.py` 登录报 Timeout | 按钮选择器/networkidle | 更新到最新脚本;手动用浏览器验证登录页结构 | | 拖拽后卡片没动 | 卡片 draggable=false 或一次跨太多列 | 脚本已刷新+两步法;仍失败可开 `BOARD_HEADED=1` 人工看 | | add-issue 找不到项目下拉 | 工单详情页结构变化(Gitea 升级) | 打开工单页找 `.ui.dropdown.full-width` 里 strong 文本 ==「项目」 | | 404 on org repos | BASE 少了 `/api/v1` | `GITEA_BASE` 必须带 `/api/v1` | | 看板列 id 与名称 | 列 id 不稳定(不同看板不同) | 一律用 `boards` 子命令先查 id,或用列名(脚本按名匹配) | ## 维护说明 - 脚本自带 `python3 -m py_compile` 可快速验证语法。 - 若 Gitea 升级导致 DOM 变化,用 Playwright/浏览器 DevTools 重新抓取选择器(参考:列 `.project-column[data-id]`、列名 `.project-column-title-text`、列菜单 `.project-column-header .ui.dropdown.tw-p-1`、创建列表单 `#project-column-title-input` + `.project-column-button-save`、看板删除 `button[data-url*="/projects/{id}/delete"]`、确认按钮 `.ui.primary.ok.button`)。 - 经验文档 `docs/gitea-project-governance.md` 记录了 PineSound 四仓库治理现状(里程碑/看板列/标签全量清单),动治理前先读。