dsh-github-flow 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.en.md +102 -15
  2. package/README.md +105 -15
  3. package/docs/UPGRADE-NOTES.md +12 -11
  4. package/docs/architecture.md +67 -14
  5. package/lib/api/route-helpers.d.ts +19 -0
  6. package/lib/api/route-helpers.js +54 -0
  7. package/lib/api/routes-actions.js +126 -2
  8. package/lib/api/routes-lifecycle.js +8 -1
  9. package/lib/api/routes-overview.d.ts +26 -0
  10. package/lib/api/routes-overview.js +276 -34
  11. package/lib/api/routes-proxy.js +8 -1
  12. package/lib/api/routes.d.ts +11 -1
  13. package/lib/api/routes.js +15 -2
  14. package/lib/cache.d.ts +0 -21
  15. package/lib/cache.js +0 -37
  16. package/lib/client.d.ts +2 -1
  17. package/lib/client.js +2108 -658
  18. package/lib/destructive.d.ts +59 -0
  19. package/lib/destructive.js +285 -0
  20. package/lib/executor/args-guard.d.ts +17 -0
  21. package/lib/executor/args-guard.js +23 -0
  22. package/lib/executor/failure-anchors.d.ts +52 -0
  23. package/lib/executor/failure-anchors.js +116 -0
  24. package/lib/executor/pr-review.d.ts +95 -0
  25. package/lib/executor/pr-review.js +175 -0
  26. package/lib/executor/run-log.d.ts +36 -0
  27. package/lib/executor/run-log.js +78 -0
  28. package/lib/executor.d.ts +204 -3
  29. package/lib/executor.js +670 -28
  30. package/lib/github-service.d.ts +1 -1
  31. package/lib/main.d.ts +92 -18
  32. package/lib/main.js +186 -95
  33. package/lib/renderers/common.d.ts +0 -3
  34. package/lib/renderers/common.js +8 -44
  35. package/lib/renderers/markdown.d.ts +1 -0
  36. package/lib/renderers/markdown.js +1 -0
  37. package/lib/renderers/pr.js +26 -1
  38. package/lib/renderers/task.d.ts +9 -0
  39. package/lib/renderers/task.js +120 -0
  40. package/lib/review-rules.d.ts +60 -0
  41. package/lib/review-rules.js +204 -0
  42. package/lib/shared/state-badges.d.ts +42 -0
  43. package/lib/shared/state-badges.js +102 -0
  44. package/lib/task-primitives.d.ts +109 -0
  45. package/lib/task-primitives.js +194 -0
  46. package/lib/tool-tiers.d.ts +57 -0
  47. package/lib/tool-tiers.js +168 -0
  48. package/lib/tools/agent-task.d.ts +4 -0
  49. package/lib/tools/agent-task.js +22 -9
  50. package/lib/tools/api.d.ts +4 -0
  51. package/lib/tools/api.js +48 -12
  52. package/lib/tools/body-input.d.ts +58 -0
  53. package/lib/tools/body-input.js +123 -0
  54. package/lib/tools/cache.js +42 -40
  55. package/lib/tools/codespace.js +4 -12
  56. package/lib/tools/discussion.d.ts +4 -0
  57. package/lib/tools/discussion.js +33 -17
  58. package/lib/tools/gist.d.ts +1 -39
  59. package/lib/tools/gist.js +4 -14
  60. package/lib/tools/help.js +65 -13
  61. package/lib/tools/issue.d.ts +4 -0
  62. package/lib/tools/issue.js +37 -38
  63. package/lib/tools/label.js +4 -15
  64. package/lib/tools/pr.d.ts +51 -0
  65. package/lib/tools/pr.js +204 -46
  66. package/lib/tools/project.js +4 -13
  67. package/lib/tools/release.d.ts +4 -0
  68. package/lib/tools/release.js +26 -26
  69. package/lib/tools/repo.js +4 -4
  70. package/lib/tools/ruleset.js +9 -6
  71. package/lib/tools/run.js +76 -24
  72. package/lib/tools/secret.js +11 -17
  73. package/lib/tools/skill.js +18 -22
  74. package/lib/tools/status.js +4 -4
  75. package/lib/tools/task.d.ts +223 -0
  76. package/lib/tools/task.js +464 -0
  77. package/lib/types.d.ts +183 -1
  78. package/lib/ui/commit-modal.js +108 -4
  79. package/lib/ui/diff-utils.d.ts +51 -0
  80. package/lib/ui/diff-utils.js +155 -0
  81. package/lib/ui/file-diff-modal.d.ts +33 -0
  82. package/lib/ui/file-diff-modal.js +250 -0
  83. package/lib/ui/help-modal.d.ts +1 -1
  84. package/lib/ui/help-modal.js +24 -12
  85. package/lib/ui/issue-modal.js +7 -3
  86. package/lib/ui/pr-modal.js +50 -11
  87. package/lib/ui/run-modal.d.ts +26 -0
  88. package/lib/ui/run-modal.js +486 -0
  89. package/lib/ui/styles.d.ts +1 -1
  90. package/lib/ui/styles.js +376 -44
  91. package/lib/ui/sync-settings-modal.js +1 -1
  92. package/lib/ui/workspace-panel.js +78 -23
  93. package/lib/version.d.ts +1 -1
  94. package/lib/version.js +1 -1
  95. package/package.json +9 -2
package/lib/tools/help.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { wrapWithGate } from '../destructive.js';
1
2
  const KNOWLEDGE_BASE = {
2
3
  overview: `### 🧭 DSH GitHub Flow 工具箱全景导航
3
4
 
@@ -15,19 +16,21 @@ const KNOWLEDGE_BASE = {
15
16
  | \`github_skill\` | 跨 Agent 开源 Skills 生态发现与维护 | 检索/预览/安装 GitHub 上的 Agent Skills(gh preview 特性);\`publish\` 规范自检与发布、\`update\` 检查并同步更新已安装技能 |
16
17
  | \`github_label\` | 标签体系管理与跨仓库克隆 | 查看标签列表、创建/编辑标签、从源仓库一键 clone 标签体系、删除标签(需 confirm) |
17
18
  | \`github_ruleset\` | 分支保护规则与策略诊断 | 检查分支适用的合并策略规则 (check)、查看规则集列表与详情,权威回答“PR 为何无法合并” |
18
- | \`github_cache\` | Actions 构建缓存盘点与安全清理 | 查看缓存列表与体积占用、按 ID/Key 删除指定缓存、全量清空缓存(需 confirm) |
19
+ | \`github_cache\` | Actions 构建缓存盘点与安全清理 | 查看缓存列表与体积占用、按 ID/Key 删除指定缓存、删除缓存(三态均需 confirm) |
19
20
  | \`github_gist\` | 代码片段与调试日志分享治理 | 查看 Gist 列表与代码正文、创建 Secret/Public 代码片段、编辑与物理删除(需 confirm) |
20
21
  | \`github_status\` | 跨仓库待办与动态聚合看板 | 账号维度汇总指派给我的 Issue/PR、等待评审、被 @提及 以及订阅仓库近期动态 |
21
22
  | \`github_agent_task\` | 云端智能体编码任务会话治理 | 查阅已分派任务列表、检视任务状态与会话日志、向云端 Agent 发起新编码重构任务 |
22
23
  | \`github_discussion\` | 团队技术讨论与开源社区 RFC 闭环 | 浏览讨论列表、检视正文与评论楼层、发起新讨论、编辑正文与追加回复 |
23
24
  | \`github_project\` | Projects v2 项目看板与任务规划 | 查阅项目列表/详情、浏览看板条目 (Items)、添加 Issue/PR 到看板、创建草稿任务与删除条目 |
24
25
  | \`github_codespace\` | 云端远程开发环境管控 (MVP) | 查阅运行中实例、检视机器规格配置、停机防计费 (stop)、安全销毁环境 (delete,需 confirm) |
26
+ | \`github_task\` | **任务级原语**(一次调用完成一件完整的事) | 审一个 PR(元数据 + diff + CI + 仓库规则,可选写回行级评注)、诊断一次 CI 失败(定位 job/step + 日志锚点)、准备一次发版(Conventional Commits 归类 + SemVer + notes 草稿);默认只读 |
25
27
  | \`github_help\` | 路由导航、官方语法速查与故障自愈 | 遇到不确定的操作、搜索语法或接口报错时查询用法与诊断配方 |
26
28
 
27
29
  💡 **使用准则**:优先调用高层语义工具(pr / issue / run / repo / release / secret);当高层工具无法满足特殊接口需求时,再降级使用 \`github_api\`。
28
- 📊 **规模口径**:18 个工具 / 103 个业务 action(不含 \`github_help\` 的 21 个查询主题与 \`github_api\` 的 4 个 HTTP method——它们不是业务动作)。
30
+ 📄 **长正文走文件,别塞进 body**:正文/发版说明/请求体/prompt 若已经写成本地 md 文件,直接用 \`body_file\` / \`notes_file\` / \`input_file\` / \`prompt_file\` 指向它,不要把全文内联——插件读文件、正文经 stdin 交给 gh,既不占模型上下文,也不会撞上 Windows 的命令行长度上限。
31
+ 📊 **规模口径**:19 个工具 / 106 个业务 action(不含 \`github_help\` 的 22 个查询主题与 \`github_api\` 的 4 个 HTTP method——它们不是业务动作)。
29
32
  💬 **人类指令**:聊天框支持 \`/gh status\`(大盘看板)、\`/gh upgrade\`(一键升级插件)、\`/gh help\`(快捷帮助)。
30
- 🖥️ **可视化工作台**:最左侧 Octocat 图标打开全景驾驶舱(4 标签:本地工作区 / 我的仓库 / Pull Requests / 待办 Issues);每个会话右侧栏有 5 个 Tab(Git / 未提交 / PR / Issues / Actions)。点击任一 PR 或 Issue 卡片可在线查看详情与评论,无需跳浏览器。
33
+ 🖥️ **可视化工作台**:最左侧 Octocat 图标打开全景驾驶舱(4 标签:本地工作区 / 我的仓库 / Pull Requests / 待办 Issues);每个会话右侧栏有 5 个 Tab(Git / 未提交 / PR / Issues / Actions)。PR、Issue、Actions 三类卡片点击均可在线查看详情(Actions 为 job / step 树 + 按需失败日志),无需跳浏览器;提交详情里的「文件变更统计」与未提交清单里的文件均可弹窗预览该文件的 Unified Diff(整包补丁按文件切分、重命名路径自动归一、超长补丁按文件边界截断)。
31
34
  🗺️ **命令对照**:不确定某 gh 命令对应哪个工具?\`github_help(topic: 'mapping')\` 查询完整映射表(含未覆盖域的 API 引导)。`,
32
35
  pr: `### 🔀 github_pr 详细操作指南
33
36
 
@@ -50,6 +53,8 @@ const KNOWLEDGE_BASE = {
50
53
  - 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI 门禁失败。
51
54
  - **上传本地附件/图片**: \`github_pr(action: 'create', title: 'UI 重构', body: '对比图如下', attach_images: ['./before.png', './after.png'])\`
52
55
  - 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个文件,禁止网络 URL,与 view_images 对称闭环。
56
+ - **长正文走本地文件**: \`github_pr(action: 'create', title: '重构方案', body_file: './PR_BODY.md')\`
57
+ - \`body_file\` 与 \`body\` 二选一,适用于 create/edit/comment/review 四个动作(review 的行级评注走 REST,正文同样可由文件提供);相对路径按当前工作区解析,文件必须是合法 UTF-8(GBK/ANSI 会明确报错),正文上限 65,536 字符;插件读文件后经 \`--body-file -\` 走 stdin,正文不进模型上下文。
53
58
  - **追加评论与编辑**: \`github_pr(action: 'comment', pr_number: 123, body: 'LGTM'); github_pr(action: 'edit', pr_number: 123, body: '更新后的描述')\`
54
59
  - 两者同样支持 \`attach_images\` 附件通道。`,
55
60
  issue: `### 🎯 github_issue 详细操作指南
@@ -63,6 +68,8 @@ const KNOWLEDGE_BASE = {
63
68
  - 触达目录上限时返回 \`remaining\` 结构化清单,直接作为下一批 image_urls 续传即可。
64
69
  - **上传本地附件/图片**: \`github_issue(action: 'comment', issue_number: 42, body: '复现证据', attach_images: ['./bug.png'])\`
65
70
  - 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个本地文件(绝对路径或工作区相对路径,禁止网络 URL)。
71
+ - **长正文走本地文件**: \`github_issue(action: 'create', title: '周报:本周修复清单', body_file: './report.md')\`
72
+ - \`body_file\` 与 \`body\` 二选一(同时给出会直接报错,不做静默优先级);相对路径按当前工作区解析,文件必须是合法 UTF-8(GBK/ANSI 会明确报错),正文上限 65,536 字符;插件读文件后经 \`--body-file -\` 走 stdin,正文不进模型上下文。
66
73
  - **关联开发分支**: \`github_issue(action: 'develop', issue_number: 42, branch_name: 'fix-login-bug')\`
67
74
  - 自动在远端和本地创建带有官方关联关系的开发分支并切换过去,实现“读需求 -> 开分支写代码”的无缝衔接。
68
75
  - **编辑 Issue**: \`github_issue(action: 'edit', issue_number: 42, title: '新标题', add_labels: ['p1'], remove_labels: ['triage'])\`
@@ -92,7 +99,11 @@ const KNOWLEDGE_BASE = {
92
99
  - **启停工作流**: \`github_run(action: 'workflow_enable'/'workflow_disable', workflow: '…')\`
93
100
  - disable 后 trigger 将静默失败,直至重新 enable——输出会显式警告这一点。
94
101
  - **下载构建产物**: \`github_run(action: 'download', run_id: '12345678', name: '构建产物名'?)\`
95
- - 落盘目录由插件固定管理(\`~/.dsh/data/dsh-github-flow/artifacts/\` 下按仓库与 run 隔离),返回文件清单。`,
102
+ - 落盘目录由插件固定管理(\`~/.dsh/data/dsh-github-flow/artifacts/\` 下按仓库与 run 隔离),返回文件清单。
103
+ - **看单次运行的 job / step 结构**: \`github_run(action: 'view', run_id: '12345678')\`
104
+ - 返回 run 元数据 + jobs 树(每个 job 的 conclusion、起止时间、url,以及 steps 的 number/name/conclusion);这是「挂在第几步」最快的入口。
105
+ - **人类侧在线查看(不是工具调用)**: 右侧栏 Actions Tab 点击任一卡片 → job / step 树 + 失败步骤高亮 + 按需日志锚点(一次最多 3 个失败 job、服务端批次预算 90 秒)。
106
+ - Agent 侧的等价快捷方式是 \`github_task(action: 'diagnose_ci', run_id: '12345678')\`:定位失败 job/step → 萃取日志锚点 → 汇总疑似相关文件,一次返回结论包。`,
96
107
  repo: `### 📦 github_repo 详细操作指南
97
108
 
98
109
  - **查看元数据与标签**: \`github_repo(action: 'view')\`
@@ -119,6 +130,9 @@ const KNOWLEDGE_BASE = {
119
130
  - 附加 \`Accept: application/vnd.github.raw\`,返回纯文本而非 base64 包装。
120
131
  - **全量聚合翻页**:
121
132
  \`github_api(endpoint: 'repos/owner/repo/commits', paginate: true)\`
133
+ - **长请求体走本地文件**:
134
+ \`github_api(endpoint: 'repos/owner/repo/issues', method: 'POST', input_file: './payload.json')\`
135
+ - 映射 \`--input -\`(插件读文件后经 stdin 交给 gh,省去把 JSON 塞进参数);与 \`fields\` **不互斥**:两者同时给出时 \`fields\` 作为 URL 查询参数、文件内容作为请求体;请求体不是 GitHub 正文,不受 65,536 字符的正文口径约束。
122
136
  - **GraphQL 请求**:
123
137
  \`github_api(endpoint: 'graphql', fields: { query: 'query { viewer { login } }' })\``,
124
138
  release: `### 🏷️ github_release 版本发布指南
@@ -128,6 +142,8 @@ const KNOWLEDGE_BASE = {
128
142
  - **创建 Release(自动生成说明)**:
129
143
  \`github_release(action: 'create', tag: 'v0.1.9', generate_notes: true, prerelease: false)\`
130
144
  - 若远端尚无该 Tag,gh 会自动从 target 分支(默认主分支)最新提交打 Tag。
145
+ - **用本地 md 作为发版说明**: \`github_release(action: 'create', tag: 'v0.1.9', notes_file: './RELEASE_NOTES.md')\`
146
+ - \`notes_file\` 与 \`notes\` 二选一(映射 \`--notes-file -\`);相对路径按当前工作区解析,文件必须是合法 UTF-8、不超过 4MB;**发版说明不套用正文的 65,536 字符口径**(GitHub 对 Release 说明的上限宽得多),长说明写进文件即可,不必先读进上下文再原样回填。
131
147
  - **上传构建资产**:
132
148
  \`github_release(action: 'upload', tag: 'v0.1.9', assets: ['./dsh-github-flow-0.1.9.tgz'])\`
133
149
  - 覆盖同名旧资产必须附加 \`clobber: true, confirm: true\`(旧资产删除后不可恢复)。
@@ -203,8 +219,8 @@ const KNOWLEDGE_BASE = {
203
219
  - 返回仓库 Actions 构建缓存清单、占用体积(自动换算为 KB/MB/GB)、关联 Git Ref 以及最后访问时间。
204
220
  - 可附加 \`key: 'npm-cache'\` 过滤特定前缀,或通过 \`ref: 'refs/heads/main'\` 限定分支。
205
221
  - **精准删除单条缓存**:
206
- - 按数字 ID 删除: \`github_cache(action: 'delete', cache_id: '12345678')\`
207
- - 按 Key 键名删除: \`github_cache(action: 'delete', key: 'Linux-npm-18-abcd1234')\`
222
+ - 按数字 ID 删除: \`github_cache(action: 'delete', cache_id: '12345678', confirm: true)\`
223
+ - 按 Key 键名删除: \`github_cache(action: 'delete', key: 'Linux-npm-18-abcd1234', confirm: true)\`
208
224
  - **全量清空仓库缓存**:
209
225
  \`github_cache(action: 'delete', all: true, confirm: true)\`
210
226
  - 批量破坏性操作:会导致后续流水线构建重新拉取依赖,必须显式附加 \`confirm: true\`;底层自动注入 \`--succeed-on-no-caches\` 保证幂等执行。`,
@@ -231,7 +247,7 @@ const KNOWLEDGE_BASE = {
231
247
  - **排除噪音仓库**: \`github_status(exclude: ['owner/noisy-repo'])\`
232
248
  - 过滤高频刷屏但无需关注的仓库活动。
233
249
  - **查看工具链运行时健康度**: \`github_status(action: 'health')\`
234
- - 审计当前会话 18 大工具矩阵的综合健康分、API 降级率、安全防线拦截频次与针对性自愈建议。`,
250
+ - 审计当前会话 19 大工具矩阵的综合健康分、API 降级率、安全防线拦截频次与针对性自愈建议。`,
235
251
  agent_task: `### 🤖 github_agent_task 智能体任务指南
236
252
 
237
253
  管理与调度 GitHub Copilot Coding Agent 云端智能体会话:
@@ -249,6 +265,8 @@ const KNOWLEDGE_BASE = {
249
265
  - **检视讨论正文与回复楼层**: \`github_discussion(action: 'view', discussion_number: 123, comments: true)\`
250
266
  - **发起新讨论**: \`github_discussion(action: 'create', title: 'RFC: 架构演进方案', category: 'Ideas', body: '详细方案...')\`
251
267
  - \`title\`、\`category\` 与 \`body\` 均为必填项。
268
+ - **长正文走本地文件**: \`github_discussion(action: 'create', title: 'RFC: 架构演进方案', category: 'Ideas', body_file: './rfc.md')\`
269
+ - \`body_file\` 与 \`body\` 二选一(\`title\` / \`category\` 仍必填,映射 \`--body-file -\`);相对路径按当前工作区解析,文件必须是合法 UTF-8,正文上限 65,536 字符;RFC 动辄数千字,写进文件比塞进参数更省上下文。
252
270
  - **编辑讨论**: \`github_discussion(action: 'edit', discussion_number: 123, title: '新标题', body: '修正后的正文')\`
253
271
  - **发表讨论回复**: \`github_discussion(action: 'comment', discussion_number: 123, body: '赞同该方案!')\`
254
272
  - 💡 **提示**:若报错「discussions disabled」,说明目标仓库未在 GitHub 仓库「Settings -> General -> Features」中启用 Discussions 社区功能。`,
@@ -275,6 +293,24 @@ const KNOWLEDGE_BASE = {
275
293
  - **安全销毁实例**: \`github_codespace(action: 'delete', codespace: 'fuzzy-spoon-69vj', confirm: true)\`
276
294
  - 物理销毁不可逆,必须附加 \`confirm: true\`。
277
295
  - 🔑 若报缺少权限,在终端执行单行自愈授权命令:\`gh auth refresh -h github.com -s codespace\``,
296
+ task: `### 🎯 github_task 任务级原语指南
297
+
298
+ \`github_task\` 把「一件完整的事」封装成一次调用,返回**已裁剪、可直接执行**的结论包:
299
+ \`summary\`(结论)+ \`facts\`(结构化事实)+ \`nextActions\`(下一步)+ \`cost\`(字符数 / gh 调用数 / 耗时)。
300
+
301
+ - **审一个 PR**: \`github_task(action: 'review_pr', pr_number: 123)\`
302
+ - 一次拿齐:PR 元数据 + head SHA + 变动文件 diff(按 \`budget_chars\` 裁剪)+ CI 结论 + 仓库 \`AGENTS.md\` 审查规则 + 既有行级评论数。
303
+ - \`focus: 'security' | 'correctness' | 'perf' | 'style'\` 只影响 summary 的提示语,不改变取数。
304
+ - **写回**(默认关闭): \`github_task(action: 'review_pr', pr_number: 123, write: true, confirm: true, body: '…', comments: [{path, line, body}])\`,字段语义与 \`github_pr(action: 'review')\` 完全一致;整体审阅说明若已写成本地 md,改用 \`body_file\`(与 \`body\` 二选一,写回走 REST 请求体)。
305
+ - **诊断一次 CI 失败**: \`github_task(action: 'diagnose_ci', run_id: 456)\` 或 \`github_task(action: 'diagnose_ci', pr_number: 123)\`
306
+ - 定位失败 job/step → 萃取失败日志锚点(pytest / node / Go / tsc / npm)→ 汇总疑似相关文件。
307
+ - 日志被截断时 \`truncated.totalUnknown = true\`:**不要据此判断「就这些失败」**。
308
+ - **准备一次发版**: \`github_task(action: 'prepare_release', bump: 'auto')\`
309
+ - 读最近 tag 以来的提交 → 按 Conventional Commits 归类 → 推导 SemVer(breaking→major / feat→minor / 其余→patch)→ 生成 notes 草稿 + 发版体检(工作区干净 / 同步远端 / CI 绿)。
310
+ - **本原语不会打 tag、不会 push、不会创建 Release**:发版是人的决定,它只把「该发什么版本、notes 长什么样」准备好,并在 \`nextActions\` 里给出可直接执行的命令。
311
+
312
+ > 需要细粒度控制时,用对应的原子工具:\`github_pr\` / \`github_run\` / \`github_release\`。原语是快捷方式,不是唯一入口。
313
+ `,
278
314
  mapping: `### 🗺️ gh 命令 ↔ 插件工具映射总表
279
315
 
280
316
  #### 一、已覆盖域映射(语义化 action 精确对照)
@@ -363,7 +399,7 @@ const KNOWLEDGE_BASE = {
363
399
  | gh 命令 | 插件调用 | 备注 |
364
400
  | :--- | :--- | :--- |
365
401
  | \`gh cache list\` | \`github_cache(action:'list')\` | 缓存盘点与过滤 |
366
- | \`gh cache delete\` | \`github_cache(action:'delete', cache_id:…)\` | all:true 需 confirm |
402
+ | \`gh cache delete\` | \`github_cache(action:'delete', cache_id:…, confirm:true)\` | 三态均需 confirm |
367
403
 
368
404
  **Gist(github_gist,5 action)**
369
405
  | gh 命令 | 插件调用 | 备注 |
@@ -373,6 +409,14 @@ const KNOWLEDGE_BASE = {
373
409
  | \`gh gist edit\` | \`github_gist(action:'edit', gist_id:…)\` | 描述与文件修改 |
374
410
  | \`gh gist delete\` | \`github_gist(action:'delete', gist_id:…, confirm:true)\` | 物理删除,需 confirm |
375
411
 
412
+ **Task(github_task,3 action)**
413
+
414
+ | gh 命令 | 插件调用 | 说明 |
415
+ | :--- | :--- | :--- |
416
+ | \`gh pr view\` + \`gh pr diff\` + \`gh pr checks\` | \`github_task(action:'review_pr')\` | 一次拿齐审阅上下文(可选写回行级评注) |
417
+ | \`gh run view --json jobs\` + \`--log-failed\` | \`github_task(action:'diagnose_ci')\` | 定位失败 job/step 并萃取日志锚点 |
418
+ | \`git describe\` + \`git log\` | \`github_task(action:'prepare_release')\` | 推导版本 + 生成 notes 草稿(不发版) |
419
+
376
420
  **Status(github_status,2 action)**
377
421
  | gh 命令 | 插件调用 | 备注 |
378
422
  | :--- | :--- | :--- |
@@ -417,8 +461,10 @@ const KNOWLEDGE_BASE = {
417
461
 
418
462
  - \`gh workflow …\` / \`gh search …\` / \`gh variable …\` 分别归 **github_run / github_repo / github_secret**(不是独立工具)
419
463
  - \`gh api\` 的等价物是 \`github_api\`(全端点逃生门,支持 raw/paginate/jq)
464
+ - 人类侧在线查看(PR / Issue / Actions Run 详情弹窗)由插件 HTTP 路由提供(\`/api/github/pr-detail\`、\`/api/github/issue-detail\`、\`/api/github/run-detail\`、\`/api/github/run-logs\`),**不是工具 action**;Agent 侧对应能力分别是 \`github_pr(action:'view')\`、\`github_issue(action:'view')\`、\`github_run(action:'view')\`
420
465
  - 图片查看:\`github_issue\`/\`github_pr\` 的 \`view_images\`、\`github_repo\` 的 \`readme\` + image_urls(插件独有能力,gh 无对应命令)
421
466
  - 附件上传:\`github_issue\`/\`github_pr\` 的 \`attach_images\` 映射 gh ≥2.99 原生可重复 \`--attach\` flag(支持 create/edit/comment)
467
+ - 本地文件正文:\`gh issue create -F report.md\` 的等价写法是 \`github_issue(action:'create', body_file:'report.md')\`——「本地 md → Issue/PR/讨论/发版说明/请求体/任务 prompt」这条通道已由插件覆盖,文件由插件读取、正文经 stdin 交给 gh,**不经过模型上下文**;对应参数分别是 \`body_file\`(issue / pr / discussion / task)、\`notes_file\`(release)、\`input_file\`(api)、\`prompt_file\`(agent_task),相对路径按当前工作区解析
422
468
 
423
469
  #### 三、未覆盖域引导(经 github_api 调用)
424
470
 
@@ -472,10 +518,16 @@ const KNOWLEDGE_BASE = {
472
518
  - *解法*:需管理员前往 GitHub 网页端 \`Settings -> General -> Features\` 勾选开启 \`Discussions\`。
473
519
  8. **操作被拦截提示 \`Awaiting confirmation\`**:
474
520
  - *原因*:对于高危破坏性操作(如合并 PR、删除分支、物理删除 Issue、清空构建缓存、删除密钥、销毁 Codespace),插件设置了强制安全确认防线,未确认时绝对不触发底层 CLI。
475
- - *解法*:审阅影响评估报告后,在参数中显式传入 \`confirm: true\` 即可放行。`
521
+ - *解法*:审阅影响评估报告后,在参数中显式传入 \`confirm: true\` 即可放行。
522
+ 9. **\`log_failed\` 拉取很慢 / 报 \`unexpected EOF\` / 连接被重置**:
523
+ - *原因*:GitHub 日志接口是「先打包再下载」,实测单次 4–40 秒,走代理时可能被中途掐断;矩阵构建下日志体量随 job 数线性增长。
524
+ - *解法*:① 先 \`github_run(action:'view')\` 确认到底哪个 job 挂了,只对失败 job 重试;② 换 \`github_task(action:'diagnose_ci')\` 走一次性的结论包(内部同样是锚点萃取,但会顺带汇总相关文件);③ 仍失败时改用 \`github_api\` 直接拉 job 日志端点,或让用户到 Actions 页面查看完整日志。
525
+ 10. **人类侧「提取失败日志」为何只给一小段 / 为什么最多 3 个 job**:
526
+ - *原因*:弹窗内展示的是**锚点萃取后的片段**(失败块 + 上下文,无锚点时回退尾部 200 行),不是完整日志;一次点击最多串行拉 3 个失败 job,服务端总预算 90 秒——这是为了不让一次点击把面板卡住几分钟。
527
+ - *解法*:需要完整日志或更多 job 时,让用户点弹窗右上角「GitHub ↗」,或由 Agent 用 \`github_run(action:'log_failed')\` / \`github_task(action:'diagnose_ci')\` 分批处理。`
476
528
  };
477
529
  export function createHelpTool(executor) {
478
- return {
530
+ return wrapWithGate({
479
531
  name: 'github_help',
480
532
  description: 'GitHub 工具箱使用指南与官方命令手册。当你不确定该用哪个工具、需要特定操作的高级语法与示例、或排查 GitHub 报错时调用。',
481
533
  parameters: {
@@ -502,6 +554,7 @@ export function createHelpTool(executor) {
502
554
  'discussion',
503
555
  'project',
504
556
  'codespace',
557
+ 'task',
505
558
  'mapping',
506
559
  'search_syntax',
507
560
  'troubleshooting',
@@ -526,8 +579,7 @@ export function createHelpTool(executor) {
526
579
  },
527
580
  },
528
581
  async execute(args, execContext) {
529
- const cwd = execContext?.cwd || process.cwd();
530
- const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
582
+ const { cwd, sessionId } = executor.resolveExecScope(execContext);
531
583
  // 1. 若显式指定了 gh_command,直接透传查询官方离线手册
532
584
  if (args.gh_command && typeof args.gh_command === 'string') {
533
585
  let parts = args.gh_command.trim().split(/\s+/);
@@ -557,5 +609,5 @@ export function createHelpTool(executor) {
557
609
  result: `未找到主题「${rawTopic}」的指南。可用主题:${Object.keys(KNOWLEDGE_BASE).join(', ')}。`,
558
610
  };
559
611
  },
560
- };
612
+ });
561
613
  }
@@ -36,6 +36,10 @@ export declare function createIssueTool(executor: GhExecutor): {
36
36
  type: string;
37
37
  description: string;
38
38
  };
39
+ body_file: {
40
+ type: string;
41
+ description: string;
42
+ };
39
43
  labels: {
40
44
  type: string;
41
45
  items: {
@@ -1,6 +1,9 @@
1
+ import { wrapWithGate } from '../destructive.js';
1
2
  import { compareGhVersions } from '../executor.js';
2
3
  import { renderIssueMarkdown } from '../renderers/markdown.js';
3
4
  import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls, validateAttachPaths } from './image-utils.js';
5
+ import { assertPositionArg } from '../executor/args-guard.js';
6
+ import { resolveBodySource } from './body-input.js';
4
7
  function normalizeOutput(data) {
5
8
  if (data && typeof data === 'object' && !Array.isArray(data)) {
6
9
  return data;
@@ -13,7 +16,7 @@ function normalizeOutput(data) {
13
16
  // 共享图片工具链的同名再导出:保持路径与单测稳定
14
17
  export { extractImageUrls as extractIssueImageUrls, validateAttachPaths } from './image-utils.js';
15
18
  export function createIssueTool(executor) {
16
- return {
19
+ return wrapWithGate({
17
20
  name: 'github_issue',
18
21
  description: '管理 GitHub Issues。支持查看 Issue 列表、详情、创建新 Issue、追加评论、关闭 Issue、编辑 Issue 以及关联创建本地开发分支 (develop)。',
19
22
  parameters: {
@@ -47,7 +50,11 @@ export function createIssueTool(executor) {
47
50
  },
48
51
  body: {
49
52
  type: 'string',
50
- description: 'Issue 描述正文或追加评论内容(create, comment, edit 时使用)。',
53
+ description: 'Issue 描述正文或追加评论内容(create, comment, edit 时使用)。与 body_file 二选一。',
54
+ },
55
+ body_file: {
56
+ type: 'string',
57
+ description: '正文来源:本地 Markdown 文件路径(相对路径按当前工作区解析)。与 body 二选一;长正文用本参数,无需把全文读进上下文。',
51
58
  },
52
59
  labels: {
53
60
  type: 'array',
@@ -108,8 +115,9 @@ export function createIssueTool(executor) {
108
115
  },
109
116
  },
110
117
  async execute(args, execContext) {
111
- const cwd = execContext?.cwd || process.cwd();
112
- const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
118
+ // 位置参数守卫:issue_number 是 `gh issue <cmd> <number>` 的位置参数
119
+ assertPositionArg(args.issue_number, 'Issue 编号');
120
+ const { cwd, sessionId } = executor.resolveExecScope(execContext);
113
121
  let repoTarget = args.repo;
114
122
  if (!repoTarget) {
115
123
  const meta = await executor.getRepoMetadata(cwd, sessionId);
@@ -122,6 +130,8 @@ export function createIssueTool(executor) {
122
130
  }
123
131
  const repoArgs = ['-R', repoTarget];
124
132
  const runOpts = { cwd, sessionId };
133
+ // 正文来自本地文件时经 stdin 注入(`--body-file -`):绕开 Windows 约 32K 的命令行上限
134
+ const stdinPatch = (body) => (body.stdin === undefined ? {} : { stdin: body.stdin });
125
135
  const appendAttachFlags = async (cmd, attachImages) => {
126
136
  const attachPaths = validateAttachPaths(attachImages, cwd);
127
137
  if (attachPaths.length > 0) {
@@ -227,7 +237,8 @@ export function createIssueTool(executor) {
227
237
  };
228
238
  }
229
239
  case 'create': {
230
- const cmd = ['issue', 'create', ...repoArgs, '--title', args.title || '', '--body', args.body || ''];
240
+ const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd, keepEmptyInline: true });
241
+ const cmd = ['issue', 'create', ...repoArgs, '--title', args.title || '', ...body.argv];
231
242
  if (args.labels && Array.isArray(args.labels) && args.labels.length > 0) {
232
243
  cmd.push('--label', args.labels.join(','));
233
244
  }
@@ -235,7 +246,7 @@ export function createIssueTool(executor) {
235
246
  cmd.push('--assignee', args.assignees.join(','));
236
247
  }
237
248
  await appendAttachFlags(cmd, args.attach_images);
238
- const res = await executor.run(cmd, runOpts);
249
+ const res = await executor.run(cmd, { ...runOpts, ...stdinPatch(body) });
239
250
  if (!res.ok)
240
251
  throw new Error(res.error);
241
252
  return { message: 'Issue 创建成功', url: res.rawOutput };
@@ -243,11 +254,11 @@ export function createIssueTool(executor) {
243
254
  case 'edit': {
244
255
  if (!args.issue_number)
245
256
  throw new Error('edit 操作必须提供 issue_number');
257
+ const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd });
246
258
  const editCmd = ['issue', 'edit', String(args.issue_number), ...repoArgs];
247
259
  if (args.title)
248
260
  editCmd.push('--title', args.title);
249
- if (args.body)
250
- editCmd.push('--body', args.body);
261
+ editCmd.push(...body.argv);
251
262
  if (args.add_labels && Array.isArray(args.add_labels) && args.add_labels.length > 0) {
252
263
  for (const l of args.add_labels)
253
264
  editCmd.push('--add-label', l);
@@ -257,7 +268,7 @@ export function createIssueTool(executor) {
257
268
  editCmd.push('--remove-label', l);
258
269
  }
259
270
  await appendAttachFlags(editCmd, args.attach_images);
260
- const res = await executor.run(editCmd, runOpts);
271
+ const res = await executor.run(editCmd, { ...runOpts, ...stdinPatch(body) });
261
272
  if (!res.ok)
262
273
  throw new Error(res.error);
263
274
  return { message: `Issue #${args.issue_number} 更新成功`, output: res.rawOutput };
@@ -265,6 +276,15 @@ export function createIssueTool(executor) {
265
276
  case 'develop': {
266
277
  if (!args.issue_number)
267
278
  throw new Error('develop 操作必须提供 issue_number');
279
+ // `--checkout` 会把工作区切到新分支,脏改动会被一起带走——与界面切分支同源风险,
280
+ // 必须过同一道闸门(executor.ensureCleanWorkspace,唯一实现)。
281
+ const guard = await executor.ensureCleanWorkspace(cwd);
282
+ if (!guard.ok) {
283
+ const sample = guard.samples.length
284
+ ? `\n涉及文件(前 ${guard.samples.length} 条):\n${guard.samples.map((s) => ' ' + s).join('\n')}`
285
+ : '';
286
+ throw new Error(`${guard.error}${sample}\n请先提交或 stash 后再创建并检出开发分支。`);
287
+ }
268
288
  const devCmd = ['issue', 'develop', String(args.issue_number), ...repoArgs, '--checkout'];
269
289
  if (args.branch_name) {
270
290
  devCmd.push('-n', args.branch_name);
@@ -275,11 +295,14 @@ export function createIssueTool(executor) {
275
295
  return { message: `已成功为 Issue #${args.issue_number} 创建并检出开发分支`, output: res.rawOutput };
276
296
  }
277
297
  case 'comment': {
278
- if (!args.issue_number || !args.body)
279
- throw new Error('comment 必须提供 issue_number 和 body');
280
- const commentCmd = ['issue', 'comment', String(args.issue_number), ...repoArgs, '--body', args.body];
298
+ if (!args.issue_number)
299
+ throw new Error('comment 操作必须提供 issue_number');
300
+ const body = resolveBodySource({ inline: args.body, file: args.body_file }, { cwd });
301
+ if (body.chars === 0)
302
+ throw new Error('comment 必须提供 body 或 body_file(评论正文)');
303
+ const commentCmd = ['issue', 'comment', String(args.issue_number), ...repoArgs, ...body.argv];
281
304
  await appendAttachFlags(commentCmd, args.attach_images);
282
- const res = await executor.run(commentCmd, runOpts);
305
+ const res = await executor.run(commentCmd, { ...runOpts, ...stdinPatch(body) });
283
306
  if (!res.ok)
284
307
  throw new Error(res.error);
285
308
  return { message: '评论发布成功', output: res.rawOutput };
@@ -287,14 +310,6 @@ export function createIssueTool(executor) {
287
310
  case 'close': {
288
311
  if (!args.issue_number)
289
312
  throw new Error('close 操作必须提供 issue_number');
290
- if (args.confirm !== true) {
291
- return {
292
- status: 'AWAITING_CONFIRMATION',
293
- warning: `⚠️ 高危操作拦截:你即将关闭 Issue #${args.issue_number}。`,
294
- impact: '关闭后该议题状态将变更为 CLOSED,请与用户确认是否确实已解决或需要关闭。',
295
- requiredAction: '如确认关闭,请在参数中附加 `confirm: true` 重新调用。',
296
- };
297
- }
298
313
  const res = await executor.run(['issue', 'close', String(args.issue_number), ...repoArgs], runOpts);
299
314
  if (!res.ok)
300
315
  throw new Error(res.error);
@@ -340,14 +355,6 @@ export function createIssueTool(executor) {
340
355
  if (!args.issue_number)
341
356
  throw new Error('delete 操作必须提供 issue_number');
342
357
  // 物理删除 > close 的拦截级别:issue 编号永久消失、评论湮灭、GraphQL 无法恢复
343
- if (args.confirm !== true) {
344
- return {
345
- status: 'AWAITING_CONFIRMATION',
346
- warning: `⚠️ 高危操作拦截:你即将**物理删除** Issue #${args.issue_number}(非 close,不可恢复)。`,
347
- impact: 'Issue 编号将永久消失,全部评论与附件随之湮灭,GraphQL 层无法恢复——这与 close(可 reopen 逆转)有本质区别。',
348
- requiredAction: '如确认删除,请与用户核实后附加 `confirm: true` 重新调用。',
349
- };
350
- }
351
358
  // 非 TTY 下 gh issue delete 会挂起等待 prompt,--yes 是非交互执行的唯一途径
352
359
  const res = await executor.run(['issue', 'delete', String(args.issue_number), ...repoArgs, '--yes'], runOpts);
353
360
  if (!res.ok)
@@ -363,14 +370,6 @@ export function createIssueTool(executor) {
363
370
  throw new Error(`transfer 目标仓库 (${args.target_repo}) 与当前仓库相同,迁移无意义。`);
364
371
  }
365
372
  // 跨仓库迁移:原 URL 301 但归属改变,不可静默撤回——与 delete 同级 confirm
366
- if (args.confirm !== true) {
367
- return {
368
- status: 'AWAITING_CONFIRMATION',
369
- warning: `⚠️ 高危操作拦截:你即将把 Issue #${args.issue_number} 从 ${repoTarget} 迁移到 ${args.target_repo}。`,
370
- impact: `迁移后 Issue 归属 ${args.target_repo}(原 URL 301 重定向),标签/里程碑不随迁,操作不可静默撤回。需要目标仓库的写权限。`,
371
- requiredAction: '如确认迁移,请与用户核实后附加 `confirm: true` 重新调用。',
372
- };
373
- }
374
373
  const res = await executor.run(['issue', 'transfer', String(args.issue_number), args.target_repo, ...repoArgs], runOpts);
375
374
  if (!res.ok)
376
375
  throw new Error(res.error);
@@ -387,5 +386,5 @@ export function createIssueTool(executor) {
387
386
  throw new Error(`未知的 Issue 操作: ${args.action}`);
388
387
  }
389
388
  },
390
- };
389
+ });
391
390
  }
@@ -1,3 +1,4 @@
1
+ import { wrapWithGate } from '../destructive.js';
1
2
  import { renderLabelMarkdown } from '../renderers/markdown.js';
2
3
  /**
3
4
  * 校验并归一化 6 位十六进制颜色(去除前导 #)
@@ -21,7 +22,7 @@ function normalizeOutput(data) {
21
22
  return { result: data !== undefined && data !== null ? data : '' };
22
23
  }
23
24
  export function createLabelTool(executor) {
24
- return {
25
+ return wrapWithGate({
25
26
  name: 'github_label',
26
27
  description: '管理 GitHub 仓库标签体系 (Labels):查看标签列表、创建新标签、编辑修改现有标签、克隆跨仓库标签体系 (clone) 以及安全删除标签。删除操作不可逆且会解绑议题,需 confirm: true。',
27
28
  parameters: {
@@ -92,8 +93,7 @@ export function createLabelTool(executor) {
92
93
  },
93
94
  },
94
95
  async execute(args, execContext) {
95
- const cwd = execContext?.cwd || process.cwd();
96
- const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
96
+ const { cwd, sessionId } = executor.resolveExecScope(execContext);
97
97
  let repoTarget = args.repo;
98
98
  if (!repoTarget) {
99
99
  const meta = await executor.getRepoMetadata(cwd, sessionId);
@@ -188,17 +188,6 @@ export function createLabelTool(executor) {
188
188
  if (!args.name) {
189
189
  throw new Error('删除标签时 `name` 参数为必填项。');
190
190
  }
191
- if (args.confirm !== true) {
192
- return {
193
- status: 'AWAITING_CONFIRMATION',
194
- action: 'delete',
195
- name: args.name,
196
- repo: repoTarget,
197
- warning: `⚠️ 高危破坏性操作:即将删除仓库 ${repoTarget} 的标签 "${args.name}"`,
198
- impact: `删除标签不可逆,且会导致目标仓库内所有已打上该标签的 Issue 与 PR 自动解绑该标记。`,
199
- requiredAction: `如确认彻底删除,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_label(action: 'delete', name: '${args.name}', confirm: true)\``,
200
- };
201
- }
202
191
  const deleteCmd = ['label', 'delete', args.name, '--yes', ...repoArgs];
203
192
  const res = await executor.run(deleteCmd, runOpts);
204
193
  if (!res.ok)
@@ -233,5 +222,5 @@ export function createLabelTool(executor) {
233
222
  throw new Error(`不支持的 label 操作: "${args.action}"。可选值: list, create, edit, delete, clone。`);
234
223
  }
235
224
  },
236
- };
225
+ });
237
226
  }
package/lib/tools/pr.d.ts CHANGED
@@ -27,6 +27,10 @@ export declare function createPrTool(executor: GhExecutor): {
27
27
  type: string;
28
28
  description: string;
29
29
  };
30
+ body_file: {
31
+ type: string;
32
+ description: string;
33
+ };
30
34
  base: {
31
35
  type: string;
32
36
  description: string;
@@ -44,6 +48,49 @@ export declare function createPrTool(executor: GhExecutor): {
44
48
  enum: string[];
45
49
  description: string;
46
50
  };
51
+ /**
52
+ * 行级评注(review 动作使用)。
53
+ *
54
+ * 为什么必须单独一个参数而不是塞进 body:`gh pr review` 没有行级参数
55
+ * (`gh pr review --help` 里 line/side/path 零命中),只能走
56
+ * `POST /pulls/{n}/reviews` 的 `comments[]`。一次 review 一个请求 =
57
+ * 通知只发一轮,也避开 secondary rate limit 的「创建内容过快」。
58
+ */
59
+ comments: {
60
+ type: string;
61
+ items: {
62
+ type: string;
63
+ properties: {
64
+ path: {
65
+ type: string;
66
+ description: string;
67
+ };
68
+ line: {
69
+ type: string;
70
+ description: string;
71
+ };
72
+ side: {
73
+ type: string;
74
+ enum: string[];
75
+ description: string;
76
+ };
77
+ start_line: {
78
+ type: string;
79
+ description: string;
80
+ };
81
+ body: {
82
+ type: string;
83
+ description: string;
84
+ };
85
+ };
86
+ required: string[];
87
+ };
88
+ description: string;
89
+ };
90
+ commit_id: {
91
+ type: string;
92
+ description: string;
93
+ };
47
94
  merge_method: {
48
95
  type: string;
49
96
  enum: string[];
@@ -53,6 +100,10 @@ export declare function createPrTool(executor: GhExecutor): {
53
100
  type: string;
54
101
  description: string;
55
102
  };
103
+ force: {
104
+ type: string;
105
+ description: string;
106
+ };
56
107
  undo: {
57
108
  type: string;
58
109
  description: string;