dsh-github-flow 0.5.0 → 0.7.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 (103) hide show
  1. package/README.en.md +112 -17
  2. package/README.md +115 -17
  3. package/docs/UPGRADE-NOTES.md +12 -11
  4. package/docs/architecture.md +81 -22
  5. package/docs/images/1.png +0 -0
  6. package/lib/api/route-helpers.d.ts +19 -0
  7. package/lib/api/route-helpers.js +54 -0
  8. package/lib/api/routes-actions.js +214 -9
  9. package/lib/api/routes-lifecycle.js +8 -1
  10. package/lib/api/routes-overview.d.ts +26 -0
  11. package/lib/api/routes-overview.js +396 -36
  12. package/lib/api/routes-proxy.js +8 -1
  13. package/lib/api/routes.d.ts +11 -1
  14. package/lib/api/routes.js +15 -2
  15. package/lib/cache.d.ts +56 -4
  16. package/lib/cache.js +77 -7
  17. package/lib/client.d.ts +8 -3
  18. package/lib/client.js +5542 -1391
  19. package/lib/destructive.d.ts +59 -0
  20. package/lib/destructive.js +285 -0
  21. package/lib/executor/args-guard.d.ts +17 -0
  22. package/lib/executor/args-guard.js +23 -0
  23. package/lib/executor/error-classifier.d.ts +55 -0
  24. package/lib/executor/error-classifier.js +199 -0
  25. package/lib/executor/failure-anchors.d.ts +52 -0
  26. package/lib/executor/failure-anchors.js +116 -0
  27. package/lib/executor/git-parser.d.ts +124 -0
  28. package/lib/executor/git-parser.js +397 -0
  29. package/lib/executor/pr-review.d.ts +95 -0
  30. package/lib/executor/pr-review.js +175 -0
  31. package/lib/executor/run-log.d.ts +36 -0
  32. package/lib/executor/run-log.js +78 -0
  33. package/lib/executor.d.ts +277 -6
  34. package/lib/executor.js +1265 -94
  35. package/lib/github-service.d.ts +1 -1
  36. package/lib/main.d.ts +92 -18
  37. package/lib/main.js +186 -95
  38. package/lib/renderers/common.d.ts +0 -3
  39. package/lib/renderers/common.js +8 -44
  40. package/lib/renderers/markdown.d.ts +1 -0
  41. package/lib/renderers/markdown.js +1 -0
  42. package/lib/renderers/pr.js +26 -1
  43. package/lib/renderers/task.d.ts +9 -0
  44. package/lib/renderers/task.js +120 -0
  45. package/lib/review-rules.d.ts +60 -0
  46. package/lib/review-rules.js +204 -0
  47. package/lib/shared/state-badges.d.ts +42 -0
  48. package/lib/shared/state-badges.js +102 -0
  49. package/lib/task-primitives.d.ts +109 -0
  50. package/lib/task-primitives.js +194 -0
  51. package/lib/tool-tiers.d.ts +57 -0
  52. package/lib/tool-tiers.js +168 -0
  53. package/lib/tools/agent-task.js +4 -4
  54. package/lib/tools/api.js +18 -6
  55. package/lib/tools/cache.js +42 -40
  56. package/lib/tools/codespace.js +4 -12
  57. package/lib/tools/discussion.js +4 -4
  58. package/lib/tools/gist.d.ts +1 -39
  59. package/lib/tools/gist.js +4 -14
  60. package/lib/tools/help.js +72 -18
  61. package/lib/tools/image-utils.d.ts +27 -0
  62. package/lib/tools/image-utils.js +73 -3
  63. package/lib/tools/issue.js +16 -28
  64. package/lib/tools/label.js +4 -15
  65. package/lib/tools/pr.d.ts +47 -0
  66. package/lib/tools/pr.js +170 -33
  67. package/lib/tools/project.js +4 -13
  68. package/lib/tools/release.js +11 -22
  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 +219 -0
  76. package/lib/tools/task.js +457 -0
  77. package/lib/types.d.ts +514 -1
  78. package/lib/ui/help-modal.d.ts +6 -0
  79. package/lib/ui/help-modal.js +124 -16
  80. package/lib/ui/issue-modal.d.ts +14 -0
  81. package/lib/ui/issue-modal.js +370 -0
  82. package/lib/ui/markdown-lite.d.ts +66 -0
  83. package/lib/ui/markdown-lite.js +312 -0
  84. package/lib/ui/network-banner.d.ts +12 -0
  85. package/lib/ui/network-banner.js +35 -0
  86. package/lib/ui/pr-modal.d.ts +17 -0
  87. package/lib/ui/pr-modal.js +555 -0
  88. package/lib/ui/repo-badges.d.ts +43 -0
  89. package/lib/ui/repo-badges.js +82 -0
  90. package/lib/ui/repo-selector.d.ts +14 -0
  91. package/lib/ui/repo-selector.js +112 -0
  92. package/lib/ui/run-modal.d.ts +26 -0
  93. package/lib/ui/run-modal.js +486 -0
  94. package/lib/ui/storage.d.ts +47 -0
  95. package/lib/ui/storage.js +139 -0
  96. package/lib/ui/styles.d.ts +1 -1
  97. package/lib/ui/styles.js +1330 -44
  98. package/lib/ui/sync-settings-modal.js +1 -1
  99. package/lib/ui/utils.js +13 -1
  100. package/lib/ui/workspace-panel.js +226 -29
  101. package/lib/version.d.ts +1 -1
  102. package/lib/version.js +1 -1
  103. package/package.json +9 -2
package/lib/types.d.ts CHANGED
@@ -36,6 +36,22 @@ export interface PluginConfig {
36
36
  * 注意:放在 Cordis Config 意味着修改此项会触发插件 reload(静态默认值,可接受)。
37
37
  */
38
38
  proxyUrl?: string;
39
+ /**
40
+ * 工具档位:控制注册给模型的工具数量(默认不填 = `full`,18 个全量,零破坏)。
41
+ *
42
+ * 三种预设 `core`(5 个)/ `ci`(7 个)/ `maintain`(8 个),也可直接写工具名
43
+ * (如 `['github_pr','github_release']`)混用。未知名字会 console.warn 一次后忽略。
44
+ * 存在的理由:18 个工具的 schema 常驻约 24,000 字符,等于每轮对话都在为用不到的工具付费。
45
+ */
46
+ toolsets?: string[];
47
+ /**
48
+ * 只读档位(默认 false)。
49
+ *
50
+ * 硬语义,不做运行时判断:写 action 在**注册期**就从 `enum` 里被剔除,模型连
51
+ * 「可以写」都看不到,宿主参数校验会直接拒绝。`github_api`(端点与 method 均可任意,
52
+ * 无法降级)在只读模式下整体剔除。
53
+ */
54
+ read_only?: boolean;
39
55
  }
40
56
  /** 数据同步与自动化感知偏好配置 */
41
57
  export interface SyncSettings {
@@ -68,6 +84,18 @@ export interface GhExecutionOptions {
68
84
  * 暴露在进程命令行(ps / 任务管理器 / 审计日志可见)的传入通道。
69
85
  */
70
86
  stdin?: string;
87
+ /**
88
+ * 单次调用的输出字符上限覆盖(默认沿用实例配置的 maxOutputChars=24000)。
89
+ *
90
+ * 为什么需要按调用覆盖:这个默认值是按「喂给模型上下文的工具输出」调校的,
91
+ * 目的是防 Token 爆窗。但有些输出是**程序化消费**的——例如 PR 全量 Unified Diff
92
+ * 会被切片成按文件索引、前端一次只渲染一个文件,永远不会整段进模型上下文。
93
+ * 对这类调用沿用 24KB 会静默截断真实数据(实测一个普通 PR 的 diff 已达 39KB,
94
+ * 被截断后后半数文件点开皆空)。消费者不同,阈值就该不同。
95
+ */
96
+ maxOutputChars?: number;
97
+ /** 单次调用的输出 UTF-8 字节上限覆盖,语义同 maxOutputChars */
98
+ maxOutputBytes?: number;
71
99
  }
72
100
  export interface GhResult<T = unknown> {
73
101
  ok: boolean;
@@ -78,6 +106,22 @@ export interface GhResult<T = unknown> {
78
106
  truncated?: boolean;
79
107
  /** 二进制模式下的字节长度(data 为 Buffer 时存在) */
80
108
  binaryBytes?: number;
109
+ /**
110
+ * 失败语义分类(仅 ok=false 时存在)。
111
+ *
112
+ * 为什么挂在结果上而不是让调用方自己 parse error 文本:调用方散落在
113
+ * 各工具与路由里,重复实现文本匹配必然漂移;分类规则只应有一份事实源
114
+ * (src/executor/error-classifier.ts)。上层据此决定「重试 / 回退快照 / 直接降级」。
115
+ */
116
+ errorKind?: 'transient' | 'deterministic' | 'unknown';
117
+ /**
118
+ * 权限不足时的可操作处置指引(仅 errorKind=deterministic 且命中权限特征时存在)。
119
+ *
120
+ * 为什么单独成字段而不直接拼进 error:error 是「发生了什么」,
121
+ * 这是「接下来怎么做」。工具层可以选择只抛 error(静默场景)
122
+ * 或把两者一起抛给模型(需要模型自愈的场景)。
123
+ */
124
+ permissionRemedy?: string;
81
125
  }
82
126
  export interface AuthInstallGuide {
83
127
  /** 推荐包管理器命令,例如 Windows 下 "winget install --id GitHub.cli" */
@@ -107,6 +151,47 @@ export interface AuthStatus {
107
151
  /** 本地是否已检测到持久化凭据(如 hosts.yml 或 Keyring) */
108
152
  hasLocalToken?: boolean;
109
153
  }
154
+ /**
155
+ * 当前账号对仓库的权限档位(gh `viewerPermission` 字段的取值域)。
156
+ * 只读(READ / TRIAGE)意味着任何写操作都会被 GitHub 拒绝,
157
+ * UI 应据此在操作前给出提示,而不是让用户点了才吃 403。
158
+ */
159
+ export type ViewerPermission = 'ADMIN' | 'MAINTAIN' | 'WRITE' | 'TRIAGE' | 'READ';
160
+ /**
161
+ * 工作区的远程布局分类(离线解析得出,用于确定「该用哪个 remote 的身份」)。
162
+ *
163
+ * 为什么要分类:混合布局下若按 `.git/config` 的书写顺序选 remote,
164
+ * 同一个工作区在两次 `git remote` 操作后可能解析出不同仓库。
165
+ * 分类后每种布局的处置规则固定,与书写顺序无关。
166
+ */
167
+ export type RepoLayoutKind =
168
+ /** 全部 remote 都是 github.com */
169
+ 'github-only'
170
+ /** 含 GitHub remote,但 git 选中的那个不是 GitHub */
171
+ | 'github-mixed'
172
+ /** 无任何 GitHub remote(全 GHE 或其他 host) */
173
+ | 'no-github'
174
+ /** 无 remote */
175
+ | 'no-remote';
176
+ /**
177
+ * 关联仓库角色标识:
178
+ * - fork: 属于当前登录用户的个人复刻仓库
179
+ * - upstream: 社区上游主干仓库(名为 upstream 或作为 parent)
180
+ * - origin: 默认 origin 远程仓库
181
+ * - parent: 上游源仓库(在线识别自 repo.parent,本地尚未添加 remote)
182
+ * - remote: 其他自定义名称的远程仓库
183
+ */
184
+ export type AssociatedRepoRole = 'origin' | 'upstream' | 'fork' | 'parent' | 'remote';
185
+ /**
186
+ * 当前工作区可切换的关联 GitHub 仓库项(供仓库切换器下拉菜单消费)。
187
+ */
188
+ export interface AssociatedRepoItem {
189
+ nameWithOwner: string;
190
+ remoteName?: string;
191
+ url: string;
192
+ isCurrent: boolean;
193
+ role: AssociatedRepoRole;
194
+ }
110
195
  export interface RepoMetadata {
111
196
  nameWithOwner: string;
112
197
  name: string;
@@ -118,6 +203,30 @@ export interface RepoMetadata {
118
203
  url?: string;
119
204
  stargazerCount?: number;
120
205
  forkCount?: number;
206
+ /** 是否为 fork(影响 star 取谁、是否提示可查看上游) */
207
+ isFork?: boolean;
208
+ /**
209
+ * fork 的上游。
210
+ * gh 的 `parent` 只返回骨架(id/name/owner.login),nameWithOwner 需自行拼接;
211
+ * 上游 star 数拿不到(`--json` 不支持嵌套子字段),故此处不含热度字段。
212
+ */
213
+ parent?: {
214
+ nameWithOwner: string;
215
+ url?: string;
216
+ };
217
+ /**
218
+ * 当前账号对该仓库的权限。
219
+ * 离线解析时为 undefined——**不猜测**:猜错会导致 UI 给出错误的操作许可。
220
+ */
221
+ viewerPermission?: ViewerPermission;
222
+ /** 远程布局分类(仅离线解析产生,供 UI 与诊断区分场景) */
223
+ layout?: RepoLayoutKind;
224
+ /**
225
+ * 当前工作区可供用户手动切换的关联仓库列表。
226
+ * 包含:本地 Git 配置的全部 GitHub remote (origin, upstream 等) 及在线 parent。
227
+ * 仅当数量 > 1 时 UI 渲染切换控件,单仓库场景保持零噪音。
228
+ */
229
+ associatedRepos?: AssociatedRepoItem[];
121
230
  }
122
231
  /** Git 分支详情项(供分支切换器下拉列表消费) */
123
232
  export interface GitBranchItem {
@@ -152,6 +261,24 @@ export interface GitLocalStatus {
152
261
  path: string;
153
262
  }>;
154
263
  }
264
+ /**
265
+ * 工作区干净度闸门结果(`executor.ensureCleanWorkspace`)。
266
+ *
267
+ * 为什么要有 `reason: 'unknown'`:`getLocalGitStatus()` 在 git 不可用或非仓库时返回 null。
268
+ * 旧实现用 `if (gitSt && gitSt.dirtyCount > 0)` 判断,null 时表达式为假 → 闸门静默放行。
269
+ * 安全闸门不能因为「读不到状态」而失效,所以这里把「读不到」与「干净」显式区分开。
270
+ */
271
+ export type WorkspaceCleanResult = {
272
+ ok: true;
273
+ dirtyCount: 0;
274
+ samples: string[];
275
+ } | {
276
+ ok: false;
277
+ reason: 'dirty' | 'unknown';
278
+ dirtyCount: number;
279
+ samples: string[];
280
+ error: string;
281
+ };
155
282
  /** 单条提交记录(结构化解析) */
156
283
  export interface GitCommitEntry {
157
284
  sha: string;
@@ -183,7 +310,7 @@ export interface PrItem {
183
310
  number: number;
184
311
  title: string;
185
312
  state: 'OPEN' | 'CLOSED' | 'MERGED';
186
- author: {
313
+ author?: {
187
314
  login: string;
188
315
  avatarUrl?: string;
189
316
  };
@@ -200,6 +327,133 @@ export interface PrItem {
200
327
  url: string;
201
328
  updatedAt: string;
202
329
  }
330
+ /** PR 变动文件详情项 */
331
+ export interface PrFileItem {
332
+ path: string;
333
+ additions: number;
334
+ deletions: number;
335
+ changeType?: string;
336
+ }
337
+ /** PR 评论项 */
338
+ export interface PrCommentItem {
339
+ id: string;
340
+ author: {
341
+ login: string;
342
+ avatarUrl?: string;
343
+ };
344
+ authorAssociation?: string;
345
+ body: string;
346
+ createdAt: string;
347
+ url: string;
348
+ }
349
+ /** PR 审查记录项 */
350
+ export interface PrReviewItem {
351
+ id: string;
352
+ author: {
353
+ login: string;
354
+ };
355
+ state: string;
356
+ body: string;
357
+ submittedAt: string;
358
+ }
359
+ /**
360
+ * PR 行级审查评论项(代码行上的评论,区别于 Issue 级讨论)。
361
+ *
362
+ * 为什么必须单列:`gh pr view --json comments` 只返回**对话区**的 issue-level 评论,
363
+ * 完全不包含挂在具体代码行上的 review comment。实测 cli/cli#14752 有 10 条行级评论,
364
+ * 而 `--json comments` 一条都拿不到——必须走
365
+ * `GET /repos/{owner}/{repo}/pulls/{number}/comments` 独立通道。
366
+ *
367
+ * 不做的部分:不把评论按行号精确嵌进 diff 的 hunk 中间(需反查 hunk 偏移,
368
+ * 多行/折叠上下文下极易错位)。改为在文件级 diff 下方按行号列出,信息完整且确定。
369
+ */
370
+ export interface PrLineCommentItem {
371
+ id: string;
372
+ /** 所属文件路径,与 fileDiffs 的键一致 */
373
+ path: string;
374
+ /** 评论锚定的行号(新文件侧);纯删除行可能为 null */
375
+ line: number | null;
376
+ side?: string;
377
+ author: {
378
+ login: string;
379
+ };
380
+ body: string;
381
+ createdAt: string;
382
+ }
383
+ /** PR 完整详情与差异数据契约(供 PrDetailModal 在线审阅消费) */
384
+ export interface PrDetailData {
385
+ number: number;
386
+ title: string;
387
+ state: 'OPEN' | 'CLOSED' | 'MERGED';
388
+ isDraft: boolean;
389
+ url: string;
390
+ createdAt: string;
391
+ updatedAt: string;
392
+ author: {
393
+ login: string;
394
+ avatarUrl?: string;
395
+ };
396
+ headRefName: string;
397
+ baseRefName: string;
398
+ /**
399
+ * 分支尖端的 commit SHA。
400
+ *
401
+ * 为什么必须透出:分支名会移动(force-push / 新提交),拿它当审查锚点等于
402
+ * 「我审的到底是哪一版」永远说不清。SHA 是不可移动的事实源,也是行级评注
403
+ * 写入时 `commit_id` 的取值来源。
404
+ */
405
+ headRefOid?: string;
406
+ baseRefOid?: string;
407
+ headRepoOwner?: string;
408
+ body: string;
409
+ reviewDecision?: string;
410
+ statusCheckRollup?: any;
411
+ additions: number;
412
+ deletions: number;
413
+ files: PrFileItem[];
414
+ comments: PrCommentItem[];
415
+ reviews: PrReviewItem[];
416
+ /**
417
+ * 行级审查评论(挂在具体代码行上的 review comment)。
418
+ * 与 comments 是**两套独立数据源**:comments 来自对话区,lineComments 来自
419
+ * /pulls/{n}/comments。拉取失败时降级为空数组,不阻塞主详情。
420
+ */
421
+ lineComments?: PrLineCommentItem[];
422
+ /** 按文件相对路径切片索引的 Git Unified Diff 字典 */
423
+ fileDiffs: Record<string, string>;
424
+ /**
425
+ * 为什么不回传 fullDiff 原文:`fileDiffs` 已经是全量 diff 的等价切分
426
+ * (每段拼起来就是原文),再附带一份原文会让 HTTP 响应体整整翻倍。
427
+ * 实测一个普通 PR 的 diff 就有 39KB,2MB 上限下翻倍意味着 4MB JSON——
428
+ * 白占带宽、白占浏览器内存、白耗 JSON.parse 时间,且没有任何消费方。
429
+ * 原文仅在服务端用于切片,切片完即丢弃。
430
+ */
431
+ /**
432
+ * diff 是否被输出阈值截断。
433
+ * 为什么必须透出:超巨量 PR 仍可能触顶,此时部分文件拿不到 diff。
434
+ * 静默展示「该文件无差异」会让用户误以为代码没改动——必须显式提示。
435
+ */
436
+ diffTruncated?: boolean;
437
+ /**
438
+ * PR 实际变更的文件总数(来自 `gh pr view --json changedFiles`)。
439
+ *
440
+ * 为什么必须与 `files.length` 分开:`gh pr view --json files` 在 100 条处
441
+ * **静默截断**(cli/cli#12522),`files.length` 只是「返回了几条」。
442
+ * 把两者混为一谈,界面就会把「被截断」说成「就这么多」。
443
+ */
444
+ changedFiles?: number;
445
+ /** `files` 清单是否被 gh 的 100 条上限截断(`files.length < changedFiles`) */
446
+ filesTruncated?: boolean;
447
+ /**
448
+ * 是否有文件**没拿到 diff 正文**(该文件在 `files` 里但 `fileDiffs` 无对应切片)。
449
+ *
450
+ * 与 `diffTruncated` 是并列而非同一概念:
451
+ * - `diffTruncated`:整个 diff 输出被阈值裁掉(原因在传输层);
452
+ * - `diffIncomplete`:清单里有这个文件、但切片里没有它(原因在数据缺失)。
453
+ * 两者处置不同(一个是「去看完整 diff」,一个是「这个文件没取到」),不能合并。
454
+ */
455
+ diffIncomplete?: boolean;
456
+ }
203
457
  /**
204
458
  * Issue 分类维度契约:
205
459
  * - reported: 用户提起的(他人向我名下项目或本仓库提交的 Issue 反馈)
@@ -207,6 +461,107 @@ export interface PrItem {
207
461
  * - created: 我创建的(由当前账号亲自发起的 Issue)
208
462
  */
209
463
  export type IssueCategory = 'reported' | 'assigned' | 'created';
464
+ /** Issue 评论项(在线查看弹窗的讨论时间线消费) */
465
+ export interface IssueCommentItem {
466
+ id: string;
467
+ author: {
468
+ login: string;
469
+ avatarUrl?: string;
470
+ };
471
+ authorAssociation?: string;
472
+ body: string;
473
+ createdAt: string;
474
+ url: string;
475
+ /**
476
+ * GitHub 侧被折叠/隐藏的评论(垃圾、攻击性内容等)。
477
+ * 为什么必须透出:实测 cli/cli#1 的评论带 `isMinimized: true, minimizedReason: "SPAM"`,
478
+ * 若照常渲染成普通讨论,用户会把垃圾内容当成真实技术意见。
479
+ */
480
+ isMinimized?: boolean;
481
+ minimizedReason?: string;
482
+ includesCreatedEdit?: boolean;
483
+ }
484
+ /**
485
+ * Issue 关联的 PR 引用。
486
+ *
487
+ * 字段集受限于 gh 的实际返回:实测 `gh issue view --json closedByPullRequestsReferences`
488
+ * 的节点只有 `id / number / repository / url`,**没有 title 与 state**。
489
+ * UI 因此只渲染 `#number` 链接,不得假设更多字段存在。
490
+ */
491
+ export interface IssueLinkedPrRef {
492
+ number: number;
493
+ url: string;
494
+ /** `owner/repo`;跨仓关联时与 Issue 自身仓库不同 */
495
+ repository?: string;
496
+ }
497
+ /** Issue 里程碑(实测形态:number/title/description/dueOn,无 state) */
498
+ export interface IssueMilestoneInfo {
499
+ number?: number;
500
+ title: string;
501
+ /** ISO 时间;未设置截止日时为缺省 */
502
+ dueOn?: string;
503
+ }
504
+ /** 子议题完成度摘要(gh `subIssuesSummary` 的直接映射) */
505
+ export interface IssueSubIssuesSummary {
506
+ completed: number;
507
+ total: number;
508
+ }
509
+ /**
510
+ * Issue 完整详情数据契约(供 IssueDetailModal 在线查看消费)。
511
+ *
512
+ * 为什么不复用 `IssueItem`:列表项只需要渲染一行卡片,字段刻意精简;
513
+ * 详情需要正文、评论时间线、里程碑、子议题等重字段,混在一起会让
514
+ * 全局概览的缓存与快照体积翻数倍(每条 Issue 都拖着全部评论)。
515
+ */
516
+ export interface IssueDetailData {
517
+ number: number;
518
+ title: string;
519
+ state: 'OPEN' | 'CLOSED';
520
+ /** COMPLETED / NOT_PLANNED / REOPENED / ''(实测空串形态存在) */
521
+ stateReason?: string;
522
+ url: string;
523
+ createdAt: string;
524
+ updatedAt: string;
525
+ closedAt?: string;
526
+ author: {
527
+ login: string;
528
+ avatarUrl?: string;
529
+ };
530
+ body: string;
531
+ labels: Array<{
532
+ name: string;
533
+ color?: string;
534
+ description?: string;
535
+ }>;
536
+ assignees: Array<{
537
+ login: string;
538
+ }>;
539
+ milestone?: IssueMilestoneInfo;
540
+ isPinned?: boolean;
541
+ issueType?: string;
542
+ comments: IssueCommentItem[];
543
+ linkedPrs: IssueLinkedPrRef[];
544
+ subIssues?: IssueSubIssuesSummary;
545
+ /**
546
+ * 正文与评论中识别到的图片 URL(与 github_issue(view_images) 同源提取)。
547
+ *
548
+ * 为什么只给清单不在弹窗内联渲染:图片可能来自任意 https 外域,
549
+ * 在面板里直接 `<img src>` 会把宿主页面 Referer/来源暴露给第三方,
550
+ * 也绕过了插件的域名白名单防线。UI 只展示「附件 N 张」并给外链。
551
+ */
552
+ images: string[];
553
+ /**
554
+ * 是否发生过字段集降级。
555
+ * 为什么需要:老版本 gh 遇到未知 JSON 字段会整体失败(实测
556
+ * `Unknown JSON field: "closedAt"`),此时回退到 BASE 字段集重试;
557
+ * 该标记供排查「为什么没看到里程碑/关联 PR」这类差异,不弹错。
558
+ *
559
+ * 注意:不设 truncated 标记——响应被输出阈值截断时 executor 直接返回
560
+ * `ok:false` + 浏览器直达链接,绝不会产出「零评论」的空详情对象,
561
+ * 因此该失败语义不需要在数据契约里再表达一次。
562
+ */
563
+ fieldsDegraded?: boolean;
564
+ }
210
565
  export interface IssueStats {
211
566
  total: number;
212
567
  reported: number;
@@ -242,6 +597,93 @@ export interface WorkflowRunItem {
242
597
  url: string;
243
598
  createdAt: string;
244
599
  }
600
+ /**
601
+ * 单次 Run 里的一个 job 步骤(`gh run view --json jobs` 实测字段)。
602
+ *
603
+ * 为什么只有这四个时间/状态字段:step 在 GitHub 上**没有独立 URL**(锚点是页面内
604
+ * `#step:` 片段,API 不返回),也没有自己的一行日志入口。UI 因此只能展示
605
+ * 「序号 + 名称 + 结论 + 耗时」,任何「点击某步看日志」的交互都做不到——
606
+ * 日志的最小粒度是 job。
607
+ */
608
+ export interface RunStepItem {
609
+ number: number;
610
+ name: string;
611
+ status: string;
612
+ conclusion?: string;
613
+ startedAt?: string;
614
+ completedAt?: string;
615
+ }
616
+ /** 单次 Run 里的一个 job(数据库 ID 即 check-run ID,可用于注解/日志端点) */
617
+ export interface RunJobItem {
618
+ databaseId: number;
619
+ name: string;
620
+ status: string;
621
+ conclusion?: string;
622
+ startedAt?: string;
623
+ completedAt?: string;
624
+ /** 该 job 在 GitHub 的页面地址;step 级别没有对应 URL */
625
+ url?: string;
626
+ steps: RunStepItem[];
627
+ }
628
+ /**
629
+ * Run 完整详情契约(供 RunDetailModal 在线查看消费)。
630
+ *
631
+ * 与 PrDetailData / IssueDetailData 的关键差异:这里**不携带日志正文**。
632
+ * 日志动辄几十 MB 且实测单次拉取 4–40 秒还会被代理掐断,挂在详情上会让
633
+ * 弹窗首屏被慢链路拖死;日志走独立的 /run-logs 通道按需拉取。
634
+ */
635
+ export interface RunDetailData {
636
+ runId: number;
637
+ /** 重跑次数(>1 时 UI 显示「第 N 次尝试」) */
638
+ attempt?: number;
639
+ name: string;
640
+ displayTitle?: string;
641
+ status: string;
642
+ conclusion?: string;
643
+ event?: string;
644
+ headBranch?: string;
645
+ headSha?: string;
646
+ url: string;
647
+ createdAt: string;
648
+ startedAt?: string;
649
+ updatedAt: string;
650
+ workflowName?: string;
651
+ jobs: RunJobItem[];
652
+ /**
653
+ * 失败口径只算一次。
654
+ * 为什么在服务端算:UI 与将来的其它消费方(Agent 摘要、测试)必须同一口径,
655
+ * 三处各写一遍 `conclusion === 'failure'` 迟早出现「卡片说 1 个失败、弹窗说 2 个」。
656
+ */
657
+ failedJobs: number;
658
+ failedSteps: number;
659
+ /** gh 版本过低触发过字段降级(供排查,不弹错) */
660
+ fieldsDegraded?: boolean;
661
+ }
662
+ /** 失败日志按需拉取的结果(一 job 一块,单 job 失败不拖垮整体) */
663
+ export interface RunLogExcerptData {
664
+ runId: number;
665
+ jobs: Array<{
666
+ jobId: number;
667
+ jobName: string;
668
+ /** 锚点萃取后的纯文本片段(含尾部回退与显式截断声明) */
669
+ excerpt: string;
670
+ truncated: boolean;
671
+ }>;
672
+ /** 拉取失败但可重试的 job;UI 必须显式展示,不能静默当成「没有失败日志」 */
673
+ errors?: Array<{
674
+ jobName: string;
675
+ error: string;
676
+ }>;
677
+ /**
678
+ * job 名称未能从详情树解析(服务端详情降级/失败)。
679
+ *
680
+ * 为什么必须透出:此时 jobName 会退化成 `job #<id>`,属于用户可见的降级。
681
+ * 本仓纪律是「降级必须显式」——让界面能说一句「名称未取到」,
682
+ * 而不是让人以为这个 job 本来就叫 `job #111`。
683
+ */
684
+ namesUnavailable?: boolean;
685
+ fetchedAt: string;
686
+ }
245
687
  export interface GitHubOverviewData {
246
688
  auth: AuthStatus;
247
689
  repo?: RepoMetadata;
@@ -255,6 +697,14 @@ export interface GitHubOverviewData {
255
697
  networkError?: boolean;
256
698
  /** 网络异常的原因或提示 */
257
699
  networkErrorReason?: string;
700
+ /**
701
+ * 确定性降级说明(非错误)。
702
+ *
703
+ * 为什么与 networkError 分开:仓库关闭 Issues、未启用 Discussions 这类
704
+ * 「配置事实」不是故障,不该触发网络告警横幅与重试。它们只需要在面板上
705
+ * 安静地说明一句「该仓库未开启 Issues」,让用户知道空列表是正常的。
706
+ */
707
+ notices?: string[];
258
708
  /** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
259
709
  updateInfo?: UpdateInfo;
260
710
  /** 本地 Git 状态(ahead/behind/dirty,remote-tracking 快照语义);非 Git 工作区为 undefined */
@@ -278,6 +728,41 @@ export interface GitHubOverviewData {
278
728
  } | null;
279
729
  /** 当前工作区的 Git 分支列表(供右侧栏分支选择器 0ms 秒开) */
280
730
  branches?: GitBranchesResult | null;
731
+ /**
732
+ * 单仓库 PR 列表是否触及条数上限(仅触顶时下发)。
733
+ *
734
+ * 为什么必须透出:查询已放宽为 `--state all`,列表按 number 降序取前 N 条,
735
+ * 历史 PR 超过上限时界面会静默少显示——用户切到「Closed」看到几条
736
+ * 就会误以为仓库只有这么几个已关闭 PR,无法区分「就这么多」与「被截断了」。
737
+ * 与全局面板的 prsTruncated 同一语义。
738
+ */
739
+ prsTruncated?: boolean;
740
+ /** 单仓库 Issue 列表是否触及条数上限(语义同 prsTruncated) */
741
+ issuesTruncated?: boolean;
742
+ /** Actions 运行列表是否还有更多(语义同 prsTruncated;右侧栏分页用) */
743
+ runsTruncated?: boolean;
744
+ /** 仓库全部 PR 数(含 open/merged/closed) */
745
+ prTotal?: number;
746
+ /** 处于 open 的 PR 数 */
747
+ prOpenTotal?: number;
748
+ /** 已合并的 PR 数 */
749
+ prMergedTotal?: number;
750
+ /** 已关闭的 PR 数(GitHub 语义:含已合并) */
751
+ prClosedTotal?: number;
752
+ /** 仓库全部 Issue 数(不含 PR——用 `is:issue` 限定符区分) */
753
+ issueTotal?: number;
754
+ /** 处于 open 的 Issue 数 */
755
+ issueOpenTotal?: number;
756
+ /** 已关闭的 Issue 数 */
757
+ issueClosedTotal?: number;
758
+ /**
759
+ * Actions 运行总数。
760
+ *
761
+ * 来源是 REST `GET /repos/{owner}/{repo}/actions/runs?per_page=1` 的 `total_count`
762
+ * ——GitHub GraphQL 没有 workflow runs 连接,拿不到 totalCount。
763
+ * 同样与分页解耦:`runs.length` 只是已加载条数。
764
+ */
765
+ runTotal?: number;
281
766
  }
282
767
  export interface UserRepoItem {
283
768
  name: string;
@@ -328,14 +813,37 @@ export interface GlobalOverviewData {
328
813
  myPrs: Array<{
329
814
  number: number;
330
815
  title: string;
816
+ /**
817
+ * PR 状态。为什么必须下发:主面板默认展示「全部」而非仅 open
818
+ * (实测账号可能零 open PR,只查 open 会恒为空白),
819
+ * 前端要靠这个字段做状态胶囊筛选与徽章标注。
820
+ */
821
+ state?: 'OPEN' | 'CLOSED' | 'MERGED';
331
822
  repository: {
332
823
  nameWithOwner: string;
333
824
  };
334
825
  url: string;
335
826
  updatedAt: string;
827
+ isDraft?: boolean;
336
828
  }>;
337
829
  myIssues: GlobalIssueItem[];
338
830
  issueStats?: IssueStats;
831
+ /**
832
+ * PR 检索是否触及上限(仅触顶时下发)。
833
+ * 为什么必须透出:去掉 open 过滤后按 updatedAt 降序返回最近 N 条,
834
+ * 若账号历史 PR 超过上限,界面会静默少显示,用户无法区分
835
+ * 「列表到此为止」与「数据没取到」。
836
+ */
837
+ prsTruncated?: boolean;
838
+ /** Issues 三路检索是否任一触及上限(有更多可加载) */
839
+ issuesTruncated?: boolean;
840
+ prTotal?: number;
841
+ /** 账号 open PR 数 */
842
+ prOpenTotal?: number;
843
+ /** 账号已合并 PR 数 */
844
+ prMergedTotal?: number;
845
+ /** 账号已关闭 PR 数(GitHub 语义:含已合并) */
846
+ prClosedTotal?: number;
339
847
  lastUpdated: string;
340
848
  error?: string;
341
849
  /** 网络是否发生异常(超时、连接拒绝、代理阻断等) */
@@ -344,6 +852,11 @@ export interface GlobalOverviewData {
344
852
  networkErrorReason?: string;
345
853
  /** 工作区矩阵读取失败的原因。区分「没有工作区」与「读不到工作区」,避免把故障显示成空状态 */
346
854
  workspaceError?: string;
855
+ /**
856
+ * 确定性降级说明(非错误):仓库关闭 Issues、未启用 Discussions 等配置事实。
857
+ * 与 networkError 互斥——它不是故障,不该触发告警横幅与重试。
858
+ */
859
+ notices?: string[];
347
860
  /** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
348
861
  updateInfo?: UpdateInfo;
349
862
  }
@@ -7,6 +7,12 @@
7
7
  * 3. 拒绝技术自嗨清单,从用户行动视角组织:3分钟快速上手、Agent实战Prompt、双界面图解与常见排错;
8
8
  * 4. 宽屏卡片排列,配备 Prompt 一键复制到剪贴板与短暂反馈气泡;
9
9
  * 5. 监听键盘 Esc 键与遮罩点击秒关,复制完指令直接回到聊天输入框发送,零打扰、不中断工作流。
10
+ *
11
+ * 【内容与代码同步的纪律】
12
+ * 本文件里出现的工具数量、action 数量、Tab 名称、按钮文案,全部以当前代码为准:
13
+ * 工具矩阵 = src/main.ts 的 19 处 ctx.tools.register;
14
+ * Tab 布局 = src/client.ts 的 TAB_DEFS 与 src/ui/workspace-panel.ts 的四个标签;
15
+ * 若后续增删工具或 Tab,必须同步改这里,否则用户会照着过时说明去找不存在的入口。
10
16
  */
11
17
  export declare function GithubHelpModal(props: {
12
18
  isOpen: boolean;