feat(cli): unify gh issue and pr crud

This commit is contained in:
Codex
2026-05-20 12:52:58 +00:00
parent 4b65f713fa
commit b8cc847e48
6 changed files with 397 additions and 58 deletions
+4 -4
View File
@@ -31,10 +31,10 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
- `artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service` 管理 D601 host-managed CNCF Distribution registry 的声明、安装、只读检查和 pull-only artifact CD。该 registry 固定为 D601 loopback `127.0.0.1:5000`,由 systemd + Docker Compose 管理,位于 native k3s 故障域外;`deploy-service` 只拉取 CI 已发布的 commit-pinned 镜像、retag/recreate 或导入 native k3s,并做 live commit 验证,不构建 runtime source。`deploy-backend-core` 是 deprecated 兼容名,标准 backend-core prod CD 入口是 `deploy apply --env prod --service backend-core`。长期规则见 `docs/reference/artifact-registry.md`
- `gh auth status [--repo owner/name]` 探测 GitHub 操作前置条件并输出脱敏 JSON:是否存在 `gh` binary、是否存在 `GH_TOKEN`/`GITHUB_TOKEN` 或可用 `gh auth token` fallback、REST API 是否可达、目标 repo 是否可见、issue 是否可读。degraded reason 必须归类为 `missing-binary``missing-token``auth-failed``network-proxy-failed``permission-denied``repo-not-found``repo-forbidden``issue-not-found``pr-not-found``scope-insufficient``validation-failed``invalid-response``unsupported-command`,不得打印 token;失败对象必须包含 `runnerDisposition=infra-blocked|business-failed`runner 应优先用该字段分流。
- `gh issue list [--state open|closed|all] [--limit N] [--repo owner/name] [--json number,title,state,url,updatedAt,createdAt,author,labels]` 通过 GitHub REST 列出 issue,默认 `state=open``limit=30`,输出稳定 JSON 且不依赖系统 `gh` binary。`--limit` 会映射到 GitHub `per_page` 并限制返回数量,避免一次拉爆上下文;未知 state 或未知 `--json` 字段必须结构化失败并带 `runnerDisposition=business-failed`。GitHub issues API 可能混入 PRCLI 会从 `.data.issues` 中过滤 pull request。
- `gh issue view <number> [--repo owner/name] [--json body,title,state,comments]` 通过 GitHub REST 读取 issue title/body/state/url 和 comments,默认输出 JSON;兼容旧脚本的 `--json body``--json body,title,state,comments` 字段选择,且正文仍稳定暴露在 `.data.issue.body`,避免调用方因为 JSON 路径变化把空值当成正文。字段白名单是 `body,title,state,comments,number,url,author,createdAt,updatedAt`,未知字段必须结构化失败并带 `runnerDisposition=business-failed``gh issue create --title <title> --body-file <file> [--dry-run]``gh issue edit <number> --body-file <file> [--title ...] [--dry-run]``gh issue comment <number> --body-file <file> [--dry-run]``gh issue close|reopen <number> [--dry-run]` 都走 REST,不依赖 `gh` binary。
- `gh issue edit <number> --body-file <file>` 默认拒绝字面量 `null`、空白正文和过短正文;只有真实需要写短正文时才允许显式加 `--allow-short-body`,返回 JSON 会报告该风险。#20 总看板和 #24 指挥简报是长期 body-only issue`--body-profile auto` 会按 issue number 自动启用结构 guard#20 必须包含 `## 看板(OPEN`#24 必须包含 `## 常驻观察与长期建议`;也可显式使用 `--body-profile code-queue-board|commander-brief``--dry-run` 不 PATCH GitHub,输出新正文长度、SHA、关键标题检查结果、字面量 `\n`、反引号、Markdown 表格和 shell 污染信号;若环境里有 `GH_TOKEN``GITHUB_TOKEN`,dry-run 还会只读抓取旧正文长度、SHA 和 `updatedAt` 作为更新前对照。正式写入可带 `--expect-updated-at <updated_at>``--expect-body-sha <sha256>`,CLI 会先读当前 issue,匹配后才 PATCH,防止旧缓存覆盖新正文。
- `gh issue view <number> [--repo owner/name] [--json body,title,state,comments]` 通过 GitHub REST 读取 issue title/body/state/url 和 comments,默认输出 JSON;兼容旧脚本的 `--json body``--json body,title,state,comments` 字段选择,且正文仍稳定暴露在 `.data.issue.body`,避免调用方因为 JSON 路径变化把空值当成正文。字段白名单是 `body,title,state,comments,number,url,author,createdAt,updatedAt`,未知字段必须结构化失败并带 `runnerDisposition=business-failed``gh issue create --title <title> --body-file <file> [--dry-run]``gh issue update <number> --mode replace|append --body-file <file> [--title ...] [--dry-run]``gh issue comment create <number> --body-file <file> [--dry-run]``gh issue comment delete <commentId> [--dry-run]``gh issue close|reopen <number> [--dry-run]` 都走 REST,不依赖 `gh` binary。`gh issue delete <number>` 是结构化 `unsupported-command`,因为 GitHub REST 不支持 issue 硬删除;生命周期删除语义请使用 `close`
- `gh issue update <number> --mode replace|append --body-file <file>` 是正文更新主入口,`edit` 保留为兼容别名。`replace` 用文件正文替换现有 body`append` 先读取当前 body,再按 UTF-8 文件字节追加,保留真实换行、反引号和 Markdown 表格。更新默认拒绝字面量 `null`、空白正文和过短正文;只有真实需要写短正文时才允许显式加 `--allow-short-body`,返回 JSON 会报告该风险。#20 总看板和 #24 指挥简报是长期 body-only issue`--body-profile auto` 会按 issue number 自动启用结构 guard#20 必须包含 `## 看板(OPEN`#24 必须包含 `## 常驻观察与长期建议`;也可显式使用 `--body-profile code-queue-board|commander-brief``--dry-run` 不 PATCH GitHub,输出新正文长度、SHA、关键标题检查结果、字面量 `\n`、反引号、Markdown 表格和 shell 污染信号;若环境里有 `GH_TOKEN``GITHUB_TOKEN`,dry-run 还会只读抓取旧正文长度、SHA 和 `updatedAt` 作为更新前对照。正式写入可带 `--expect-updated-at <updated_at>``--expect-body-sha <sha256>`,CLI 会先读当前 issue,匹配后才 PATCH,防止旧缓存覆盖新正文。
- `gh issue edit 24 --body-file <file> --notify-claudeqq-brief-diff [--dry-run]` 是指挥简报 #24 的通知入口。正式执行会先读取 GitHub 上 #24 旧正文并通过 #24 body profile guard,再从 `--body-file` 读取新正文;随后先 PATCH issue 主体,再把本次新增的 `## 更新 YYYY-MM-DD HH:MM 北京时间` 段落发送给 ClaudeQQClaudeQQ 失败不会回滚 issue 正文,失败只体现在返回 JSON 的 `claudeqq.ok=false` 和结构化 `degradedReason`。带通知 flag 的 `--dry-run` 不 PATCH、不发送;它按新正文做发送预览,并在输出中标明非 dry-run 才会读取旧正文做可靠 diff。默认 ClaudeQQ 目标是私聊 `645275593`,默认 base URL 是 UniDesk 受控入口 `http://backend-core:8080/api/microservices/claudeqq/proxy`,可用 `UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_ENABLED``UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_BASE_URL``UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_TARGET_TYPE``UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_USER_ID``UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_GROUP_ID``UNIDESK_COMMANDER_BRIEF_CLAUDEQQ_TIMEOUT_MS` 覆盖。
- `gh issue scan-escape [--repo owner/name] [--limit N]` 只读扫描 issue 主体和 comments 中的字面量 `\n`、可疑 `\t`、shell newline escape 和 ANSI escape 字符串,输出 issue/comment id、url、kind、snippet,不自动修复。`gh pr list|view` 继续提供 REST 只读列表和详情`gh pr create --title <title> --body-file <file>|--body <text> --base <branch> --head <branch> [--draft] [--dry-run]``gh pr comment <number> --body-file <file>|--body <text> [--dry-run]` 是安全写入口。`pr create --dry-run` 只输出 planned operation,不访问 GitHub;非 dry-run 创建前会校验 repo、base、head 和 compare ahead 状态,成功时返回 PR number/url。`pr comment --dry-run` 只输出计划并保留 Markdown 原始换行和反引号;非 dry-run 会先确认 PR 存在再写入 issue comment。`gh pr merge` 本阶段不开放,始终结构化返回 `unsupported-command`
- `gh issue scan-escape [--repo owner/name] [--limit N]` 只读扫描 issue 主体和 comments 中的字面量 `\n`、可疑 `\t`、shell newline escape 和 ANSI escape 字符串,输出 issue/comment id、url、kind、snippet,不自动修复。`gh pr list|view [--json ...]` 提供 REST 列表和详情PR 字段白名单是 `body,title,state,number,url,author,head,base,draft,createdAt,updatedAt``gh pr create --title <title> --body-file <file>|--body <text> --base <branch> --head <branch> [--draft] [--dry-run]``gh pr update <number> --mode replace|append --body-file <file>|--body <text> [--title ...] [--dry-run]``gh pr comment create <number> --body-file <file>|--body <text> [--dry-run]``gh pr comment delete <commentId> [--dry-run]``gh pr close|reopen <number> [--dry-run]` 是 PR CRUD/生命周期入口。`pr create --dry-run` 只输出 planned operation,不访问 GitHub;非 dry-run 创建前会校验 repo、base、head 和 compare ahead 状态,成功时返回 PR number/url。`pr update --mode append` 会先读取当前 PR body 再追加正文。`gh pr delete <number>``gh pr merge` 本阶段不开放,始终结构化返回 `unsupported-command`PR 生命周期删除语义请使用 `close`
- `ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs` 管理 D601 原生 k3s 上的 Tekton CI。`run` 手动创建每 commit 检查和 Code Queue 只读性能门禁;`publish-backend-core``publish-user-service` 从 pushed Git commit 构建并发布 `127.0.0.1:5000/unidesk/<service>:<commit>` commit-pinned artifacts,输出 `artifactSummary`(含 `serviceId``sourceCommit``sourceRepo``dockerfile``imageRef``tag``digest``digestRef`),但不部署生产;`run-dev-e2e` 的 Git 控制 runner、短 launcher、host fetch 边界、临时 smoke namespace 和 no-CD 规则只在 `docs/reference/dev-ci-runner.md` 定义;Tekton CI 通用规则见 `docs/reference/ci.md`
- `codex deploy <commitId>` 是旧 Code Queue 兼容部署入口,已禁用以防止维护通道直连 D601 部署 Code Queue;当前 dev 自动化只做 `ci run-dev-e2e` smoke,不提供 Code Queue CD,详细规则见 `docs/reference/codex-deploy.md`
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。提交确认和 dry-run 必须返回完整 prompt、字符数和 `truncated=false`,不能套用任务详情的预览截断策略,否则长任务 prompt 无法被人工验收。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQLD601 scheduler 只轮询并执行已入库任务。
@@ -69,7 +69,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
`microservice proxy` 是面向人工验证和受控调试的私有后端入口。默认 method 为 GET;使用 `--body-json JSON``--body-file path``--body-stdin` 时默认 method 切换为 POST,也可显式加 `--method POST|PUT|PATCH|DELETE`,但 GET/HEAD 不允许携带请求体。所有请求仍受 config 中的 `allowedMethods``allowedPathPrefixes` 限制。为了避免 Pipeline snapshot 这类超大业务 JSON 造成 CLI 输出爆炸,响应 body 超过默认阈值时会返回 `bodyOmitted=true``bodyPreview``bodyBytes``rawHint``--raw` 仍受默认硬限额保护,需要完整 body 时显式添加 `--raw --full`,或用 `--max-body-bytes <N>` 调整预览阈值。正式 frontend 展示仍应优先使用业务控件和 `__unideskArrayLimit` 这类展示级裁剪参数,而不是默认倾倒完整 JSON。
GitHub issue 写操作必须优先使用 `bun scripts/cli.ts gh issue ... --body-file <file>`。不要把 Markdown 正文拼进 shell 参数、`gh issue comment --body``gh api -f body=...`;这些路径容易把真实换行污染成字面量 `\n``gh issue` 写命令第一阶段不接受 stdin 正文;需要从生成内容写入 issue 时,先落到临时 Markdown 文件或已审阅的工作文件,再把该文件路径传给 `--body-file`。PR 安全写入口同样优先 `--body-file``--body` 只适合短单行内容;`gh pr merge` 暂不开放,不存在 `--confirm` 可绕过的真实 merge 路径。CLI 会按 UTF-8 原样读取文件内容并用 JSON body 调用 REST API。
GitHub issue/PR 写操作必须优先使用 `bun scripts/cli.ts gh issue|pr ... --body-file <file>`。不要把 Markdown 正文拼进 shell 参数、`gh issue comment --body``gh api -f body=...`;这些路径容易把真实换行污染成字面量 `\n``gh issue` 写命令第一阶段不接受 stdin 正文;需要从生成内容写入 issue 时,先落到临时 Markdown 文件或已审阅的工作文件,再把该文件路径传给 `--body-file`。PR 安全写入口同样优先 `--body-file``--body` 只适合短单行内容;`update --mode append` 用 REST 读取旧正文后追加文件字节,不引入 shell 拼接正文路径。`gh pr merge` 暂不开放,不存在 `--confirm` 可绕过的真实 merge 路径。CLI 会按 UTF-8 原样读取文件内容并用 JSON body 调用 REST API。
`network perf` 用于生成组网性能前后对比数据。标准 Code Queue overview 读路径基准命令是 `bun scripts/cli.ts network perf --service code-queue --path /api/tasks/overview?limit=30 --count 30 --concurrency 1 --label before`,远程主 server 可用 `bun scripts/cli.ts --main-server-ip 74.48.78.17 network perf ...`。输出包含成功/失败数、状态码分布、`x-unidesk-cache``x-unidesk-proxy-mode``x-unidesk-upstream-proxy-mode` 分布和 min/p50/p90/p95/maxprovider-gateway 长连接数据面验收应看到 `proxyModeCounts.provider-ws-http-tunnel`adapter native Service 数据面验收应看到 upstream proxy mode 为 `kubernetes-native-service`,若出现 `kubernetes-api-service-proxy` 必须结合 `/api/control-plane.nativeServiceProxy.failedServices` 解释 fallback 原因。