dsh-github-flow 0.2.0 → 0.3.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 (44) hide show
  1. package/README.en.md +68 -22
  2. package/README.md +69 -23
  3. package/docs/UPGRADE-NOTES.md +93 -0
  4. package/docs/architecture.md +76 -31
  5. package/docs/images/1.png +0 -0
  6. package/docs/images/2.png +0 -0
  7. package/docs/images/3.png +0 -0
  8. package/docs/images/4.png +0 -0
  9. package/docs/npm-publish-guide.md +27 -5
  10. package/lib/api/routes-actions.d.ts +18 -4
  11. package/lib/api/routes-actions.js +143 -19
  12. package/lib/api/routes-lifecycle.d.ts +1 -1
  13. package/lib/api/routes-lifecycle.js +53 -2
  14. package/lib/api/routes-overview.js +104 -1
  15. package/lib/client.js +991 -55
  16. package/lib/commands/gh.js +11 -2
  17. package/lib/executor.d.ts +118 -1
  18. package/lib/executor.js +479 -12
  19. package/lib/main.js +10 -0
  20. package/lib/renderers/markdown.d.ts +12 -1
  21. package/lib/renderers/markdown.js +150 -16
  22. package/lib/tools/help.js +144 -11
  23. package/lib/tools/image-utils.d.ts +79 -0
  24. package/lib/tools/image-utils.js +247 -0
  25. package/lib/tools/issue.d.ts +18 -4
  26. package/lib/tools/issue.js +161 -6
  27. package/lib/tools/pr.d.ts +12 -4
  28. package/lib/tools/pr.js +128 -7
  29. package/lib/tools/repo.d.ts +20 -4
  30. package/lib/tools/repo.js +196 -4
  31. package/lib/tools/run.d.ts +8 -0
  32. package/lib/tools/run.js +104 -3
  33. package/lib/tools/secret.d.ts +5 -0
  34. package/lib/tools/secret.js +108 -23
  35. package/lib/types.d.ts +85 -1
  36. package/lib/update-settings.d.ts +18 -0
  37. package/lib/update-settings.js +60 -0
  38. package/lib/updater.d.ts +32 -1
  39. package/lib/updater.js +117 -9
  40. package/lib/upgrade-manager.d.ts +70 -1
  41. package/lib/upgrade-manager.js +346 -46
  42. package/lib/version.d.ts +1 -1
  43. package/lib/version.js +1 -1
  44. package/package.json +1 -1
@@ -35,18 +35,25 @@ export function formatStateBadge(state, isDraft) {
35
35
  return '🔴 CLOSED';
36
36
  return s;
37
37
  }
38
- /** 格式化 CI 工作流状态徽章 */
38
+ /**
39
+ * 格式化 CI 工作流状态徽章。
40
+ * 第二参数按调用域语义不同:run 域传 conclusion(success/failure/cancelled/skipped),
41
+ * pr checks 域传 bucket(pass/fail/pending/skipping/cancel)——取值族不同但映射到
42
+ * 同一组徽章;state 参数兜底(in_progress/queued 等进行中态)。
43
+ */
39
44
  export function formatCiBadge(status, conclusion) {
40
45
  const conc = (conclusion || '').toLowerCase();
41
46
  const st = (status || '').toLowerCase();
42
- if (conc === 'success')
43
- return '✅ success';
44
- if (conc === 'failure')
45
- return '❌ failure';
46
- if (conc === 'cancelled')
47
- return '🚫 cancelled';
48
- if (conc === 'skipped')
49
- return '⏭️ skipped';
47
+ if (conc === 'success' || conc === 'pass')
48
+ return '✅ ' + conc;
49
+ if (conc === 'failure' || conc === 'fail')
50
+ return '❌ ' + conc;
51
+ if (conc === 'cancelled' || conc === 'cancel')
52
+ return '🚫 ' + conc;
53
+ if (conc === 'skipped' || conc === 'skipping')
54
+ return '⏭️ ' + conc;
55
+ if (conc === 'pending')
56
+ return '⏳ pending';
50
57
  if (st === 'in_progress')
51
58
  return '⏳ running';
52
59
  if (st === 'queued')
@@ -66,6 +73,17 @@ export function formatConflictBadge(status) {
66
73
  return '⛔ BLOCKED';
67
74
  return s;
68
75
  }
76
+ /**
77
+ * 降级链路可见标注(runGuarded 契约的渲染兑现)。
78
+ * 为什么必须可见:降级若是静默的,Agent 就无法感知能力边界(同一 action 在
79
+ * 不同 gh 版本上走不同链路);一条 blockquote 注释成本一行,换链路透明。
80
+ */
81
+ export function appendDegradedNote(value) {
82
+ if (value && value.degraded === true && value.degradedReason) {
83
+ return `\n\n> ⚠️ ${value.degradedReason}`;
84
+ }
85
+ return '';
86
+ }
69
87
  // -------------------------------------------------------------
70
88
  // 1. PR 渲染器
71
89
  // -------------------------------------------------------------
@@ -112,14 +130,18 @@ export function renderPrMarkdown(args, value) {
112
130
  return '当前 PR 暂无 CI Checks 运行记录。';
113
131
  const rows = list.map((c) => {
114
132
  const name = sanitizeCell(c.name, 40);
115
- const badge = formatCiBadge(c.state, c.conclusion);
116
- const url = c.detailsUrl ? `[查看详情](${c.detailsUrl})` : '-';
117
- return `| ${name} | ${badge} | ${url} |`;
133
+ // bucket 是 gh pr checks 的分类字段(pass/fail/pending/skipping/cancel),
134
+ // 优先于 state 呈现;state(SUCCESS/FAILURE/...)作为兜底——渲染器同步
135
+ // Issue #3 修复:旧 conclusion/detailsUrl 字段在 gh 中不存在。
136
+ const badge = formatCiBadge(c.state, c.bucket);
137
+ const url = c.link ? `[查看详情](${c.link})` : '-';
138
+ const wf = c.workflow ? sanitizeCell(c.workflow, 24) : '-';
139
+ return `| ${name} | ${badge} | ${wf} | ${url} |`;
118
140
  });
119
141
  return [
120
142
  `**CI 检查结果 (${list.length} 项):**\n`,
121
- '| 检查项名称 | 结论 / 状态 | 详情链接 |',
122
- '| :--- | :--- | :--- |',
143
+ '| 检查项名称 | 结论 / 状态 | 工作流 | 详情链接 |',
144
+ '| :--- | :--- | :--- | :--- |',
123
145
  ...rows,
124
146
  ].join('\n');
125
147
  }
@@ -228,6 +250,10 @@ export function renderIssueMarkdown(args, value) {
228
250
  if (value.body) {
229
251
  lines.push('\n**议题正文:**\n```markdown\n' + sanitizeCell(value.body, 300) + '\n```');
230
252
  }
253
+ // 图片附件清单(view 附带 URL 清单,view_images 按需下载查看)
254
+ if (Array.isArray(value.images) && value.images.length > 0) {
255
+ lines.push(`- **📎 图片附件 (${value.images.length}):** 调用 \`github_issue(action: 'view_images', issue_number: ${value.number})\` 可下载查看`);
256
+ }
231
257
  return lines.join('\n');
232
258
  }
233
259
  // 兜底降级
@@ -265,7 +291,34 @@ export function renderRunMarkdown(args, value) {
265
291
  ...rows,
266
292
  ].join('\n');
267
293
  }
268
- // 3.2 Run View 详情
294
+ // 3.2 Workflow 清单(字段集为 gh 2.96 实测合法全集 4 个)
295
+ if (Array.isArray(value.items) && args?.action === 'workflow_list') {
296
+ const list = value.items.filter(Boolean);
297
+ if (list.length === 0)
298
+ return args?.all === true ? '📭 该仓库没有任何工作流。' : '📭 暂无 active 工作流(可能全部 disabled,传 all: true 查看)。';
299
+ const rows = list.map((w) => {
300
+ const badge = w.state === 'active' ? '🟢 active' : (w.state === 'disabled' ? '🔴 disabled' : String(w.state || '-'));
301
+ return `| \`${w.id}\` | ${sanitizeCell(w.name, 40)} | ${badge} | \`${sanitizeCell(w.path, 45)}\` |`;
302
+ });
303
+ return [
304
+ `**工作流清单 (${list.length} 个${args?.all === true ? ',含 disabled' : ''}):**\n`,
305
+ '| ID | 名称 | 状态 | 文件 |',
306
+ '| :--- | :--- | :--- | :--- |',
307
+ ...rows,
308
+ ].join('\n');
309
+ }
310
+ // 3.3 Workflow 详情(gh 无 --json,头部结构化 + Recent runs 原文引用块——D4 决策)
311
+ if (args?.action === 'workflow_view' && value.workflow) {
312
+ const lines = [
313
+ `### 🔄 ${value.workflow}`,
314
+ `- **ID**: \`${value.id || '-'}\`${value.totalRuns ? ` | **累计运行**: ${value.totalRuns} 次` : ''}`,
315
+ ];
316
+ if (value.recentRuns) {
317
+ lines.push('', '**最近运行:**', '```', value.recentRuns, '```');
318
+ }
319
+ return lines.join('\n');
320
+ }
321
+ // 3.4 Run View 详情
269
322
  if (args?.action === 'view' && value.databaseId) {
270
323
  const idLink = value.url ? `[${value.databaseId}](${value.url})` : `\`${value.databaseId}\``;
271
324
  const badge = formatCiBadge(value.status, value.conclusion);
@@ -292,6 +345,18 @@ export function renderRepoMarkdown(args, value) {
292
345
  return '无返回内容';
293
346
  if (typeof value.result === 'string')
294
347
  return value.result;
348
+ // 4.0 README 预览(文本截断版 + 图片清单)——必须在 message 早退分支之前:
349
+ // readme 返回同时带 message 与 readme 字段,先判 readme 才能渲染预览而非一行摘要
350
+ if (args?.action === 'readme' && typeof value.readme === 'string') {
351
+ const lines = [`### 📖 README 预览`, '', '```markdown', value.readme, '```'];
352
+ if (Array.isArray(value.images) && value.images.length > 0) {
353
+ lines.push('', `**📎 图片清单 (${value.images.length}):** 可用 \`image_urls\` 参数下载指定图片(留空下载全部)`);
354
+ value.images.slice(0, 8).forEach((u, i) => lines.push(`${i + 1}. ${u}`));
355
+ if (value.images.length > 8)
356
+ lines.push(`…共 ${value.images.length} 个`);
357
+ }
358
+ return lines.join('\n');
359
+ }
295
360
  if (typeof value.message === 'string' && !value.items) {
296
361
  return `**${value.message}**` + (value.output ? `\n\`\`\`\n${value.output}\n\`\`\`` : '');
297
362
  }
@@ -303,7 +368,7 @@ export function renderRepoMarkdown(args, value) {
303
368
  const branch = value.defaultBranchRef?.name || value.defaultBranch || 'main';
304
369
  const lines = [
305
370
  `### 📦 ${link}`,
306
- `> ⭐ **Stars**: ${stars} | 🍴 **Forks**: ${forks} | 🌿 **默认分支**: \`${branch}\``,
371
+ `> ⭐ **Stars**: ${stars} | 🍴 **Forks**: ${forks} | ⎇ **默认分支**: \`${branch}\``,
307
372
  value.description ? `\n${value.description}\n` : '',
308
373
  ];
309
374
  if (value.licenseInfo?.name || value.licenseInfo?.key) {
@@ -348,6 +413,75 @@ export function renderRepoMarkdown(args, value) {
348
413
  ...rows,
349
414
  ].join('\n');
350
415
  }
416
+ // 4.4 Issue/PR 搜索列表(search_issues / search_prs 共用,字段同构)
417
+ if (Array.isArray(value.items) && (args?.action === 'search_issues' || args?.action === 'search_prs')) {
418
+ if (value.items.length === 0)
419
+ return '未找到匹配的结果。';
420
+ const kind = args.action === 'search_prs' ? 'PR' : 'Issue';
421
+ const rows = value.items.map((it) => {
422
+ const num = it.number ?? '-';
423
+ const repo = it.repository?.nameWithOwner || it.repository?.fullName || '-';
424
+ const link = it.url ? `[#${num}](${it.url})` : `#${num}`;
425
+ const badge = formatStateBadge(it.state, it.isDraft);
426
+ const author = it.author?.login || '-';
427
+ const date = it.updatedAt ? String(it.updatedAt).slice(0, 10) : '-';
428
+ return `| ${link} | ${sanitizeCell(it.title, 45)} | ${badge} | ${repo} | ${author} | ${date} |`;
429
+ });
430
+ return [
431
+ `**${kind} 检索结果 (${value.items.length} 条):**\n`,
432
+ '| 编号 | 标题 | 状态 | 仓库 | 作者 | 更新日期 |',
433
+ '| :--- | :--- | :--- | :--- | :--- | :--- |',
434
+ ...rows,
435
+ ].join('\n');
436
+ }
437
+ // 4.5 Commit 搜索列表(嵌套 commit.commit.message 取首行)
438
+ if (Array.isArray(value.items) && args?.action === 'search_commits') {
439
+ if (value.items.length === 0)
440
+ return '未找到匹配的提交。';
441
+ const rows = value.items.map((c) => {
442
+ const sha = `\`${String(c.sha || '').slice(0, 7)}\``;
443
+ const msg = sanitizeCell(String(c.commit?.message || '').split('\n')[0], 50);
444
+ const author = c.commit?.author?.name || c.author?.login || '-';
445
+ const repo = c.repository?.fullName || '-';
446
+ return `| ${sha} | ${msg} | ${author} | ${repo} |`;
447
+ });
448
+ return [
449
+ `**提交检索结果 (${value.items.length} 条):**\n`,
450
+ '| SHA | 提交信息 | 作者 | 仓库 |',
451
+ '| :--- | :--- | :--- | :--- |',
452
+ ...rows,
453
+ ].join('\n');
454
+ }
455
+ // 4.6 目录清单(read_dir:dir 置顶 + size 人性化;mode 八进制是 Git 内部语义不展示)
456
+ if (args?.action === 'read_dir' && Array.isArray(value.items)) {
457
+ if (value.items.length === 0)
458
+ return `**${value.path || '/'}** 为空目录。` + appendDegradedNote(value);
459
+ const sorted = [...value.items].sort((a, b) => (a.type === b.type ? String(a.name).localeCompare(String(b.name)) : a.type === 'dir' ? -1 : 1));
460
+ const rows = sorted.map((e) => {
461
+ const icon = e.type === 'dir' ? '📁' : '📄';
462
+ const size = e.type === 'dir' ? '-' : (typeof e.size === 'number' ? (e.size > 1024 ? `${(e.size / 1024).toFixed(1)}KB` : `${e.size}B`) : '-');
463
+ return `| ${icon} \`${sanitizeCell(e.name, 60)}\` | ${size} |`;
464
+ });
465
+ return [
466
+ `**目录 ${value.path || '/'}(${value.items.length} 项):**\n`,
467
+ '| 名称 | 大小 |',
468
+ '| :--- | :--- |',
469
+ ...rows,
470
+ appendDegradedNote(value),
471
+ ].filter((l) => l !== '').join('\n');
472
+ }
473
+ // 4.7 文件内容(read_file:代码块 + 元信息行)
474
+ if (args?.action === 'read_file' && typeof value.content === 'string') {
475
+ const ext = (value.path || '').split('.').pop() || '';
476
+ const size = typeof value.size === 'number' ? (value.size > 1024 ? `${(value.size / 1024).toFixed(1)}KB` : `${value.size}B`) : '-';
477
+ return [
478
+ `**📄 \`${value.path}\`** (${size}${value.ref ? ` @ ${value.ref}` : ''})\n`,
479
+ '```' + ext,
480
+ value.content,
481
+ '```',
482
+ appendDegradedNote(value),
483
+ ].filter((l) => l !== '').join('\n');
484
+ }
351
485
  // 兜底降级
352
486
  return JSON.stringify(value, null, 2);
353
487
  }
package/lib/tools/help.js CHANGED
@@ -7,18 +7,22 @@ const KNOWLEDGE_BASE = {
7
7
  | :--- | :--- | :--- |
8
8
  | \`github_pr\` | Pull Request 管理、审查与本地检出 | 查看 PR 列表/详情、比对 diff、本地检出代码验证、检查 CI 状态、执行带确认合并 |
9
9
  | \`github_issue\` | Issue 任务追踪与分支开发关联 | 筛选待办议题、追加评论、编辑标题/标签、从 Issue 一键切出关联开发分支 |
10
- | \`github_run\` | GitHub Actions 构建排查与工作流管理 | 查看构建列表、提取失败日志 (log_failed)、重新触发、手动触发 workflow_dispatch |
11
- | \`github_repo\` | 仓库元数据、Fork 同步与全局检索 | 查看仓库与 topics、一键拉平 Fork 仓库 (sync)、检索全局代码与仓库 |
10
+ | \`github_run\` | Actions 构建排查与工作流管理 | 查看构建列表、提取失败日志 (log_failed)、重新触发、手动触发 workflow_dispatch、启停工作流、下载构建产物 |
11
+ | \`github_repo\` | 仓库元数据、Fork 同步与全局检索 | 查看仓库与 topics、一键拉平 Fork (sync)、检索代码/仓库/Issue/PR/提交、免 clone 读取任意文件与目录 |
12
12
  | \`github_api\` | 原生 GitHub REST/GraphQL 逃生门 | 调用任意端点、获取原始文件字节 (raw)、支持自动翻页 (paginate) |
13
13
  | \`github_release\` | 版本发布与构建资产交付 | 查看发版历史、自动生成 Release Notes、上传 .tgz 等构建产物 |
14
- | \`github_secret\` | CI/CD 部署密钥安全管理 | 查看 NPM_TOKEN 等密钥清单、经 stdin 安全写入密文、删除密钥 |
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
16
  | \`github_help\` | 路由导航、官方语法速查与故障自愈 | 遇到不确定的操作、搜索语法或接口报错时查询用法与诊断配方 |
17
17
 
18
- 💡 **使用准则**:优先调用高层语义工具(pr / issue / run / repo / release / secret);当高层工具无法满足特殊接口需求时,再降级使用 \`github_api\`。`,
18
+ 💡 **使用准则**:优先调用高层语义工具(pr / issue / run / repo / release / secret);当高层工具无法满足特殊接口需求时,再降级使用 \`github_api\`。
19
+ 🗺️ **命令对照**:不确定某 gh 命令对应哪个工具?\`github_help(topic: 'mapping')\` 查询完整映射表(含未覆盖域的 API 引导)。`,
19
20
  pr: `### 🔀 github_pr 详细操作指南
20
21
 
21
22
  - **查看列表**: \`github_pr(action: 'list', state: 'open' | 'closed' | 'merged' | 'all', limit: 20, author: '@me')\`
23
+ - **下载并查看 PR 图片**: \`github_pr(action: 'view_images', pr_number: 123)\`
24
+ - 提取范围覆盖 PR 正文 + 评论,经 gh api 下载(UI 变更对比图、复现截图等)到本地并以图片内容块返回,Agent 审查时可直接看图;魔数校验 + 字节护栏与 Issue 侧一致。
25
+ - **按 URL 精准获取**: 附加 \`image_urls: [...]\` 只下载指定图片;触达目录上限时返回 remaining 清单可分批续传(与 Issue 侧用法完全一致)。
22
26
  - **本地检出代码**: \`github_pr(action: 'checkout', pr_number: 123)\`
23
27
  - 自动切换到本地分支并拉取最新提交,非常适用于 Agent 在本地沙箱中跑单元测试验证 PR 改动。
24
28
  - **差异比对**: \`github_pr(action: 'diff', pr_number: 123, stat_only: true)\`
@@ -26,16 +30,35 @@ const KNOWLEDGE_BASE = {
26
30
  - **检查冲突状态**: \`github_pr(action: 'status')\`
27
31
  - 查看当前分支关联 PR 的 merge conflict 状态。
28
32
  - **安全合并**: \`github_pr(action: 'merge', pr_number: 123, confirm: true, merge_method: 'squash', delete_branch: false)\`
29
- - 高危操作必须显式附带 \`confirm: true\`;若未传将返回拦截警告与待合并影响报告。`,
33
+ - 高危操作必须显式附带 \`confirm: true\`;若未传将返回拦截警告与待合并影响报告。
34
+ - **Draft 切换**: \`github_pr(action: 'ready', pr_number: 123)\`;反向转回 Draft 附加 \`undo: true\`
35
+ - **反向提交**: \`github_pr(action: 'revert', pr_number: 123, confirm: true)\`
36
+ - 在远端创建反向提交与新 PR(非本地操作),需 confirm。
37
+ - **分支落后自愈**: \`github_pr(action: 'update_branch', pr_number: 123)\`
38
+ - 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI 门禁失败。`,
30
39
  issue: `### 🎯 github_issue 详细操作指南
31
40
 
32
41
  - **筛选 Issue**: \`github_issue(action: 'list', state: 'open' | 'closed' | 'all', labels: ['bug'], assignees: ['octocat'], limit: 20)\`
42
+ - **查看 Issue 附带截图**: \`github_issue(action: 'view', issue_number: 42)\`
43
+ - 返回正文中的图片 URL 清单(用户拖拽上传的报障截图等);需要"看图"时执行下方 view_images。
44
+ - **下载并查看 Issue 图片**: \`github_issue(action: 'view_images', issue_number: 42)\`
45
+ - 经 gh api 下载正文+评论图片到本地(~/.dsh/data/dsh-github-flow/images/,内容寻址去重),以图片内容块返回,Agent 可直接查看用户报障截图;魔数校验 + 字节护栏(单图 5MB、目录 15MB)。
46
+ - **按 URL 精准获取**: 附加 \`image_urls: ['<view 返回清单中的 URL>']\` 只下载指定图片(可单张可批量);传入 URL 必须在清单内(防任意下载)。
47
+ - 触达目录上限时返回 \`remaining\` 结构化清单,直接作为下一批 image_urls 续传即可。
33
48
  - **关联开发分支**: \`github_issue(action: 'develop', issue_number: 42, branch_name: 'fix-login-bug')\`
34
49
  - 自动在远端和本地创建带有官方关联关系的开发分支并切换过去,实现“读需求 -> 开分支写代码”的无缝衔接。
35
50
  - **编辑 Issue**: \`github_issue(action: 'edit', issue_number: 42, title: '新标题', add_labels: ['p1'], remove_labels: ['triage'])\`
36
51
  - **追加讨论**: \`github_issue(action: 'comment', issue_number: 42, body: '定位到问题原因...')\`
37
52
  - **安全关闭**: \`github_issue(action: 'close', issue_number: 42, confirm: true)\`
38
- - 关闭操作必须附带 \`confirm: true\`。`,
53
+ - 关闭操作必须附带 \`confirm: true\`。
54
+ - **物理删除**: \`github_issue(action: 'delete', issue_number: 42, confirm: true)\`
55
+ - 与 close 有本质区别:编号永久消失、评论湮灭、GraphQL 层无法恢复——拦截级别高于 close。
56
+ - **跨仓库迁移**: \`github_issue(action: 'transfer', issue_number: 42, target_repo: 'other/repo', confirm: true)\`
57
+ - 原 URL 301 重定向,归属改变不可静默撤回。
58
+ - **置顶**: \`github_issue(action: 'pin'/'unpin', issue_number: 42)\`(可逆状态位)
59
+ - **锁定对话**: \`github_issue(action: 'lock', issue_number: 42, lock_reason: 'resolved')\`
60
+ - reason 合法值:off_topic / resolved / spam / too_heated;解锁用 \`action: 'unlock'\`(独立命令)。
61
+ - **我的分配议题**: \`github_issue(action: 'status')\`(账号维度面板)`,
39
62
  run: `### 🚀 github_run 详细操作指南
40
63
 
41
64
  - **过滤构建记录**: \`github_run(action: 'list', branch: 'main', status: 'failure', limit: 10)\`
@@ -44,14 +67,30 @@ const KNOWLEDGE_BASE = {
44
67
  - 专为 Agent 优化的失败信息提取能力,直接定位报错位置;若无失败步骤则优雅提示。
45
68
  - **主动触发工作流**: \`github_run(action: 'trigger', workflow: 'pr-check.yml', ref: 'main', inputs: { env: 'staging' })\`
46
69
  - 提交代码修复后,主动触发远端 CI/CD 验证。
47
- - **重新执行失败任务**: \`github_run(action: 'rerun', run_id: '12345678')\``,
70
+ - **重新执行失败任务**: \`github_run(action: 'rerun', run_id: '12345678')\`
71
+ - **工作流查询**: \`github_run(action: 'workflow_list', all: true)\`
72
+ - 默认仅 active;\`all: true\` 含 disabled(排查「工作流为何不跑」时 disabled 本身就是答案)。
73
+ - **工作流详情**: \`github_run(action: 'workflow_view', workflow: 'npm-publish.yml')\`(名称/ID/文件名均可定位)
74
+ - **启停工作流**: \`github_run(action: 'workflow_enable'/'workflow_disable', workflow: '…')\`
75
+ - disable 后 trigger 将静默失败,直至重新 enable——输出会显式警告这一点。
76
+ - **下载构建产物**: \`github_run(action: 'download', run_id: '12345678', name: '构建产物名'?)\`
77
+ - 落盘目录由插件固定管理(\`~/.dsh/data/dsh-github-flow/artifacts/\` 下按仓库与 run 隔离),返回文件清单。`,
48
78
  repo: `### 📦 github_repo 详细操作指南
49
79
 
50
80
  - **查看元数据与标签**: \`github_repo(action: 'view')\`
51
81
  - 返回仓库基本信息、默认分支、Star 计数、许可证以及 \`repositoryTopics\`(用于 DSH 插件合规审查)。
82
+ - **读取 README 及截图**: \`github_repo(action: 'readme', repo: 'owner/repo')\`
83
+ - 返回 README 原文与图片 URL 清单(仓库内相对路径自动解析为 raw 链接;默认过滤徽章小图标,\`include_badges: true\` 保留)。
84
+ - **下载图片查看**: 附加 \`image_urls: [...]\` 精准下载指定图(留空下载全部截图);护栏与 Issue/PR 图片一致,触顶返回 remaining 可续传。
52
85
  - **一键拉平 Fork 主干 (防止 Stale)**: \`github_repo(action: 'sync', branch: 'main')\`
53
86
  - 解决开源项目贡献中因本地 Fork 落后导致上游 CI 门禁失败的顽疾。
54
- - **代码检索**: \`github_repo(action: 'search_code', query: 'filename:package.json dsh.bundle')\``,
87
+ - **代码检索**: \`github_repo(action: 'search_code', query: 'filename:package.json dsh.bundle')\`
88
+ - **Issue/PR/Commit 检索**: \`github_repo(action: 'search_issues'/'search_prs'/'search_commits', query: 'repo:o/r state:open')\`
89
+ - 限定符直接写进 query(gh 惯例);注意 commits 必须含搜索文本,纯限定符会被 gh 拒绝。
90
+ - **免 clone 读单文件**: \`github_repo(action: 'read_file', path: 'src/index.ts', ref: 'dev'?)\`
91
+ - gh ≥2.95 直连;低版本自动降级 contents API(输出带降级标注)。二进制文件会被拒绝并引导 github_api。
92
+ - **免 clone 列目录**: \`github_repo(action: 'read_dir', path: '.github/workflows'?)\`
93
+ - 返回目录清单(dir 置顶 + 大小),降级链路同上。`,
55
94
  api: `### ⚡ github_api 底层逃生门指南
56
95
 
57
96
  - **标准 GET 请求带参数**:
@@ -77,7 +116,9 @@ const KNOWLEDGE_BASE = {
77
116
  - **下载资产**: \`github_release(action: 'download', tag: 'v0.1.9', pattern: '*.tgz', dir: './dist')\`
78
117
  - **删除 Release**: \`github_release(action: 'delete', tag: 'v0.1.9', confirm: true)\`
79
118
  - 高危不可逆操作,必须显式确认。`,
80
- secret: `### 🔐 github_secret 部署密钥管理指南
119
+ secret: `### 🔐 github_secret 密钥与变量管理指南
120
+
121
+ 支持两类对象(kind 分流):**Secret**(密文,默认)与 **Variable**(CI 明文变量)。
81
122
 
82
123
  - **查看密钥清单**: \`github_secret(action: 'list')\`
83
124
  - 仅返回名称、可见性与更新时间;GitHub API 设计上就不允许读取密文。
@@ -87,7 +128,11 @@ const KNOWLEDGE_BASE = {
87
128
  - gh 在本地用仓库公钥加密后才上传,明文不落盘。
88
129
  - **组织/环境级密钥**: 附加 \`org: 'my-org'\` 或 \`env: 'production'\` 参数切换作用域。
89
130
  - **删除密钥**: \`github_secret(action: 'delete', name: 'NPM_TOKEN', confirm: true)\`
90
- - 删除后引用该密钥的 CI 工作流会立即失败,务必先确认无工作流依赖。`,
131
+ - 删除后引用该密钥的 CI 工作流会立即失败,务必先确认无工作流依赖。
132
+ - **变量操作(CI 明文)**: 附加 \`kind: 'variable'\`
133
+ - \`github_secret(action: 'set', kind: 'variable', name: 'DEPLOY_ENV', value: 'staging')\`——经 -b 直传(明文无 stdin 红线)。
134
+ - \`github_secret(action: 'get', kind: 'variable', name: 'DEPLOY_ENV')\`——可回显值(这正是变量与密钥的设计区别:secret 写入后 GitHub 侧加密,物理不可读取)。
135
+ - list/delete 与 secret 同形(delete 需 confirm)。`,
91
136
  skill: `### 🧠 github_skill Agent Skills 生态指南
92
137
 
93
138
  基于 GitHub CLI 的 \`gh skill\` preview 特性(需 gh >= 2.9x),跨仓库发现与安装开源 Agent Skills:
@@ -102,6 +147,93 @@ const KNOWLEDGE_BASE = {
102
147
  - 支持的 agent 值:claude-code / cursor / github-copilot / codex / gemini-cli 等。
103
148
  - **查看已安装**: \`github_skill(action: 'list')\`
104
149
  - ⚠️ 若报「unknown command」,说明宿主机 gh 版本过低,需升级到 2.9x 以上。`,
150
+ mapping: `### 🗺️ gh 命令 ↔ 插件工具映射总表
151
+
152
+ #### 一、已覆盖域映射(语义化 action 精确对照)
153
+
154
+ **PR(github_pr,13 action)**
155
+ | gh 命令 | 插件调用 | 备注 |
156
+ | :--- | :--- | :--- |
157
+ | \`gh pr list/view/create/diff/edit/comment\` | \`github_pr(action:'list'/'view'/'create'/'diff'/'edit'/'comment')\` | 高频基础操作 |
158
+ | \`gh pr close\` | 暂无专用 action——用 \`github_pr(action:'edit')\` 或 \`github_api(endpoint:'repos/{o}/{r}/pulls/{n}', method:'PATCH', fields:{state:'closed'})\` | 插件侧未覆盖 |
159
+ | \`gh pr checks\` | \`github_pr(action:'checks', pr_number:n)\` | CI 状态药丸 |
160
+ | \`gh pr review\` | \`github_pr(action:'review', review_decision:'APPROVE'…)\` | |
161
+ | \`gh pr merge\` | \`github_pr(action:'merge', confirm:true)\` | 高危确认 |
162
+ | \`gh pr checkout\` | \`github_pr(action:'checkout')\` | 本地检出 |
163
+ | \`gh pr status\` | \`github_pr(action:'status')\` | 当前分支关联 PR |
164
+ | \`gh pr ready [--undo]\` | \`github_pr(action:'ready', undo:true?)\` | Draft↔Ready 可逆 |
165
+ | \`gh pr revert\` | \`github_pr(action:'revert', confirm:true)\` | 远端创建反向 PR |
166
+ | \`gh pr update-branch\` | \`github_pr(action:'update_branch')\` | 分支落后自愈 |
167
+
168
+ **Issue(github_issue,16 action)**
169
+ | gh 命令 | 插件调用 | 备注 |
170
+ | :--- | :--- | :--- |
171
+ | \`gh issue list/view/create/comment/close/reopen/edit\` | \`github_issue(action:…)\` | 基础操作 |
172
+ | \`gh issue develop\` | \`github_issue(action:'develop', branch_name:…)\` | 关联开发分支 |
173
+ | \`gh issue delete\` | \`github_issue(action:'delete', confirm:true)\` | 物理删除(非 close) |
174
+ | \`gh issue transfer\` | \`github_issue(action:'transfer', target_repo:…, confirm:true)\` | 跨仓库迁移 |
175
+ | \`gh issue pin/unpin\` | \`github_issue(action:'pin'/'unpin')\` | 置顶状态位 |
176
+ | \`gh issue lock/unlock\` | \`github_issue(action:'lock', lock_reason:'resolved'…)\` | reason 四枚举 |
177
+ | \`gh issue status\` | \`github_issue(action:'status')\` | 我的分配议题面板 |
178
+
179
+ **Actions(github_run,11 action)——注意 workflow 域也在此工具**
180
+ | gh 命令 | 插件调用 | 备注 |
181
+ | :--- | :--- | :--- |
182
+ | \`gh run list/view/rerun/cancel\` | \`github_run(action:…)\` | |
183
+ | \`gh run view --log-failed\` | \`github_run(action:'log_failed', run_id:…)\` | 失败日志提取 |
184
+ | \`gh run download\` | \`github_run(action:'download', run_id:…)\` | 落盘插件管理目录 |
185
+ | \`gh workflow run\` | \`github_run(action:'trigger', workflow:…)\` | workflow_dispatch |
186
+ | \`gh workflow list/view\` | \`github_run(action:'workflow_list'/'workflow_view')\` | all:true 含 disabled |
187
+ | \`gh workflow enable/disable\` | \`github_run(action:'workflow_enable'/'workflow_disable')\` | disable 后 trigger 静默失败 |
188
+
189
+ **Repo(github_repo,10 action)——注意 search 域也在此工具**
190
+ | gh 命令 | 插件调用 | 备注 |
191
+ | :--- | :--- | :--- |
192
+ | \`gh repo view\` | \`github_repo(action:'view')\` | 元数据 |
193
+ | (README 读取,gh 无专用命令) | \`github_repo(action:'readme', repo:…)\` | 原文 + 图片清单/下载(插件独有) |
194
+ | \`gh repo sync\` | \`github_repo(action:'sync', force:true?)\` | Fork 对齐 |
195
+ | \`gh repo read-file\` | \`github_repo(action:'read_file', path:…)\` | 免 clone 读文件,gh<2.95 自动降级 API |
196
+ | \`gh repo read-dir\` | \`github_repo(action:'read_dir', path:?)\` | 目录清单,同上降级 |
197
+ | \`gh search code/repos\` | \`github_repo(action:'search_code'/'search_repos')\` | |
198
+ | \`gh search issues/prs/commits\` | \`github_repo(action:'search_issues'/'search_prs'/'search_commits')\` | 限定符写进 query |
199
+
200
+ **Release(github_release,6 action)**
201
+ \`gh release list/view/create/upload/download/delete\` → \`github_release(action:…)\`;delete/clobber 需 confirm。
202
+
203
+ **Secret 与 Variable(github_secret,kind 分流)——注意 variable 域也在此工具**
204
+ | gh 命令 | 插件调用 | 备注 |
205
+ | :--- | :--- | :--- |
206
+ | \`gh secret list/set/delete\` | \`github_secret(action:…)\` | 密文走 stdin,永不回显 |
207
+ | \`gh variable list/set/get/delete\` | \`github_secret(action:…, kind:'variable')\` | CI 明文:-b 直传、get 可回显 |
208
+ | (gh secret get 不存在) | — | GitHub 侧加密存储,物理不可读取 |
209
+
210
+ **Skill(github_skill,4 action)**
211
+ \`gh skill search/preview/install/list\` → \`github_skill(action:…)\`;install 需 confirm(写磁盘)。
212
+
213
+ #### 二、归属提示(命令域与工具名不同构)
214
+
215
+ - \`gh workflow …\` / \`gh search …\` / \`gh variable …\` 分别归 **github_run / github_repo / github_secret**(不是独立工具)
216
+ - \`gh api\` 的等价物是 \`github_api\`(全端点逃生门,支持 raw/paginate/jq)
217
+ - 图片查看:\`github_issue\`/\`github_pr\` 的 \`view_images\`、\`github_repo\` 的 \`readme\` + image_urls(插件独有能力,gh 无对应命令)
218
+
219
+ #### 三、未覆盖域引导(经 github_api 调用)
220
+
221
+ | gh 域 | 用途 | 推荐 REST 端点 |
222
+ | :--- | :--- | :--- |
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
+ | \`gh org\` | 组织信息 | \`orgs/{org}\` |
231
+ | \`gh auth/config/extension/alias\` | 本机配置类 | 多数无需 API(本机文件/命令) |
232
+ | \`gh browse\` | 打开浏览器 | 直接给用户 URL 即可 |
233
+ | \`gh attestation/gpg-key/ssh-key\` | 签名与密钥 | \`repos/{o}/{r}/attestations\` 等 |
234
+ | \`gh agent-task\` | Copilot 会话 | \`graphql\`(需 Copilot 授权) |
235
+
236
+ > 用法示例:\`github_api(endpoint: 'repos/owner/repo/labels', method: 'POST', fields: { name: 'bug', color: 'ff0000' })\``,
105
237
  search_syntax: `### 🔍 GitHub 官方高级检索语法速查手册
106
238
 
107
239
  在 \`github_repo\`、\`github_api\` 或搜索场景中,请严格遵守 GitHub 官方搜索语法:
@@ -154,10 +286,11 @@ export function createHelpTool(executor) {
154
286
  'release',
155
287
  'secret',
156
288
  'skill',
289
+ 'mapping',
157
290
  'search_syntax',
158
291
  'troubleshooting',
159
292
  ],
160
- description: '帮助主题分类。留空则返回全局工具箱导航。',
293
+ description: '帮助主题分类。mapping 提供 gh 命令与插件工具的完整对照表(不确定某 gh 命令对应哪个工具时查询)。留空则返回全局工具箱导航。',
161
294
  },
162
295
  gh_command: {
163
296
  type: 'string',
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 把 README 中的仓库内相对图片路径解析为 raw.githubusercontent 绝对 URL。
3
+ * 强制落在 raw 域名模板内——不存在拼出任意域名的可能(SSRF 白名单天然覆盖)。
4
+ * 路径以 ./ ../ /docs/ 等形态出现;URL 编码空格等字符;无效形态返回 null。
5
+ */
6
+ export declare function resolveRelativeImageUrl(rel: string, owner: string, repo: string, branch: string): string | null;
7
+ /**
8
+ * 判断 URL 是否为徽章类小图标(shields.io / badge 类 svg)。
9
+ * README 图片场景默认过滤:用户要看的是截图/架构图,版本徽章是排版噪音;
10
+ * include_badges=true 时保留(有人确实要查徽章状态)。
11
+ */
12
+ export declare function isBadgeImageUrl(url: string): boolean;
13
+ /**
14
+ * 从正文(Issue/PR body 或评论)提取图片 URL。
15
+ * 域名白名单 + https 扩展名双条件:
16
+ * - GitHub 四资产域名直接放行(用户上传/旧版上传/仓库引用/camo 代理);
17
+ * - 外链强制 https 且带图片扩展名——关闭恶意正文诱导访问内网元数据端点
18
+ * (169.254.169.254)等 SSRF 面,file:/// 与 http:// 一律拒绝。
19
+ */
20
+ export declare function extractImageUrls(body: unknown): string[];
21
+ /**
22
+ * README 专用提取:绝对 URL 走通用提取器;仓库内相对路径解析为 raw URL。
23
+ * 默认过滤徽章小图标(isBadgeImageUrl),include_badges 开关保留。
24
+ */
25
+ export declare function extractReadmeImageUrls(body: unknown, ctx: {
26
+ owner: string;
27
+ repo: string;
28
+ branch: string;
29
+ includeBadges?: boolean;
30
+ }): {
31
+ urls: string[];
32
+ relativeResolved: number;
33
+ badgeFiltered: number;
34
+ };
35
+ /** 从魔数推断图片 MIME(下载内容才是事实源——URL 扩展名可伪造) */
36
+ export declare function sniffImageMime(buf: Buffer): string | null;
37
+ /** 图片下载护栏:按真实资源消耗计算——单图字节与目录总量,无张数截断 */
38
+ export declare const IMG_MAX_BYTES: number;
39
+ export declare const IMG_DIR_MAX_BYTES: number;
40
+ export interface DownloadedImage {
41
+ file: string;
42
+ mimeType: string;
43
+ url: string;
44
+ bytes: number;
45
+ }
46
+ export interface ImageBatchResult {
47
+ images: DownloadedImage[];
48
+ failures: string[];
49
+ /**
50
+ * 因目录总量护栏触顶而未处理的 URL(结构化续传清单)。
51
+ * Agent 可直接 image_urls: result.remaining 发起下一批——remaining 中的
52
+ * URL 本就来自正文合法清单,交集校验天然通过,分批获取无需清理目录。
53
+ */
54
+ remaining?: string[];
55
+ }
56
+ /**
57
+ * 交集过滤:把调用方请求的 URL 硬约束在正文实际提取的合法清单内。
58
+ * 为什么必须交集而非直接信任传入:image_urls 来自模型生成参数,存在幻觉或
59
+ * 被诱导传入任意 URL 的可能——交集把下载目标限定在本条 Issue/PR 引用过的
60
+ * 图片范围,SSRF 防线(提取阶段的 https+域名白名单)自动覆盖全部下载目标。
61
+ * 返回 { matched, rejected }:matched 保持请求顺序(Agent 按自己的关注顺序拿结果)。
62
+ */
63
+ export declare function filterRequestedUrls(requested: unknown, extracted: string[]): {
64
+ matched: string[];
65
+ rejected: string[];
66
+ };
67
+ /**
68
+ * 批量下载图片并落盘(Issue/PR 的 view_images 共用执行体)。
69
+ * 每张经 executor 的二进制通道(gh api),魔数校验 + 三重护栏 + 内容寻址;
70
+ * 权威目录不可写时降级系统临时目录(只损失跨会话去重)。
71
+ * 单张失败不中断批次——failures 记录原因由调用方透传给 Agent。
72
+ */
73
+ export declare function downloadImages(urls: string[], run: (url: string) => Promise<{
74
+ ok: boolean;
75
+ data?: unknown;
76
+ error?: string;
77
+ }>): Promise<ImageBatchResult>;
78
+ /** render 的 image 内容块构造(text 块由调用方自行拼):仅双合法字段才产出块 */
79
+ export declare function buildImageBlocks(value: any): any[];