dsh-github-flow 0.3.0 → 0.5.0

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 (121) hide show
  1. package/README.en.md +287 -143
  2. package/README.md +270 -125
  3. package/docs/UPGRADE-NOTES.md +12 -9
  4. package/docs/architecture.md +126 -126
  5. package/lib/api/routes-actions.js +249 -0
  6. package/lib/api/routes-overview.js +94 -2
  7. package/lib/cache.d.ts +4 -0
  8. package/lib/cache.js +14 -0
  9. package/lib/client.d.ts +14 -1
  10. package/lib/client.js +4287 -2500
  11. package/lib/commands/gh.d.ts +6 -1
  12. package/lib/commands/gh.js +144 -71
  13. package/lib/executor/git-parser.d.ts +57 -0
  14. package/lib/executor/git-parser.js +255 -0
  15. package/lib/executor/platform-guide.d.ts +14 -0
  16. package/lib/executor/platform-guide.js +40 -0
  17. package/lib/executor/process-utils.d.ts +51 -0
  18. package/lib/executor/process-utils.js +162 -0
  19. package/lib/executor/version-utils.d.ts +12 -0
  20. package/lib/executor/version-utils.js +27 -0
  21. package/lib/executor.d.ts +70 -48
  22. package/lib/executor.js +371 -356
  23. package/lib/git-watcher.d.ts +36 -0
  24. package/lib/git-watcher.js +91 -0
  25. package/lib/main.js +37 -3
  26. package/lib/renderers/agent-task.d.ts +16 -0
  27. package/lib/renderers/agent-task.js +107 -0
  28. package/lib/renderers/cache.d.ts +8 -0
  29. package/lib/renderers/cache.js +66 -0
  30. package/lib/renderers/codespace.d.ts +16 -0
  31. package/lib/renderers/codespace.js +103 -0
  32. package/lib/renderers/common.d.ts +24 -0
  33. package/lib/renderers/common.js +88 -0
  34. package/lib/renderers/discussion.d.ts +16 -0
  35. package/lib/renderers/discussion.js +101 -0
  36. package/lib/renderers/gist.d.ts +15 -0
  37. package/lib/renderers/gist.js +99 -0
  38. package/lib/renderers/issue.d.ts +4 -0
  39. package/lib/renderers/issue.js +70 -0
  40. package/lib/renderers/label.d.ts +4 -0
  41. package/lib/renderers/label.js +48 -0
  42. package/lib/renderers/markdown.d.ts +19 -32
  43. package/lib/renderers/markdown.js +19 -624
  44. package/lib/renderers/pr.d.ts +4 -0
  45. package/lib/renderers/pr.js +107 -0
  46. package/lib/renderers/project.d.ts +20 -0
  47. package/lib/renderers/project.js +128 -0
  48. package/lib/renderers/release.d.ts +4 -0
  49. package/lib/renderers/release.js +74 -0
  50. package/lib/renderers/repo.d.ts +4 -0
  51. package/lib/renderers/repo.js +149 -0
  52. package/lib/renderers/ruleset.d.ts +4 -0
  53. package/lib/renderers/ruleset.js +74 -0
  54. package/lib/renderers/run.d.ts +4 -0
  55. package/lib/renderers/run.js +79 -0
  56. package/lib/renderers/skill.d.ts +34 -0
  57. package/lib/renderers/skill.js +241 -0
  58. package/lib/renderers/status.d.ts +28 -0
  59. package/lib/renderers/status.js +176 -0
  60. package/lib/telemetry.d.ts +70 -0
  61. package/lib/telemetry.js +161 -0
  62. package/lib/tools/agent-task.d.ts +54 -0
  63. package/lib/tools/agent-task.js +135 -0
  64. package/lib/tools/cache.d.ts +64 -0
  65. package/lib/tools/cache.js +186 -0
  66. package/lib/tools/codespace.d.ts +51 -0
  67. package/lib/tools/codespace.js +154 -0
  68. package/lib/tools/discussion.d.ts +70 -0
  69. package/lib/tools/discussion.js +195 -0
  70. package/lib/tools/gist.d.ts +155 -0
  71. package/lib/tools/gist.js +190 -0
  72. package/lib/tools/help.js +214 -13
  73. package/lib/tools/image-utils.d.ts +8 -0
  74. package/lib/tools/image-utils.js +34 -0
  75. package/lib/tools/issue.d.ts +8 -1
  76. package/lib/tools/issue.js +26 -4
  77. package/lib/tools/label.d.ts +80 -0
  78. package/lib/tools/label.js +237 -0
  79. package/lib/tools/pr.d.ts +8 -0
  80. package/lib/tools/pr.js +48 -3
  81. package/lib/tools/project.d.ts +80 -0
  82. package/lib/tools/project.js +241 -0
  83. package/lib/tools/repo.js +13 -1
  84. package/lib/tools/ruleset.d.ts +80 -0
  85. package/lib/tools/ruleset.js +177 -0
  86. package/lib/tools/skill.d.ts +24 -1
  87. package/lib/tools/skill.js +115 -5
  88. package/lib/tools/status.d.ts +40 -0
  89. package/lib/tools/status.js +58 -0
  90. package/lib/types.d.ts +44 -0
  91. package/lib/ui/auth-guide-card.d.ts +4 -0
  92. package/lib/ui/auth-guide-card.js +161 -0
  93. package/lib/ui/branch-selector.d.ts +10 -0
  94. package/lib/ui/branch-selector.js +163 -0
  95. package/lib/ui/commit-modal.d.ts +8 -0
  96. package/lib/ui/commit-modal.js +219 -0
  97. package/lib/ui/help-modal.d.ts +15 -0
  98. package/lib/ui/help-modal.js +202 -0
  99. package/lib/ui/icons.d.ts +7 -0
  100. package/lib/ui/icons.js +53 -0
  101. package/lib/ui/network-banner.d.ts +10 -0
  102. package/lib/ui/network-banner.js +54 -0
  103. package/lib/ui/storage.d.ts +35 -0
  104. package/lib/ui/storage.js +158 -0
  105. package/lib/ui/styles.d.ts +6 -0
  106. package/lib/ui/styles.js +1544 -0
  107. package/lib/ui/sync-settings-modal.d.ts +11 -0
  108. package/lib/ui/sync-settings-modal.js +350 -0
  109. package/lib/ui/sync-status-badge.d.ts +8 -0
  110. package/lib/ui/sync-status-badge.js +64 -0
  111. package/lib/ui/upgrade-card.d.ts +7 -0
  112. package/lib/ui/upgrade-card.js +222 -0
  113. package/lib/ui/utils.d.ts +45 -0
  114. package/lib/ui/utils.js +226 -0
  115. package/lib/ui/workspace-panel.d.ts +4 -0
  116. package/lib/ui/workspace-panel.js +489 -0
  117. package/lib/upgrade-manager.d.ts +5 -1
  118. package/lib/upgrade-manager.js +24 -1
  119. package/lib/version.d.ts +1 -1
  120. package/lib/version.js +1 -1
  121. package/package.json +2 -2
@@ -0,0 +1,190 @@
1
+ import { renderGistMarkdown, parseGistListOutput } from '../renderers/markdown.js';
2
+ export function createGistTool(executor) {
3
+ return {
4
+ name: 'github_gist',
5
+ description: '管理 GitHub Gist 代码片段与诊断留痕:查看 Gist 列表、检视代码正文 (view)、通过文本内容或本地文件创建新 Gist (create,默认安全 secret 模式)、编辑以及安全删除。删除操作不可逆,需 confirm: true。',
6
+ parameters: {
7
+ type: 'object',
8
+ properties: {
9
+ action: {
10
+ type: 'string',
11
+ enum: ['list', 'view', 'create', 'edit', 'delete'],
12
+ description: 'Gist 操作类型。list 查看列表、view 查看正文、create 新建、edit 修改、delete 物理删除(需 confirm)。',
13
+ },
14
+ gist_id: {
15
+ type: 'string',
16
+ description: 'Gist 唯一 ID 或完整 URL(view, edit, delete 动作必需,例如 "5b0e0062eb8e9654adad7bb1d81cc75f")。',
17
+ },
18
+ filename: {
19
+ type: 'string',
20
+ description: '文件名(view 动作指定查看文件、create 动作命名单文件、edit 动作指定编辑文件;默认 "snippet.txt")。',
21
+ },
22
+ files: {
23
+ type: 'array',
24
+ items: { type: 'string' },
25
+ description: '本地文件路径列表(create 动作上传多文件时使用)。',
26
+ },
27
+ content: {
28
+ type: 'string',
29
+ description: '代码片段或文本正文内容(create 动作直接传正文时使用,通过 stdin 安全管道传入)。',
30
+ },
31
+ description: {
32
+ type: 'string',
33
+ description: 'Gist 描述摘要或用途备注(create, edit 动作可选)。',
34
+ },
35
+ public: {
36
+ type: 'boolean',
37
+ description: '是否公开创建为 Public Gist(create 动作可选,默认 false 即 Secret 弱保密模式)。',
38
+ },
39
+ filter: {
40
+ type: 'string',
41
+ description: '按正则表达式过滤检索描述或文件内容(list 动作可选)。',
42
+ },
43
+ limit: {
44
+ type: 'number',
45
+ description: '返回 Gist 数量上限(list 动作使用,默认 10)。',
46
+ },
47
+ confirm: {
48
+ type: 'boolean',
49
+ description: '高危破坏性操作确认标志(delete 动作必需)。未传 true 时输出影响评估报告并拦截。',
50
+ },
51
+ },
52
+ required: ['action'],
53
+ },
54
+ output: {
55
+ schema: { type: 'object' },
56
+ render(args, value) {
57
+ return [{ type: 'text', text: renderGistMarkdown(args, value) }];
58
+ },
59
+ },
60
+ async execute(args, execContext) {
61
+ const cwd = execContext?.cwd || process.cwd();
62
+ const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
63
+ const runOpts = { cwd, sessionId, rawText: true };
64
+ switch (args.action) {
65
+ case 'list': {
66
+ const listCmd = ['gist', 'list'];
67
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 10;
68
+ listCmd.push('-L', String(limit));
69
+ if (args.public === true) {
70
+ listCmd.push('--public');
71
+ }
72
+ else if (args.public === false) {
73
+ listCmd.push('--secret');
74
+ }
75
+ if (args.filter) {
76
+ listCmd.push('--filter', String(args.filter));
77
+ }
78
+ const res = await executor.run(listCmd, runOpts);
79
+ if (!res.ok)
80
+ throw new Error(res.error);
81
+ const items = parseGistListOutput(res.rawOutput || '');
82
+ return { items, count: items.length, rawText: res.rawOutput || '' };
83
+ }
84
+ case 'view': {
85
+ if (!args.gist_id) {
86
+ throw new Error('查看 Gist 时 `gist_id` 参数为必填项(可提供 Gist 数字字母 ID 或完整 URL)。');
87
+ }
88
+ const viewCmd = ['gist', 'view', String(args.gist_id), '-r'];
89
+ if (args.filename) {
90
+ viewCmd.push('-f', String(args.filename));
91
+ }
92
+ const res = await executor.run(viewCmd, runOpts);
93
+ if (!res.ok)
94
+ throw new Error(res.error);
95
+ return {
96
+ gist_id: String(args.gist_id),
97
+ filename: args.filename,
98
+ content: res.rawOutput || '',
99
+ };
100
+ }
101
+ case 'create': {
102
+ if (!args.content && (!args.files || !Array.isArray(args.files) || args.files.length === 0)) {
103
+ throw new Error('创建 Gist 时必须提供 `content`(代码片段文本)或 `files`(本地文件路径数组)。');
104
+ }
105
+ const createCmd = ['gist', 'create'];
106
+ let stdinData;
107
+ if (args.content) {
108
+ createCmd.push('-');
109
+ const fn = args.filename ? String(args.filename).trim() : 'snippet.txt';
110
+ createCmd.push('-f', fn);
111
+ stdinData = String(args.content);
112
+ }
113
+ else if (args.files) {
114
+ for (const f of args.files) {
115
+ createCmd.push(String(f));
116
+ }
117
+ }
118
+ if (args.public === true) {
119
+ createCmd.push('--public');
120
+ }
121
+ if (args.description) {
122
+ createCmd.push('-d', String(args.description));
123
+ }
124
+ const res = await executor.run(createCmd, { ...runOpts, stdin: stdinData });
125
+ if (!res.ok)
126
+ throw new Error(res.error);
127
+ const rawUrl = (res.rawOutput || '').trim();
128
+ const urlMatch = rawUrl.match(/gist\.github\.com\/([^/]+\/)?([0-9a-fA-F]+)/i);
129
+ const gistId = urlMatch ? urlMatch[2] : rawUrl;
130
+ return {
131
+ success: true,
132
+ url: rawUrl,
133
+ gist_id: gistId,
134
+ public: args.public === true,
135
+ description: args.description,
136
+ message: `✅ 成功创建 Gist: ${rawUrl}`,
137
+ };
138
+ }
139
+ case 'edit': {
140
+ if (!args.gist_id) {
141
+ throw new Error('编辑 Gist 时 `gist_id` 参数为必填项。');
142
+ }
143
+ const editCmd = ['gist', 'edit', String(args.gist_id)];
144
+ if (args.description) {
145
+ editCmd.push('-d', String(args.description));
146
+ }
147
+ if (args.filename) {
148
+ editCmd.push('-f', String(args.filename));
149
+ }
150
+ const res = await executor.run(editCmd, runOpts);
151
+ if (!res.ok)
152
+ throw new Error(res.error);
153
+ return {
154
+ success: true,
155
+ gist_id: String(args.gist_id),
156
+ message: `✅ 成功更新 Gist "${args.gist_id}"`,
157
+ detail: res.rawOutput || '',
158
+ };
159
+ }
160
+ case 'delete': {
161
+ if (!args.gist_id) {
162
+ throw new Error('删除 Gist 时 `gist_id` 参数为必填项。');
163
+ }
164
+ if (args.confirm !== true) {
165
+ return {
166
+ status: 'AWAITING_CONFIRMATION',
167
+ action: 'delete',
168
+ gist_id: String(args.gist_id),
169
+ warning: `⚠️ 高危破坏性操作:即将永久删除 Gist 代码片段 "${args.gist_id}"`,
170
+ impact: `删除 Gist 为物理删除,无法通过 GitHub API 恢复,所有引用该 Gist URL 的外部链接将失效并返回 404。`,
171
+ requiredAction: `如确认彻底删除,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_gist(action: 'delete', gist_id: '${args.gist_id}', confirm: true)\``,
172
+ };
173
+ }
174
+ const delCmd = ['gist', 'delete', String(args.gist_id), '--yes'];
175
+ const res = await executor.run(delCmd, runOpts);
176
+ if (!res.ok)
177
+ throw new Error(res.error);
178
+ return {
179
+ success: true,
180
+ gist_id: String(args.gist_id),
181
+ message: `🗑️ 成功删除 Gist 代码片段 "${args.gist_id}"`,
182
+ detail: res.rawOutput || '',
183
+ };
184
+ }
185
+ default:
186
+ throw new Error(`不支持的 gist 操作: "${args.action}"。可选值: list, view, create, edit, delete。`);
187
+ }
188
+ },
189
+ };
190
+ }
package/lib/tools/help.js CHANGED
@@ -13,9 +13,19 @@ const KNOWLEDGE_BASE = {
13
13
  | \`github_release\` | 版本发布与构建资产交付 | 查看发版历史、自动生成 Release Notes、上传 .tgz 等构建产物 |
14
14
  | \`github_secret\` | CI/CD 密钥与变量管理 | 查看 NPM_TOKEN 等密钥清单、经 stdin 安全写入密文、删除密钥;kind:'variable' 管理明文变量(set/get/delete) |
15
15
  | \`github_skill\` | 跨 Agent 开源 Skills 生态发现 | 检索/预览/安装 GitHub 上的 Agent Skills (gh preview 特性) |
16
+ | \`github_label\` | 标签体系管理与跨仓库克隆 | 查看标签列表、创建/编辑标签、从源仓库一键 clone 标签体系、删除标签(需 confirm) |
17
+ | \`github_ruleset\` | 分支保护规则与策略诊断 | 检查分支适用的合并策略规则 (check)、查看规则集列表与详情,权威回答“PR 为何无法合并” |
18
+ | \`github_cache\` | Actions 构建缓存盘点与安全清理 | 查看缓存列表与体积占用、按 ID/Key 删除指定缓存、全量清空缓存(需 confirm) |
19
+ | \`github_gist\` | 代码片段与调试日志分享治理 | 查看 Gist 列表与代码正文、创建 Secret/Public 代码片段、编辑与物理删除(需 confirm) |
20
+ | \`github_status\` | 跨仓库待办与动态聚合看板 | 账号维度汇总指派给我的 Issue/PR、等待评审、被 @提及 以及订阅仓库近期动态 |
21
+ | \`github_agent_task\` | 云端智能体编码任务会话治理 | 查阅已分派任务列表、检视任务状态与会话日志、向云端 Agent 发起新编码重构任务 |
22
+ | \`github_discussion\` | 团队技术讨论与开源社区 RFC 闭环 | 浏览讨论列表、检视正文与评论楼层、发起新讨论、编辑正文与追加回复 |
23
+ | \`github_project\` | Projects v2 项目看板与任务规划 | 查阅项目列表/详情、浏览看板条目 (Items)、添加 Issue/PR 到看板、创建草稿任务与删除条目 |
24
+ | \`github_codespace\` | 云端远程开发环境管控 (MVP) | 查阅运行中实例、检视机器规格配置、停机防计费 (stop)、安全销毁环境 (delete,需 confirm) |
16
25
  | \`github_help\` | 路由导航、官方语法速查与故障自愈 | 遇到不确定的操作、搜索语法或接口报错时查询用法与诊断配方 |
17
26
 
18
27
  💡 **使用准则**:优先调用高层语义工具(pr / issue / run / repo / release / secret);当高层工具无法满足特殊接口需求时,再降级使用 \`github_api\`。
28
+ 💬 **人类指令**:聊天框支持 \`/gh status\`(大盘看板)、\`/gh upgrade\`(一键升级插件)、\`/gh help\`(快捷帮助)。
19
29
  🗺️ **命令对照**:不确定某 gh 命令对应哪个工具?\`github_help(topic: 'mapping')\` 查询完整映射表(含未覆盖域的 API 引导)。`,
20
30
  pr: `### 🔀 github_pr 详细操作指南
21
31
 
@@ -35,7 +45,9 @@ const KNOWLEDGE_BASE = {
35
45
  - **反向提交**: \`github_pr(action: 'revert', pr_number: 123, confirm: true)\`
36
46
  - 在远端创建反向提交与新 PR(非本地操作),需 confirm。
37
47
  - **分支落后自愈**: \`github_pr(action: 'update_branch', pr_number: 123)\`
38
- - 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI 门禁失败。`,
48
+ - 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI 门禁失败。
49
+ - **上传本地附件/图片**: \`github_pr(action: 'create', title: 'UI 重构', body: '对比图如下', attach_images: ['./before.png', './after.png'])\`
50
+ - 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个文件,禁止网络 URL,与 view_images 对称闭环。`,
39
51
  issue: `### 🎯 github_issue 详细操作指南
40
52
 
41
53
  - **筛选 Issue**: \`github_issue(action: 'list', state: 'open' | 'closed' | 'all', labels: ['bug'], assignees: ['octocat'], limit: 20)\`
@@ -45,6 +57,8 @@ const KNOWLEDGE_BASE = {
45
57
  - 经 gh api 下载正文+评论图片到本地(~/.dsh/data/dsh-github-flow/images/,内容寻址去重),以图片内容块返回,Agent 可直接查看用户报障截图;魔数校验 + 字节护栏(单图 5MB、目录 15MB)。
46
58
  - **按 URL 精准获取**: 附加 \`image_urls: ['<view 返回清单中的 URL>']\` 只下载指定图片(可单张可批量);传入 URL 必须在清单内(防任意下载)。
47
59
  - 触达目录上限时返回 \`remaining\` 结构化清单,直接作为下一批 image_urls 续传即可。
60
+ - **上传本地附件/图片**: \`github_issue(action: 'comment', issue_number: 42, body: '复现证据', attach_images: ['./bug.png'])\`
61
+ - 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个本地文件(绝对路径或工作区相对路径,禁止网络 URL)。
48
62
  - **关联开发分支**: \`github_issue(action: 'develop', issue_number: 42, branch_name: 'fix-login-bug')\`
49
63
  - 自动在远端和本地创建带有官方关联关系的开发分支并切换过去,实现“读需求 -> 开分支写代码”的无缝衔接。
50
64
  - **编辑 Issue**: \`github_issue(action: 'edit', issue_number: 42, title: '新标题', add_labels: ['p1'], remove_labels: ['triage'])\`
@@ -146,7 +160,111 @@ const KNOWLEDGE_BASE = {
146
160
  - install 会写本地磁盘,必须 \`confirm: true\`;scope 默认 project(当前 git 仓库)。
147
161
  - 支持的 agent 值:claude-code / cursor / github-copilot / codex / gemini-cli 等。
148
162
  - **查看已安装**: \`github_skill(action: 'list')\`
163
+ - **规范自检与发布**:
164
+ - \`github_skill(action: 'publish')\` —— 默认 \`--dry-run\` 模式,对本地技能目录进行 agentskills.io 合规性检查(命名规范、目录匹配、必需 frontmatter 字段等),零副作用安全自检。
165
+ - \`github_skill(action: 'publish', fix: true)\` —— 自动清洗并剥离残留的安装元数据(\`metadata.github-*\`),修改本地文件但不发布。
166
+ - \`github_skill(action: 'publish', tag: 'v1.0.0', dry_run: false, confirm: true)\` —— 正式发布,强制要求指定 \`tag\` 并附带 \`confirm: true\`(防无头挂起与误发布),自动创建 GitHub Release。
149
167
  - ⚠️ 若报「unknown command」,说明宿主机 gh 版本过低,需升级到 2.9x 以上。`,
168
+ label: `### 🏷️ github_label 详细操作指南
169
+
170
+ - **查看标签列表**: \`github_label(action: 'list', limit: 30, search: 'bug')\`
171
+ - 支持按名称或描述模糊检索,支持 \`sort: 'created' | 'name'\` 与 \`order: 'asc' | 'desc'\`。
172
+ - **创建标签**: \`github_label(action: 'create', name: 'priority/p0', color: 'd73a4a', description: '最高优先级缺陷')\`
173
+ - 颜色自动去除前导 \`#\` 并校验 6 位十六进制;已存在时可附加 \`force: true\` 强制覆盖更新。
174
+ - **编辑标签**: \`github_label(action: 'edit', name: 'old-name', new_name: 'new-name', color: '0075ca')\`
175
+ - 支持修改描述、色值以及重命名标签(\`new_name\`)。
176
+ - **删除标签**: \`github_label(action: 'delete', name: 'deprecated', confirm: true)\`
177
+ - 高危破坏性操作:会导致所有关联 Issue/PR 自动解绑该标记,必须显式附加 \`confirm: true\`;底层自动传 \`--yes\` 杜绝无头交互挂起。
178
+ - **跨仓库克隆标签**: \`github_label(action: 'clone', source_repo: 'cli/cli', force: true)\`
179
+ - 将源仓库的标准标签体系一键同步到当前仓库(或指定的 \`repo\`),新仓库初始化刚需。`,
180
+ ruleset: `### 🛡️ github_ruleset 详细操作指南
181
+
182
+ - **检查分支合并策略约束**: \`github_ruleset(action: 'check', branch: 'master')\`
183
+ - **PR 无法合并的策略视角权威答案**:输出指定分支匹配到的全部硬性规则约束(必审人数、指定 CI、禁止 force push 等)。
184
+ - 若无规则约束,输出排障引导,建议联动 \`github_pr(action: 'checks')\` 检查 CI 执行状态。
185
+ - 检查默认分支可附加 \`default_branch: true\`。
186
+ - **查看规则集列表**: \`github_ruleset(action: 'list', limit: 30)\`
187
+ - 默认包含上级组织继承规则;附加 \`no_parents: true\` 仅查看仓库自身规则集;支持 \`org: 'org-name'\` 查看组织级规则集(需 \`admin:org\` 权限)。
188
+ - **查看规则集详情**: \`github_ruleset(action: 'view', ruleset_id: '4898070')\`
189
+ - 查看具体 Ruleset 详细配置规则与适用范围,\`ruleset_id\` 必填。`,
190
+ cache: `### ⚡ github_cache 详细操作指南
191
+
192
+ - **查看缓存列表与体积**: \`github_cache(action: 'list', limit: 30, sort: 'size_in_bytes', order: 'desc')\`
193
+ - 返回仓库 Actions 构建缓存清单、占用体积(自动换算为 KB/MB/GB)、关联 Git Ref 以及最后访问时间。
194
+ - 可附加 \`key: 'npm-cache'\` 过滤特定前缀,或通过 \`ref: 'refs/heads/main'\` 限定分支。
195
+ - **精准删除单条缓存**:
196
+ - 按数字 ID 删除: \`github_cache(action: 'delete', cache_id: '12345678')\`
197
+ - 按 Key 键名删除: \`github_cache(action: 'delete', key: 'Linux-npm-18-abcd1234')\`
198
+ - **全量清空仓库缓存**:
199
+ \`github_cache(action: 'delete', all: true, confirm: true)\`
200
+ - 批量破坏性操作:会导致后续流水线构建重新拉取依赖,必须显式附加 \`confirm: true\`;底层自动注入 \`--succeed-on-no-caches\` 保证幂等执行。`,
201
+ gist: `### 📝 github_gist 详细操作指南
202
+
203
+ - **查看我的 Gist 列表**: \`github_gist(action: 'list', limit: 10)\`
204
+ - 默认查看全部;可附加 \`public: true\` 仅看公开、\`public: false\` 仅看私密;支持 \`filter: 'regex'\` 正则过滤。
205
+ - **检视代码片段正文**: \`github_gist(action: 'view', gist_id: '5b0e0062eb8e9654adad7bb1d81cc75f')\`
206
+ - 直接输出原始代码正文,\`gist_id\` 必填(防交互挂起);可选 \`filename: 'main.py'\` 限定文件。
207
+ - **创建 Gist 代码片段**:
208
+ - 直接传文本内容: \`github_gist(action: 'create', content: 'console.log("hello");', filename: 'demo.ts', description: '排错测试片段')\`
209
+ - 传本地文件列表: \`github_gist(action: 'create', files: ['./build.log', './error.txt'], description: 'CI 失败排错日志')\`
210
+ - 默认创建 Secret Gist(Unlisted 弱保密,拥有链接者可访问);若要公开需显式附加 \`public: true\`。
211
+ - ⚠️ **安全须知**: 切勿将敏感凭据、API Token 存入 Gist。
212
+ - **编辑 Gist**: \`github_gist(action: 'edit', gist_id: '...', description: '新描述', filename: 'patch.diff')\`
213
+ - **删除 Gist**: \`github_gist(action: 'delete', gist_id: '...', confirm: true)\`
214
+ - 物理删除不可逆,必须显式附加 \`confirm: true\`;底层自动注入 \`--yes\` 杜绝交互式卡死。`,
215
+ status: `### 🧭 github_status 详细操作指南
216
+
217
+ - **查看个人待办与动态大盘**: \`github_status()\` 或 \`github_status(action: 'view')\`
218
+ - 账号维度一站式汇总:指派给我的 Issues、指派给我的 PR、等待我评审的 PR、近期 @提及 以及订阅仓库最新动态。
219
+ - **限定组织**: \`github_status(org: 'my-org')\`
220
+ - 仅汇报指定组织内部的待办事项与动态。
221
+ - **排除噪音仓库**: \`github_status(exclude: ['owner/noisy-repo'])\`
222
+ - 过滤高频刷屏但无需关注的仓库活动。
223
+ - **查看工具链运行时健康度**: \`github_status(action: 'health')\`
224
+ - 审计当前会话 18 大工具矩阵的综合健康分、API 降级率、安全防线拦截频次与针对性自愈建议。`,
225
+ agent_task: `### 🤖 github_agent_task 智能体任务指南
226
+
227
+ 管理与调度 GitHub Copilot Coding Agent 云端智能体会话:
228
+ - **查看任务列表**: \`github_agent_task(action: 'list', limit: 30)\`
229
+ - 账号全局任务全景列表(注:上游 gh CLI 原生不接收 -R/--repo 限制,默认列出当前用户所有活跃会话)。
230
+ - **检视任务会话与日志**: \`github_agent_task(action: 'view', task_id: 'e2fa49d2-...', log: true)\`
231
+ - 必须提供 \`task_id\`(支持 Session ID、PR 编号或 URL),支持 \`repo: 'owner/repo'\`,附加 \`log: true\` 可完整拉取任务执行过程日志。
232
+ - **派发新编码任务**: \`github_agent_task(action: 'create', prompt: '为用户模块补全单元测试', base_branch: 'main', repo: 'owner/repo')\`
233
+ - 支持附加 \`custom_agent: 'my-agent'\` 指定仓库预设的智能体定义,支持 \`repo\` 定向派发。`,
234
+ discussion: `### 💬 github_discussion 讨论区操作指南
235
+
236
+ 基于 GitHub CLI 的 \`gh discussion\` preview 特性,支持团队架构探讨、RFC 与社区互动:
237
+ - **查看讨论列表**: \`github_discussion(action: 'list', category: 'General', state: 'open', limit: 30)\`
238
+ - 支持按 \`category\`(分类)、\`state\`(open/closed/all)、\`labels\` 以及 \`search\`(关键词)过滤;状态自动结构化映射为 OPEN / CLOSED / ANSWERED。
239
+ - **检视讨论正文与回复楼层**: \`github_discussion(action: 'view', discussion_number: 123, comments: true)\`
240
+ - **发起新讨论**: \`github_discussion(action: 'create', title: 'RFC: 架构演进方案', category: 'Ideas', body: '详细方案...')\`
241
+ - \`title\`、\`category\` 与 \`body\` 均为必填项。
242
+ - **编辑讨论**: \`github_discussion(action: 'edit', discussion_number: 123, title: '新标题', body: '修正后的正文')\`
243
+ - **发表讨论回复**: \`github_discussion(action: 'comment', discussion_number: 123, body: '赞同该方案!')\`
244
+ - 💡 **提示**:若报错「discussions disabled」,说明目标仓库未在 GitHub 仓库「Settings -> General -> Features」中启用 Discussions 社区功能。`,
245
+ project: `### 📋 github_project 项目看板与规划指南
246
+
247
+ 基于 GitHub Projects v2,支持项目看板与任务全生命周期管理:
248
+ - **查看项目列表**: \`github_project(action: 'list', owner: '@me', closed: false)\`
249
+ - 缺省 owner 为 \`@me\`(当前用户),可指定组织或用户名称。
250
+ - **查看项目详情**: \`github_project(action: 'view', project_number: 1)\`
251
+ - **浏览看板条目 (Items)**: \`github_project(action: 'item-list', project_number: 1, query: 'is:open')\`
252
+ - **添加现有 Issue/PR 到看板**: \`github_project(action: 'item-add', project_number: 1, url: 'https://github.com/owner/repo/issues/10')\`
253
+ - **创建草稿条目**: \`github_project(action: 'item-create', project_number: 1, title: '待调研技术方案', body: '方案说明...')\`
254
+ - **删除看板条目**: \`github_project(action: 'item-delete', item_id: 'PVTI_...', confirm: true)\`
255
+ - 必须附加 \`confirm: true\`;仅解除条目与看板的关联,不删除 Issue/PR 原型。
256
+ - 🔑 若报缺少权限,在终端执行单行自愈授权命令:\`gh auth refresh -s read:project\``,
257
+ codespace: `### ☁️ github_codespace 云端开发环境指南
258
+
259
+ 管理 GitHub Codespaces 云端远程工作区(精选高频实用 MVP):
260
+ - **查看云端环境列表**: \`github_codespace(action: 'list', limit: 30)\`
261
+ - **检视实例配置与状态**: \`github_codespace(action: 'view', codespace: 'fuzzy-spoon-69vj')\`
262
+ - 必须显式提供 \`codespace\` 名称,杜绝交互式挂起。
263
+ - **停止运行环境 (防计费)**: \`github_codespace(action: 'stop', codespace: 'fuzzy-spoon-69vj')\`
264
+ - 立即向云端实例发送停机信号,防止持续扣除计算核时配额。
265
+ - **安全销毁实例**: \`github_codespace(action: 'delete', codespace: 'fuzzy-spoon-69vj', confirm: true)\`
266
+ - 物理销毁不可逆,必须附加 \`confirm: true\`。
267
+ - 🔑 若报缺少权限,在终端执行单行自愈授权命令:\`gh auth refresh -h github.com -s codespace\``,
150
268
  mapping: `### 🗺️ gh 命令 ↔ 插件工具映射总表
151
269
 
152
270
  #### 一、已覆盖域映射(语义化 action 精确对照)
@@ -207,33 +325,96 @@ const KNOWLEDGE_BASE = {
207
325
  | \`gh variable list/set/get/delete\` | \`github_secret(action:…, kind:'variable')\` | CI 明文:-b 直传、get 可回显 |
208
326
  | (gh secret get 不存在) | — | GitHub 侧加密存储,物理不可读取 |
209
327
 
210
- **Skill(github_skill,4 action)**
211
- \`gh skill search/preview/install/list\` → \`github_skill(action:…)\`;install 需 confirm(写磁盘)。
328
+ **Skill(github_skill,5 action)**
329
+ \`gh skill search/preview/install/list/publish\` → \`github_skill(action:…)\`;install 需 confirm(写磁盘);publish 默认 dry-run 安全自检,正式发布需 tag 与 confirm。
330
+
331
+ **Label(github_label,5 action)**
332
+ | gh 命令 | 插件调用 | 备注 |
333
+ | :--- | :--- | :--- |
334
+ | \`gh label list\` | \`github_label(action:'list')\` | 支持搜索与排序 |
335
+ | \`gh label create\` | \`github_label(action:'create', name:…, color:…)\` | 颜色十六进制校验 |
336
+ | \`gh label edit\` | \`github_label(action:'edit', name:…, new_name:…)\` | 支持重命名 |
337
+ | \`gh label delete\` | \`github_label(action:'delete', name:…, confirm:true)\` | 高危解绑,需 confirm |
338
+ | \`gh label clone\` | \`github_label(action:'clone', source_repo:…)\` | 跨仓库一键克隆 |
339
+
340
+ **Ruleset(github_ruleset,3 action)**
341
+ | gh 命令 | 插件调用 | 备注 |
342
+ | :--- | :--- | :--- |
343
+ | \`gh ruleset check\` | \`github_ruleset(action:'check', branch:…)\` | PR 策略排障核心 |
344
+ | \`gh ruleset list\` | \`github_ruleset(action:'list')\` | 规则集概览 |
345
+ | \`gh ruleset view\` | \`github_ruleset(action:'view', ruleset_id:…)\` | 规则详情,ID 必填 |
346
+
347
+ **Cache(github_cache,2 action)**
348
+ | gh 命令 | 插件调用 | 备注 |
349
+ | :--- | :--- | :--- |
350
+ | \`gh cache list\` | \`github_cache(action:'list')\` | 缓存盘点与过滤 |
351
+ | \`gh cache delete\` | \`github_cache(action:'delete', cache_id:…)\` | all:true 需 confirm |
352
+
353
+ **Gist(github_gist,5 action)**
354
+ | gh 命令 | 插件调用 | 备注 |
355
+ | :--- | :--- | :--- |
356
+ | \`gh gist list/view\` | \`github_gist(action:'list'/'view')\` | 代码正文检视 |
357
+ | \`gh gist create\` | \`github_gist(action:'create', content:…)\` | 默认 secret 弱保密 |
358
+ | \`gh gist edit\` | \`github_gist(action:'edit', gist_id:…)\` | 描述与文件修改 |
359
+ | \`gh gist delete\` | \`github_gist(action:'delete', gist_id:…, confirm:true)\` | 物理删除,需 confirm |
360
+
361
+ **Status(github_status,2 action)**
362
+ | gh 命令 | 插件调用 | 备注 |
363
+ | :--- | :--- | :--- |
364
+ | \`gh status\` | \`github_status(action:'view')\` | 跨仓待办大盘与动态 |
365
+ | — | \`github_status(action:'health')\` | 工具链健康度与降级率审计 |
366
+
367
+ **Agent Task(github_agent_task,3 action)**
368
+ | gh 命令 | 插件调用 | 备注 |
369
+ | :--- | :--- | :--- |
370
+ | \`gh agent-task list\` | \`github_agent_task(action:'list')\` | 智能体编码任务列表 |
371
+ | \`gh agent-task view\` | \`github_agent_task(action:'view', task_id:…)\` | 任务状态与日志,ID 必填 |
372
+ | \`gh agent-task create\` | \`github_agent_task(action:'create', prompt:…)\` | 向云端 Agent 派发任务 |
373
+
374
+ **Discussion(github_discussion,5 action)**
375
+ | gh 命令 | 插件调用 | 备注 |
376
+ | :--- | :--- | :--- |
377
+ | \`gh discussion list\` | \`github_discussion(action:'list')\` | 社区与团队讨论列表 |
378
+ | \`gh discussion view\` | \`github_discussion(action:'view', discussion_number:…)\` | 讨论正文与回复楼层 |
379
+ | \`gh discussion create\` | \`github_discussion(action:'create', title:…, category:…, body:…)\` | 发起新讨论,三项必填 |
380
+ | \`gh discussion edit\` | \`github_discussion(action:'edit', discussion_number:…)\` | 修改讨论标题或正文 |
381
+ | \`gh discussion comment\` | \`github_discussion(action:'comment', discussion_number:…, body:…)\` | 追加讨论评论 |
382
+
383
+ **Project(github_project,6 action)**
384
+ | gh 命令 | 插件调用 | 备注 |
385
+ | :--- | :--- | :--- |
386
+ | \`gh project list\` | \`github_project(action:'list')\` | 项目看板列表,默认 @me |
387
+ | \`gh project view\` | \`github_project(action:'view', project_number:…)\` | 项目元数据与描述 |
388
+ | \`gh project item-list\` | \`github_project(action:'item-list', project_number:…)\` | 任务条目看板 |
389
+ | \`gh project item-add\` | \`github_project(action:'item-add', project_number:…, url:…)\` | 关联 Issue/PR 到看板 |
390
+ | \`gh project item-create\` | \`github_project(action:'item-create', project_number:…, title:…)\` | 创建草稿条目 |
391
+ | \`gh project item-delete\` | \`github_project(action:'item-delete', item_id:…, confirm:true)\` | 移除条目,需 confirm |
392
+
393
+ **Codespace(github_codespace,4 action)**
394
+ | gh 命令 | 插件调用 | 备注 |
395
+ | :--- | :--- | :--- |
396
+ | \`gh codespace list\` | \`github_codespace(action:'list')\` | 云端开发环境列表 |
397
+ | \`gh codespace view\` | \`github_codespace(action:'view', codespace:…)\` | 实例详情,名称必填 |
398
+ | \`gh codespace stop\` | \`github_codespace(action:'stop', codespace:…)\` | 关机防计费 |
399
+ | \`gh codespace delete\` | \`github_codespace(action:'delete', codespace:…, confirm:true)\` | 销毁实例,需 confirm |
212
400
 
213
401
  #### 二、归属提示(命令域与工具名不同构)
214
402
 
215
403
  - \`gh workflow …\` / \`gh search …\` / \`gh variable …\` 分别归 **github_run / github_repo / github_secret**(不是独立工具)
216
404
  - \`gh api\` 的等价物是 \`github_api\`(全端点逃生门,支持 raw/paginate/jq)
217
405
  - 图片查看:\`github_issue\`/\`github_pr\` 的 \`view_images\`、\`github_repo\` 的 \`readme\` + image_urls(插件独有能力,gh 无对应命令)
406
+ - 附件上传:\`github_issue\`/\`github_pr\` 的 \`attach_images\` 映射 gh ≥2.99 原生可重复 \`--attach\` flag(支持 create/edit/comment)
218
407
 
219
408
  #### 三、未覆盖域引导(经 github_api 调用)
220
409
 
221
410
  | gh 域 | 用途 | 推荐 REST 端点 |
222
411
  | :--- | :--- | :--- |
223
- | \`gh label\` | 标签管理 | \`repos/{o}/{r}/labels\` |
224
- | \`gh cache\` | Actions 缓存 | \`repos/{o}/{r}/actions/caches\` |
225
- | \`gh gist\` | 代码片段 | \`gists\` |
226
- | \`gh ruleset\` | 分支保护规则 | \`repos/{o}/{r}/rulesets\` |
227
- | \`gh project\` | Projects v2 | \`graphql\`(Projects API 仅 GraphQL) |
228
- | \`gh discussion\` | 讨论 | \`graphql\`(需仓库开启 discussions) |
229
- | \`gh codespace\` | 云端开发环境 | \`user/codespaces\` |
230
412
  | \`gh org\` | 组织信息 | \`orgs/{org}\` |
231
413
  | \`gh auth/config/extension/alias\` | 本机配置类 | 多数无需 API(本机文件/命令) |
232
414
  | \`gh browse\` | 打开浏览器 | 直接给用户 URL 即可 |
233
415
  | \`gh attestation/gpg-key/ssh-key\` | 签名与密钥 | \`repos/{o}/{r}/attestations\` 等 |
234
- | \`gh agent-task\` | Copilot 会话 | \`graphql\`(需 Copilot 授权) |
235
416
 
236
- > 用法示例:\`github_api(endpoint: 'repos/owner/repo/labels', method: 'POST', fields: { name: 'bug', color: 'ff0000' })\``,
417
+ > 用法示例:\`github_api(endpoint: 'repos/owner/repo/pulls')\`(原有 \`repos/{o}/{r}/labels\` 端点已由原生 \`github_label\` 工具接管)`,
237
418
  search_syntax: `### 🔍 GitHub 官方高级检索语法速查手册
238
419
 
239
420
  在 \`github_repo\`、\`github_api\` 或搜索场景中,请严格遵守 GitHub 官方搜索语法:
@@ -265,7 +446,18 @@ const KNOWLEDGE_BASE = {
265
446
  - *解法*:宿主机尚未安装 gh。Windows 运行 \`winget install GitHub.cli\`,macOS 运行 \`brew install gh\`,然后执行 \`gh auth login\` 登录。
266
447
  5. **报 \`unknown command "skill" for "gh"\`**:
267
448
  - *原因*:\`gh skill\` 是 GitHub CLI 2.9x 引入的 preview 特性,宿主机版本过低。
268
- - *解法*:升级 gh(Windows: \`winget upgrade GitHub.cli\`;macOS: \`brew upgrade gh\`)后重试;升级前可改用 \`github_api\` 检索 \`SKILL.md\` 文件路径作为替代。`
449
+ - *解法*:升级 gh(Windows: \`winget upgrade GitHub.cli\`;macOS: \`brew upgrade gh\`)后重试;升级前可改用 \`github_api\` 检索 \`SKILL.md\` 文件路径作为替代。
450
+ 6. **报 \`the token ... needs the '<scope>' scope\` (缺少权限)**:
451
+ - *原因*:GitHub Token 缺少对应操作的最小 Scope 权限(如 Codespaces 需要 \`codespace\` 权限,Projects v2 需要 \`read:project\` 权限)。
452
+ - *解法*:在系统终端执行单行自愈命令完成授权刷新(无需重启插件,即刻生效):
453
+ - Codespace: \`gh auth refresh -h github.com -s codespace\`
454
+ - Project: \`gh auth refresh -s read:project\`
455
+ 7. **报 \`the '...' repository has discussions disabled\`**:
456
+ - *原因*:目标 GitHub 仓库未开启 Discussions 社区功能。
457
+ - *解法*:需管理员前往 GitHub 网页端 \`Settings -> General -> Features\` 勾选开启 \`Discussions\`。
458
+ 8. **操作被拦截提示 \`Awaiting confirmation\`**:
459
+ - *原因*:对于高危破坏性操作(如合并 PR、删除分支、物理删除 Issue、清空构建缓存、删除密钥、销毁 Codespace),插件设置了强制安全确认防线,未确认时绝对不触发底层 CLI。
460
+ - *解法*:审阅影响评估报告后,在参数中显式传入 \`confirm: true\` 即可放行。`
269
461
  };
270
462
  export function createHelpTool(executor) {
271
463
  return {
@@ -286,6 +478,15 @@ export function createHelpTool(executor) {
286
478
  'release',
287
479
  'secret',
288
480
  'skill',
481
+ 'label',
482
+ 'ruleset',
483
+ 'cache',
484
+ 'gist',
485
+ 'status',
486
+ 'agent_task',
487
+ 'discussion',
488
+ 'project',
489
+ 'codespace',
289
490
  'mapping',
290
491
  'search_syntax',
291
492
  'troubleshooting',
@@ -77,3 +77,11 @@ export declare function downloadImages(urls: string[], run: (url: string) => Pro
77
77
  }>): Promise<ImageBatchResult>;
78
78
  /** render 的 image 内容块构造(text 块由调用方自行拼):仅双合法字段才产出块 */
79
79
  export declare function buildImageBlocks(value: any): any[];
80
+ /**
81
+ * 校验并解析本地附件路径列表(Issue/PR --attach 专用)。
82
+ * - 仅接受本地绝对路径或工作区相对路径
83
+ * - 严格拒绝 URL(HTTP/HTTPS)
84
+ * - 单批限制最多 50 个文件(GitHub 限制)
85
+ * - 校验本地文件物理存在性
86
+ */
87
+ export declare function validateAttachPaths(attachImages: unknown, cwd: string): string[];
@@ -245,3 +245,37 @@ export function buildImageBlocks(value) {
245
245
  }
246
246
  return blocks;
247
247
  }
248
+ /**
249
+ * 校验并解析本地附件路径列表(Issue/PR --attach 专用)。
250
+ * - 仅接受本地绝对路径或工作区相对路径
251
+ * - 严格拒绝 URL(HTTP/HTTPS)
252
+ * - 单批限制最多 50 个文件(GitHub 限制)
253
+ * - 校验本地文件物理存在性
254
+ */
255
+ export function validateAttachPaths(attachImages, cwd) {
256
+ if (!attachImages)
257
+ return [];
258
+ if (!Array.isArray(attachImages)) {
259
+ throw new Error('`attach_images` 必须为本地文件路径数组(string[])。');
260
+ }
261
+ if (attachImages.length > 50) {
262
+ throw new Error(`单次上传附件数量超出 GitHub 限制(单批上限 50 个文件),当前提供: ${attachImages.length} 个。`);
263
+ }
264
+ const resolvedPaths = [];
265
+ for (const item of attachImages) {
266
+ if (typeof item !== 'string' || !item.trim()) {
267
+ throw new Error('`attach_images` 数组中的文件路径不能为空。');
268
+ }
269
+ const trimmed = item.trim();
270
+ // 严格禁止 URL
271
+ if (/^https?:\/\//i.test(trimmed)) {
272
+ throw new Error(`\`attach_images\` 仅支持本地文件路径上传,不支持网络 URL: "${trimmed}"。\n若需引用网络图片,请直接在正文/评论中使用 Markdown 语法 \`![说明](URL)\`,或先将其下载至本地再上传。`);
273
+ }
274
+ const absPath = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
275
+ if (!fs.existsSync(absPath)) {
276
+ throw new Error(`指定的附件文件不存在: "${absPath}"(输入路径: "${trimmed}")。`);
277
+ }
278
+ resolvedPaths.push(absPath);
279
+ }
280
+ return resolvedPaths;
281
+ }
@@ -1,5 +1,5 @@
1
1
  import type { GhExecutor } from '../executor.js';
2
- export { extractImageUrls as extractIssueImageUrls } from './image-utils.js';
2
+ export { extractImageUrls as extractIssueImageUrls, validateAttachPaths } from './image-utils.js';
3
3
  export declare function createIssueTool(executor: GhExecutor): {
4
4
  name: string;
5
5
  description: string;
@@ -88,6 +88,13 @@ export declare function createIssueTool(executor: GhExecutor): {
88
88
  };
89
89
  description: string;
90
90
  };
91
+ attach_images: {
92
+ type: string;
93
+ items: {
94
+ type: string;
95
+ };
96
+ description: string;
97
+ };
91
98
  };
92
99
  required: string[];
93
100
  };
@@ -1,5 +1,6 @@
1
+ import { compareGhVersions } from '../executor.js';
1
2
  import { renderIssueMarkdown } from '../renderers/markdown.js';
2
- import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls } from './image-utils.js';
3
+ import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls, validateAttachPaths } from './image-utils.js';
3
4
  function normalizeOutput(data) {
4
5
  if (data && typeof data === 'object' && !Array.isArray(data)) {
5
6
  return data;
@@ -9,8 +10,8 @@ function normalizeOutput(data) {
9
10
  }
10
11
  return { result: data !== undefined && data !== null ? data : '' };
11
12
  }
12
- // 共享图片工具链的同名再导出:phase8 测试直接 import 本模块,保持路径稳定
13
- export { extractImageUrls as extractIssueImageUrls } from './image-utils.js';
13
+ // 共享图片工具链的同名再导出:保持路径与单测稳定
14
+ export { extractImageUrls as extractIssueImageUrls, validateAttachPaths } from './image-utils.js';
14
15
  export function createIssueTool(executor) {
15
16
  return {
16
17
  name: 'github_issue',
@@ -90,6 +91,11 @@ export function createIssueTool(executor) {
90
91
  items: { type: 'string' },
91
92
  description: 'view_images 可选:仅下载指定 URL 的图片(从 view 返回的 images 清单中选取,不在清单内的会被拒绝)。留空下载全部(正文+评论)。',
92
93
  },
94
+ attach_images: {
95
+ type: 'array',
96
+ items: { type: 'string' },
97
+ description: '本地图片/附件路径列表(create, edit, comment 动作使用,需 gh ≥ 2.99;禁止 URL,单批上限 50 个)。',
98
+ },
93
99
  },
94
100
  required: ['action'],
95
101
  },
@@ -116,6 +122,18 @@ export function createIssueTool(executor) {
116
122
  }
117
123
  const repoArgs = ['-R', repoTarget];
118
124
  const runOpts = { cwd, sessionId };
125
+ const appendAttachFlags = async (cmd, attachImages) => {
126
+ const attachPaths = validateAttachPaths(attachImages, cwd);
127
+ if (attachPaths.length > 0) {
128
+ const version = await executor.getGhVersion();
129
+ if (version && compareGhVersions(version, '2.99.0') < 0) {
130
+ throw new Error(`附件上传能力 (\`--attach\`) 需要 GitHub CLI 版本 ≥ 2.99.0,当前版本为: ${version}。\n请升级 gh(Windows: \`winget upgrade GitHub.cli\`,macOS: \`brew upgrade gh\`)后再试。`);
131
+ }
132
+ for (const p of attachPaths) {
133
+ cmd.push('--attach', p);
134
+ }
135
+ }
136
+ };
119
137
  switch (args.action) {
120
138
  case 'list': {
121
139
  const listCmd = ['issue', 'list', ...repoArgs, '--json', 'number,title,state,author,labels,updatedAt,url'];
@@ -216,6 +234,7 @@ export function createIssueTool(executor) {
216
234
  if (args.assignees && Array.isArray(args.assignees) && args.assignees.length > 0) {
217
235
  cmd.push('--assignee', args.assignees.join(','));
218
236
  }
237
+ await appendAttachFlags(cmd, args.attach_images);
219
238
  const res = await executor.run(cmd, runOpts);
220
239
  if (!res.ok)
221
240
  throw new Error(res.error);
@@ -237,6 +256,7 @@ export function createIssueTool(executor) {
237
256
  for (const l of args.remove_labels)
238
257
  editCmd.push('--remove-label', l);
239
258
  }
259
+ await appendAttachFlags(editCmd, args.attach_images);
240
260
  const res = await executor.run(editCmd, runOpts);
241
261
  if (!res.ok)
242
262
  throw new Error(res.error);
@@ -257,7 +277,9 @@ export function createIssueTool(executor) {
257
277
  case 'comment': {
258
278
  if (!args.issue_number || !args.body)
259
279
  throw new Error('comment 必须提供 issue_number 和 body');
260
- const res = await executor.run(['issue', 'comment', String(args.issue_number), ...repoArgs, '--body', args.body], runOpts);
280
+ const commentCmd = ['issue', 'comment', String(args.issue_number), ...repoArgs, '--body', args.body];
281
+ await appendAttachFlags(commentCmd, args.attach_images);
282
+ const res = await executor.run(commentCmd, runOpts);
261
283
  if (!res.ok)
262
284
  throw new Error(res.error);
263
285
  return { message: '评论发布成功', output: res.rawOutput };