dsh-github-flow 0.5.0 → 0.6.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 +16 -4
  2. package/README.md +16 -4
  3. package/docs/UPGRADE-NOTES.md +8 -8
  4. package/docs/architecture.md +33 -17
  5. package/docs/images/1.png +0 -0
  6. package/lib/api/routes-actions.js +139 -8
  7. package/lib/api/routes-overview.js +147 -29
  8. package/lib/cache.d.ts +77 -4
  9. package/lib/cache.js +114 -7
  10. package/lib/client.d.ts +7 -3
  11. package/lib/client.js +3309 -314
  12. package/lib/executor/error-classifier.d.ts +55 -0
  13. package/lib/executor/error-classifier.js +199 -0
  14. package/lib/executor/git-parser.d.ts +124 -0
  15. package/lib/executor/git-parser.js +397 -0
  16. package/lib/executor.d.ts +122 -4
  17. package/lib/executor.js +685 -80
  18. package/lib/tools/help.js +22 -7
  19. package/lib/tools/image-utils.d.ts +27 -0
  20. package/lib/tools/image-utils.js +73 -3
  21. package/lib/types.d.ts +331 -0
  22. package/lib/ui/help-modal.d.ts +6 -0
  23. package/lib/ui/help-modal.js +113 -15
  24. package/lib/ui/issue-modal.d.ts +14 -0
  25. package/lib/ui/issue-modal.js +366 -0
  26. package/lib/ui/markdown-lite.d.ts +66 -0
  27. package/lib/ui/markdown-lite.js +312 -0
  28. package/lib/ui/network-banner.d.ts +12 -0
  29. package/lib/ui/network-banner.js +35 -0
  30. package/lib/ui/pr-modal.d.ts +17 -0
  31. package/lib/ui/pr-modal.js +516 -0
  32. package/lib/ui/repo-badges.d.ts +43 -0
  33. package/lib/ui/repo-badges.js +82 -0
  34. package/lib/ui/repo-selector.d.ts +14 -0
  35. package/lib/ui/repo-selector.js +112 -0
  36. package/lib/ui/storage.d.ts +47 -0
  37. package/lib/ui/storage.js +139 -0
  38. package/lib/ui/styles.d.ts +1 -1
  39. package/lib/ui/styles.js +960 -0
  40. package/lib/ui/utils.js +13 -1
  41. package/lib/ui/workspace-panel.js +169 -27
  42. package/lib/version.d.ts +1 -1
  43. package/lib/version.js +1 -1
  44. package/package.json +1 -1
package/README.en.md CHANGED
@@ -378,7 +378,9 @@ Click the **Octocat icon** in DSH's left navigation bar to open the full-screen
378
378
  * **Account-Wide Overview**: Live view of all cloud repositories, default branches, and star counts;
379
379
  * **Local Workspace Matrix**: Automatically detects open projects and bridges local directories with remote repos;
380
380
  * **3D Issue Governance Matrix**: Categorizes issues into *"📥 User Reported"*, *"🎯 Assigned to Me"*, and *"✍️ Created by Me"* with responsive pill filters;
381
- * **Update Notification Banner**: Smoothly glides down when an update is available with one-click automated upgrade.
381
+ * **Update Notification Banner**: Smoothly glides down when an update is available with one-click automated upgrade;
382
+ * **🔀 PR Review Workbench**: Click any PR to review author, branch flow, CI/review status, Markdown description, comment timeline, and the changed-file list with high-fidelity unified diffs — all without leaving DSH (diffs are sliced per file in memory, so switching files is instant);
383
+ * **📝 Issue Online Viewer**: Click any Issue to read the full discussion — body and comments rendered as Markdown, long content collapsed, GitHub-hidden (SPAM) comments explicitly flagged, with label chips, assignees, milestone, sub-issue progress, and linked PRs.
382
384
 
383
385
  <p align="center">
384
386
  <img src="./docs/images/1.png" alt="GitHub Global Cockpit" width="100%" />
@@ -402,8 +404,8 @@ Located in the right drawer of every chat workspace, seamlessly bridging your lo
402
404
  * Latest Release badge (`🏷️ v0.4.0`) anchored in the top right with hover details and direct release link;
403
405
  * **Commit Detail Modal**: Click any commit row to open a clean modal dialog centered within the right sidebar, featuring smooth vertical scrolling, full 40-char SHA, author email, date, multi-line commit message body, and file diff stat table (`--stat`), pure local 0ms instant display;
404
406
  * **Uncommitted Changes (`📁 Uncommitted (N)`)**: Full-width list of uncommitted files with color-coded status codes (`??` untracked, `M` modified, `D` deleted, `R` renamed) and change counts;
405
- * **Pull Requests (`🔀 PR (N)`)**: Open PR inspection with 3-state CI check rollup (`✅ CI Passed` / `❌ CI Failed` / `⏳ CI Pending`), merge conflict detection, review decisions (`✔ Approved` / `✋ Changes Requested` / `👀 Review Required`), Draft pills, and humanized relative timestamps;
406
- * **Workspace Issues (`📋 Issues (N)`)**: Direct issue task review with assignees, labels, and category pills;
407
+ * **Pull Requests (`🔀 PR (N)`)**: Shows **all** PRs (merged/closed included, default "All"), with status pills `All / 🟢 Open / 🟣 Merged / ⚪ Closed` showing per-state counts; 3-state CI rollup (`✅ CI Passed` / `❌ CI Failed` / `⏳ CI Pending`), merge conflict detection, review decisions (`✔ Approved` / `✋ Changes Requested` / `👀 Review Required`), Draft pills, and humanized timestamps; **click a card to open the in-app PR review workbench**, while `GitHub ↗` still jumps to the browser;
408
+ * **Workspace Issues (`📋 Issues (N)`)**: Shows **all** issues (closed included, default "All"), with status pills `All / 🟢 Open / ⚪ Closed`; **click a card to open the in-app Issue viewer** (Markdown body / comment timeline / labels & milestone, Esc to close), while `GitHub ↗` still jumps to the browser;
407
409
  * **Actions Pipeline (`⚡ Actions (N)`)**: Real-time CI workflow status tracking with trigger event badges (`push`/`schedule`/`manual`/`PR`), relative time, and self-healing diagnostic notes for failed runs (embedding `run_id` for Agent one-click log inspection);
408
410
  * **⚙️ Full Settings Access**: Dedicated settings button reusing the same settings modal as the cockpit (all four sections: Sync / Proxy / Update Source / About) — no need to switch back to the main page.
409
411
 
@@ -411,7 +413,17 @@ Located in the right drawer of every chat workspace, seamlessly bridging your lo
411
413
  <img src="./docs/images/2.png" alt="Session Right Sidebar Panel" width="100%" />
412
414
  </p>
413
415
 
414
- ### 3. Settings Center (Settings Modal)
416
+ ### 3. Help & Playbook Modal
417
+ Click **`[📖 Help]`** in the top right of the Cockpit (or the ❓ button in the right sidebar) to open the 920px immersive manual; press Esc or click the backdrop to close. Five sections:
418
+ * **🚀 3-Minute Quickstart**: login check → workspace binding → first Agent instruction, plus a capability overview (18 tools / 103 business actions / 2 workbenches);
419
+ * **🤖 Agent Prompt Playbook**: 7 high-frequency scenarios (open a PR, CI triage, daily standup, diff review, screenshot triage, bulk label governance, RFC discussion), each with one-click prompt copy;
420
+ * **🖥️ UI Walkthrough**: per-tab documentation for the 4-tab Cockpit and the 5-tab right sidebar, plus shared modal interaction habits;
421
+ * **🧰 18-Tool Reference**: all tools and actions grouped into daily development / delivery & config / ecosystem & collaboration / escape hatches, with a `/gh` command cheat sheet;
422
+ * **🔑 Troubleshooting**: login, missing scopes, the 24h supply-chain protection window, proxy flapping, empty lists, and why a DSH restart is needed.
423
+
424
+ > The same knowledge is exposed to Agents via the `github_help` tool — both sides are asserted against the source tree by phase33, so docs drifting from code fails the test suite.
425
+
426
+ ### 4. Settings Center (Settings Modal)
415
427
  Click **`[⚙️ Settings]`** in the top right of the Cockpit (or the ⚙️ button in the right sidebar) to open the sectioned settings modal:
416
428
 
417
429
  **🔄 Data Sync Section** — tune data fetching rhythm and perception sensitivity:
package/README.md CHANGED
@@ -121,7 +121,7 @@ dsh plugin --profile web add dsh-github-flow
121
121
  * `pin` / `unpin`:🆕 仓库 Issue 置顶状态管理;
122
122
  * `lock` / `unlock`:🆕 锁定讨论区,支持 4 种官方合法理由(`off_topic` / `resolved` / `spam` / `too_heated`);
123
123
  * `status`:🆕 检索指派给当前登录用户的待办议题全景面板;
124
- * `view_images`:🆕 下载 Issue 报障截图(正文+评论全量识别并转为本地图片块,Agent 直接"看"截图);
124
+ * `view_images`:🆕 下载 Issue 报障截图(正文+评论全量识别并转为本地图片块,Agent 直接"看"截图);落地目录 `~/.dsh/data/dsh-github-flow/images/`(内容寻址去重),**超 15MB 时按 mtime 自动 LRU 淘汰至 10MB 低水位**,并保护当前批次已写入的文件;测试可用 `DSH_IMAGE_DIR` 重定向该目录;
125
125
  * `attach_images`(参数):🆕 支持本地文件/截图直接作为附件随 Issue 发布(单批上限 50 个文件,需 gh ≥ 2.99)。
126
126
 
127
127
  ---
@@ -377,6 +377,8 @@ dsh plugin --profile web add dsh-github-flow
377
377
  * **账号全景概览**:实时透视名下所有 GitHub 仓库、默认分支与 Star 状态;
378
378
  * **本地工作区联动矩阵**:自动探测本机所有打开的项目,显示当前工作区与远程 GitHub 仓库的关联状态;
379
379
  * **三维待办 Issue 矩阵**:智能划分「📥 用户提单(反馈到我名下仓库)」、「🎯 指派待办(分配给我)」与「✍️ 我发起的」,支持一键药丸过滤;
380
+ * **🔀 PR 在线审阅工作台**:点击任意 PR,无需离开 DSH 即可查看作者与分支流向、CI/审查状态、Markdown 描述、评论时间线,以及变动文件列表与高保真 Unified Diff 对比(按文件内存切片,切换文件 0ms);
381
+ * **📝 Issue 在线查看弹窗**:点击任意 Issue 直接查看完整讨论——正文与评论均为 Markdown 渲染、长内容自动折叠、被 GitHub 折叠的垃圾评论显式标注、标签色块/指派人/里程碑/子议题进度/关联 PR 一目了然,评论时间线按时间顺序完整呈现;
380
382
  * **升级提示横幅**:检测到新版本时在顶部优雅滑出,提供一键自动升级与更新日志直达。
381
383
 
382
384
  <p align="center">
@@ -401,8 +403,8 @@ dsh plugin --profile web add dsh-github-flow
401
403
  * 右上角锚定最新发布 Release 徽章(`🏷️ v0.4.0`),悬停查看发布时间与预发布标示,点击直达;
402
404
  * **提交详情模态弹窗**:点击任意提交记录行,在右侧栏内局部居中弹出,内置上下滑动区域,完整展示 40 位 SHA、提交者姓名邮箱、日期、多行详细正文说明与文件变更统计(`--stat`),纯本地 0ms 秒开,支持按 `Esc` 或点击遮罩随时关闭;
403
405
  * **未提交清单 (`📁 未提交 (N)`)**:展示工作区未提交的文件清单,状态码色彩区分(`??` 新增、`M` 修改、`D` 删除、`R` 重命名)与变更总数;
404
- * **拉取请求 (`🔀 PR (N)`)**:展示当前开放 PR,带有三态 CI 门禁(`✅ CI 通过` / `❌ CI 失败` / `⏳ CI 运行中`)、冲突检测、审核判定(`✔ 已批准` / `✋ 需修改` / `👀 待审查`)、Draft 草稿标示与人性化相对时间;
405
- * **仓库议题 (`📋 Issues (N)`)**:直接在侧边栏审查待办任务,支持指派者、标签与多维状态药丸;
406
+ * **拉取请求 (`🔀 PR (N)`)**:展示**全部** PR(含已合并/已关闭,默认「全部」),顶部状态胶囊 `全部 / 🟢 Open / 🟣 Merged / ⚪ Closed` 一键筛选并显示各档计数;带三态 CI 门禁(`✅ CI 通过` / `❌ CI 失败` / `⏳ CI 运行中`)、冲突检测、审核判定(`✔ 已批准` / `✋ 需修改` / `👀 待审查`)、Draft 草稿标示与人性化相对时间;**点击卡片在线唤起 PR 审阅工作台(描述 / 评论 / 文件 Diff)**,右侧 `GitHub ↗` 保留浏览器直达;
407
+ * **仓库议题 (`📋 Issues (N)`)**:展示**全部** Issue(含已关闭,默认「全部」),顶部状态胶囊 `全部 / 🟢 Open / ⚪ Closed` 一键筛选并显示各档计数;支持指派者、标签与多维状态药丸;**点击卡片在线查看 Issue 详情(Markdown 正文 / 评论时间线 / 标签与里程碑,Esc 关闭)**,右侧 `GitHub ↗` 保留浏览器直达;
406
408
  * **构建流水线 (`⚡ Actions (N)`)**:实时关注工作流 CI 状态,展示触发方式(`push`/`定时`/`手动`/`PR`)、相对时间与失败构建的 Agent 自愈诊断指引(嵌入 run_id);
407
409
  * **⚙️ 完整设置入口**:右侧栏提供设置按钮,与主页复用同一设置弹窗(数据同步 / 网络代理 / 更新设置 / 关于 四分区完整可用),无需切回主页调整。
408
410
 
@@ -410,7 +412,17 @@ dsh plugin --profile web add dsh-github-flow
410
412
  <img src="./docs/images/2.png" alt="右侧栏工作区面板" width="100%" />
411
413
  </p>
412
414
 
413
- ### 3. 设置中心 (Settings Modal)
415
+ ### 3. 使用指南大窗口 (Help & Playbook)
416
+ 点击驾驶舱右上角 **`[📖 使用指南]`**(或右侧栏的 ❓ 按钮)打开 920px 宽屏沉浸式手册,按 Esc 或点遮罩即可关闭。共 5 个分类:
417
+ * **🚀 3分钟极速上手**:登录确认 → 绑定仓库 → 向 Agent 下指令的三步闭环,附能力总览(18 工具 / 103 业务动作 / 2 套工作台);
418
+ * **🤖 Agent 提示词实战**:7 大高频场景(建 PR、CI 排障、待办盘点、Diff 审查、看图定位、批量标签治理、RFC 研讨),每条配「📋 一键复制 Prompt」;
419
+ * **🖥️ 两大界面操作图解**:主面板 4 标签与右侧栏 5 Tab 的逐项说明,以及两个弹窗的通用操作习惯;
420
+ * **🧰 18 大工具速查**:按「日常研发 / 交付配置 / 生态协作 / 兜底元能力」四组列出全部工具与动作,附 `/gh` 指令速查;
421
+ * **🔑 常见自愈排错**:登录、权限 Scope、24h 安全保护期、代理抖动、列表为空、为何需重启 DSH 共 6 条。
422
+
423
+ > 同一套知识也通过 `github_help` 工具提供给 Agent——两侧口径由 phase33 测试强制对账,代码演进而文档没跟上会直接测试失败。
424
+
425
+ ### 4. 设置中心 (Settings Modal)
414
426
  点击全景驾驶舱右上角 **`[⚙️ 设置]`**(或右侧栏的 ⚙️ 按钮)打开分区式设置弹窗,左侧分区导航、右侧内容独立滚动(窄屏自动折叠为横向页签):
415
427
 
416
428
  **🔄 数据同步分区**——调优数据拉取节奏与感知灵敏度:
@@ -10,8 +10,8 @@
10
10
 
11
11
  | # | 文件 | 位置 | 当前值 | 说明 |
12
12
  |---|---|---|---|---|
13
- | 1 | [package.json](../package.json) | 第 3 行 `"version": "0.5.0"` | `0.5.0` | npm 发布版本、CI 取版来源 |
14
- | 2 | [src/version.ts](../src/version.ts) | 第 9 行 `CURRENT_PLUGIN_VERSION = '0.5.0'` | `'0.5.0'` | 运行时版本比较基准(假横幅的教训来源) |
13
+ | 1 | [package.json](../package.json) | 第 3 行 `"version": "0.6.0"` | `0.6.0` | npm 发布版本、CI 取版来源 |
14
+ | 2 | [src/version.ts](../src/version.ts) | 第 9 行 `CURRENT_PLUGIN_VERSION = '0.6.0'` | `'0.6.0'` | 运行时版本比较基准(假横幅的教训来源) |
15
15
 
16
16
  **两处必须同值**。`pnpm run build` 会硬校验([scripts/build.mjs](../scripts/build.mjs) 的
17
17
  `assertVersionConsistency`),不一致直接构建失败——但不要依赖拦截,发版前手动核对。
@@ -41,14 +41,14 @@
41
41
  | # | 机制 | 说明 |
42
42
  |---|---|---|
43
43
  | 9 | CI 取版(.github/workflows/npm-publish.yml:62) | `VERSION=$(node -p "require('./package.json').version")`——从 package.json 读,无硬编码 |
44
- | 10 | Git tag | `git tag v0.5.0` 手动打,值必须 = package.json 版本(tag 与 package.json 不一致时:CI 发布的 npm 版本以 package.json 为准,GitHub Release 标题以 tag 为准,出现两者错位) |
44
+ | 10 | Git tag | `git tag v0.6.0` 手动打,值必须 = package.json 版本(tag 与 package.json 不一致时:CI 发布的 npm 版本以 package.json 为准,GitHub Release 标题以 tag 为准,出现两者错位) |
45
45
 
46
46
  ## 五、发版完整流程(含版本修改)
47
47
 
48
48
  ```powershell
49
49
  # 1. 改版本(两处同值)
50
- # package.json → "version": "0.5.0"
51
- # src/version.ts → CURRENT_PLUGIN_VERSION = '0.5.0'
50
+ # package.json → "version": "0.6.0"
51
+ # src/version.ts → CURRENT_PLUGIN_VERSION = '0.6.0'
52
52
 
53
53
  # 2. 文档已用占位符(npm-publish-guide 示例、README 安装命令),发版无需改文档版本号
54
54
 
@@ -57,12 +57,12 @@ pnpm run build
57
57
  pnpm test # 全量测试全绿
58
58
 
59
59
  # 4. 本机 e2e(必须先重启 DSH 桌面进程——Node 模块缓存,软链重建不注入运行中进程)
60
- # 重启后验证:GET /api/github/check-update 的 currentVersion = 0.5.0
60
+ # 重启后验证:GET /api/github/check-update 的 currentVersion = 0.6.0
61
61
 
62
62
  # 5. 提交 + 打 tag + 推送(tag 值 = package.json 版本)
63
63
  git add -A
64
- git commit -m "chore(release): v0.5.0"
65
- git tag v0.5.0
64
+ git commit -m "chore(release): v0.6.0"
65
+ git tag v0.6.0
66
66
  git push origin master --tags
67
67
  # CI(npm-publish.yml)自动:构建→发布 npm→创建 GitHub Release
68
68
 
@@ -49,13 +49,14 @@
49
49
  │ │ - ruleset(check)│ │ - /update-settings │ │
50
50
  │ │ - cache (治理) │ │ - /proxy (全局代理) │ │
51
51
  │ │ - gist (分享) │ │ - /auth (轻量探针) │ │
52
- │ │ - status (健康) │ └──────────┬────────────┘ │
53
- │ │ - agent_task │ │ │
54
- │ │ - discussion │ ▼ │
55
- │ │ - project (v2) │ ┌───────────────────────────┐ │
56
- │ │ - codespace(MVP)│ │ git-watcher (独立服务) │ │
57
- │ │ - help (元指南) │ │ - .git/HEAD & index │ │
58
- │ └────────┬─────────┘ │ - 800ms 防抖/35s 超时 │ │
52
+ │ │ - status (健康) │ │ - /issue-detail (Issue│ │
53
+ │ │ - agent_task │ │ 在线详情与评论) │ │
54
+ │ │ - discussion │ │ - /pr-detail (PR 审阅)│ │
55
+ │ │ - project (v2) │ └──────────┬────────────┘ │
56
+ │ │ - codespace(MVP)│ ┌───────────────────────────┐ │
57
+ │ │ - help (元指南) │ │ git-watcher (独立服务) │ │
58
+ │ └────────┬─────────┘ │ - .git/HEAD & index │ │
59
+ │ │ │ - 800ms 防抖/35s 超时 │ │
59
60
  │ │ └───────────────────────────┘ │
60
61
  │ ▼ │ │
61
62
  │ ┌──────────────────────────────────┐ │ │
@@ -86,7 +87,7 @@
86
87
  │ │ · icons.ts (Octocat, Git 官方菱形分支矢量组件, 插件徽标) │ │
87
88
  │ │ · storage.ts (LocalStorage SWR 状态机与偏好存储) │ │
88
89
  │ │ · components/ (分支下拉/认证引导/升级通知/离线横幅/状态胶囊) │ │
89
- │ │ · modals/ (Commit 详情弹窗 / 4分区全局设置弹窗) │ │
90
+ │ │ · modals/ (Commit 详情弹窗 / PR 审阅工作台 / Issue 在线查看 / 全局设置弹窗) │ │
90
91
  │ │ · panels/ (首页全景大面板 workspace-panel.ts) │ │
91
92
  │ │ │ │
92
93
  │ │ 核心组装入口 (src/client.ts, ~1,260 行高内聚): │ │
@@ -114,8 +115,8 @@
114
115
 
115
116
  ### 2.3 上下文 Token 保护与防御性输出包装 (Token Budget Guard)
116
117
  - 所有的 `gh` 查询原生采用 `--json <fields>` 结构化投影;
117
- - 对长文本输出(如大型 PR 的完整 `diff`、CI 失败步骤日志)设置安全截断阈值(默认 24KB),采用码点安全切割与二分查找字节截断(`src/executor/process-utils.ts`),保留头尾关键上下文并添加显式截断标记,杜绝撑爆模型上下文;
118
- - 列表与详情输出经独立 Markdown 渲染引擎(`src/renderers/` 7 大领域模块)转化为高密度表格与卡片(带状态徽章、可点击超链接与竖线转义防护),实测 20 条 PR 列表输出压缩 70%+ Token;
118
+ - 对长文本输出(如大型 PR 的完整 `diff`、CI 失败步骤日志)设置安全截断阈值(默认 24KB),采用码点安全切割与二分查找字节截断(`src/executor/process-utils.ts`),保留头尾关键上下文并添加显式截断标记,杜绝撑爆模型上下文;**程序化消费的大载荷可按调用点显式放宽**(PR diff 与 Issue 详情放宽至 2MB 字符 / 8MB 字节),因为这两类数据只在服务端切片后渲染,不进入模型上下文;**且一旦真的被截断必须显式失败**——截断会让 `JSON.parse` 被跳过、`data` 变 `undefined`,静默降级会渲染出「元数据全空 + 零评论」的假详情;
119
+ - 列表与详情输出经独立 Markdown 渲染引擎(`src/renderers/` 15 大领域模块 + `common.ts` + `markdown.ts` 门面)转化为高密度表格与卡片(带状态徽章、可点击超链接与竖线转义防护),实测 20 条 PR 列表输出压缩 70%+ Token;
119
120
  - 所有 Agent 工具的输出统一经过归一化,列表自动打包为 `{ items, count }`,标量打包为 `{ result }`,100% 严丝合缝契合 DSH 工具网关模式约束;
120
121
  - **gh 版本守卫降级链(runGuarded)**:preview 命令与新特性按 `minVersion` 门槛路由——版本达标直通主命令、不足走 `fallback` 降级链路(结果携带 `degraded/degradedReason` 结构化标注,**降级不是静默的**,渲染层对 degraded 结果显式追加链路说明)、无降级时报错附平台升级指引;版本探测自身失败 fail-open 直通(守卫是路由优化不是安全边界)。版本比较采用数值分段(`src/executor/version-utils.ts`,杜绝 `'2.96.0' > '2.100.0'` 字典序陷阱),探测结果 30s TTL 缓存且只缓存成功值;
121
122
  - **AI_AGENT 识别信号注入**:每次 gh 子进程启动注入 `AI_AGENT=dsh`(gh detect.go 最高优先级通用信号)——gh ≥2.99 自动切换 text-only spinner、≥2.100 命令误用时打印完整 help(模型可据此自我纠正)、telemetry 归因到 dsh;旧版 gh 忽略该变量零副作用。经 `agentEnvInjection` 配置开关(默认开启)可恢复 gh 原生行为。
@@ -154,22 +155,24 @@
154
155
 
155
156
  ### 3.4 前端客户端解耦与自包含 Bundle 机制 (`src/ui/` + `src/client.ts`)
156
157
  - **源码物理剥离**:
157
- - [src/client.ts](../src/client.ts)(由 5,290 行直降至 **1,263 行,瘦身 76%**):仅保留右侧栏核心协同 `renderGithubRepoView` 与 `GithubRightbarBody` 及插件入口;
158
- - 12 个独立子模块下沉至 [src/ui/](../src/ui/) 目录(样式 1,295 行、全景大面板 732 行、设置弹窗 561 行、业务卡片 60~290 行);
158
+ - [src/client.ts](../src/client.ts)(当前约 2,400 行):保留右侧栏核心协同 `renderGithubRepoView` 与 `GithubRightbarBody` 及插件入口;新增 UI 优先下沉到 `src/ui/`,不在入口文件里堆叠;
159
+ - **18 个独立子模块**下沉至 [src/ui/](../src/ui/) 目录:样式 2,506 行、全景大面板 932 行、PR 审阅弹窗 817 行、Issue 查看弹窗 565 行、设置弹窗 552 行、使用指南 408 行,其余卡片类组件 60~300 行;
159
160
  - **命名空间防护**:
160
161
  - 严禁在与 `package.json` 的 `exports["./client"]` 同级创建名为 `client/` 的目录,统一命名为 `src/ui/` 与编译产物 `lib/ui/`,彻底杜绝模块解析器产生“同名文件 vs 目录”的歧义报错;
161
162
  - **自包含 Bundle 聚合器 (`scripts/build.mjs`)**:
162
163
  - 构建脚本按严格拓扑顺序将 `lib/ui/` 子模块内联注入 `lib/client.js` 的 `window.__ModuleLoader__.load` 闭包中,移除 `export` 关键字;
163
- - 保证 `GithubRightbarBody` 位于 `GithubWorkspacePanel` 之前(满足切片测试断言);
164
- - 对嵌入的 1,290 行纯 CSS 实施紧凑压缩,消除空行与多余注释,使编译产物从 5,282 行减至 4,008 行;
164
+ - **顺序即依赖**:`markdown-lite → pr-modal → issue-modal → …` 不可随意重排(`issue-modal` 复用了 `pr-modal` 的 `CollapsibleBody`);`workspace-panel` 置于后置列表,保证 `GithubRightbarBody` 位于 `GithubWorkspacePanel` 之前;
165
+ - 对嵌入的约 2,500 行纯 CSS 实施紧凑压缩;压缩后强制校验「无注释定界符残留 / 无行首 `*` 悬空段 / 花括号配平」,任一项不过直接构建失败——非法 CSS 会被浏览器**静默丢弃整条规则**,只表现为「样式莫名不生效」,极难排查;
165
166
  - **React Hook 时序安全防线**:
166
- - 组件内引用的 `useRef`(如 `containerRef`)必须在组件声明最前列初始化,严禁跨行下置引发变量提升 `undefined`;所有的 Ref 访问点必须配备 `containerRef && containerRef.current` 守卫,杜绝运行时渲染崩溃。
167
+ - 全部 Hook(含 `useEffect`)必须位于任何 early return 之前,否则「参数由空变有值」时两次渲染的 Hook 数量不一致,触发 `Rendered fewer hooks than expected` 崩溃;
168
+ - `renderGithubRepoView` 这类每次渲染都重新执行的普通函数内**禁止**调用 Hook,状态必须住在有 Hook 的组件体内再传参;
169
+ - 组件内引用的 `useRef`(如 `containerRef`)必须在组件声明最前列初始化,严禁跨行下置引发变量提升 `undefined`;所有的 Ref 访问点必须配备 `containerRef && containerRef.current` 守卫。
167
170
 
168
171
  ---
169
172
 
170
173
  ## 4. 自动化测试体系 (Test Suite)
171
174
 
172
- 项目配备了 **20 个独立阶段的全量自动化回归测试套件(`test/run-all.mjs`)**,覆盖 **880+ 项全量自动化测试断言**:
175
+ 项目配备了 **33 个独立阶段的全量自动化回归测试套件(`test/run-all.mjs`)**,覆盖 **1784 项全量自动化测试断言**:
173
176
  - `phase1`:Issue / Run / PR / Repo / Help 命令装配与确认门禁;
174
177
  - `phase2`:Native Markdown 渲染与 Token 压缩率实测(>60% 降幅);
175
178
  - `phase3`:Release 资产交付、Secret 密文管道与 Skills 检索;
@@ -189,4 +192,17 @@
189
192
  - `phase17`:github_skill publish(agentskills.io 规范自检、--fix 修复与发布闭环);
190
193
  - `phase18`:github_agent_task(云端编码智能体会话)与 github_discussion(社区讨论与RFC);
191
194
  - `phase19`:github_project(Projects v2 看板)、github_codespace(云端开发机)与 skill update;
192
- - `phase20`:TelemetryRingBuffer、Cordis tools/result 事件总线运行时遥测、降级率统计与健康度看板。
195
+ - `phase20`:TelemetryRingBuffer、Cordis tools/result 事件总线运行时遥测、降级率统计与健康度看板;
196
+ - `phase21`:未提交数据滚动分页、文件级 Diff 二级下钻与 DSH 原生打开文件联动;
197
+ - `phase22`:remote 配置指纹与缓存失效契约(parseRemoteUrls 段扫描、指纹对书写顺序/命名免疫、worktree `.git` 指针解析、TtlCache 指纹命中与保守失效);
198
+ - `phase23`:离线身份解析对齐 git 规则(branch.<当前分支>.remote → origin → 首个三级回落、`.` 特殊值、六种 URL 形态、worktree HEAD/config 分居、旧「取第一个 github.com」正则移除断言);
199
+ - `phase24`:仓库拓扑与权限字段(isFork / parent 拼接 / viewerPermission 五档白名单)、远程布局四分类(github-only / mixed / no-github / no-remote)与混合布局的顺序免疫选择、默认分支从 refs/remotes/<remote>/HEAD 符号引用动态推断;
200
+ - `phase25`:右侧栏仓库徽章可视化(resolveRepoBadges 纯函数:fork 徽章与血缘行、READ/TRIAGE 只读徽章、可写与未知权限零徽章、parent.url 协议白名单防 javascript:/data: 注入)、bundle 内联断言与「零新增网络往返」静态约束;
201
+ - `phase26`:写操作权限自愈(GraphQL 权限错误补入确定性分类、permissionRemedy 按 Triage/Write/Admin 三档给出可操作指引并拼入 error 供模型自愈、限流 403 不误判、非权限错误零噪音、前端快捷按钮保持可用的防回退断言);
202
+ - `phase27`:关联仓库自由切换(getAssociatedRepos 双 remote / Fork+Parent 纯本地多仓推导、RepoSelectorDropdown 下拉组件、setDefaultRepo 官方 set-default 对齐与防注入白名单、Fork 血缘行一键双向快捷切换);
203
+ - `phase28`:PR 在线审阅工作台与主面板标签页解耦(splitUnifiedDiff 按文件切片索引、core.quotePath 八进制转义还原、含 ` b/` 路径的等值分割判定、删除文件路径回退、getPrDetail 参数白名单与 diff 阈值放宽不被截断、响应体不回传 fullDiff 防膨胀、**行级审查评论独立 API 通道 + original_line 回退 + 失败降级**、/api/github/pr-detail 路由真实调用与 405/400/500 错误路径、**action/branch-switch 请求体 256KB 上限与 413 行为验证**、主面板 mywork 拆为 prs/issues 四标签页、React Hooks 顺序静态防回退);
204
+ - `phase29`:数据可见性与 PR 弹窗交互(PR/Issue 查询移除 `--state=open` 硬过滤并补齐 state 字段、clampFilePaneWidth 纯函数边界与容器极窄保下限、文件区布局偏好 localStorage 往返与脏数据回退、分隔条拖拽 document 级监听与卸载兜底清理、折叠双恢复入口、**PR 标题行与 GitHub 跳转按钮同行(min-width:0 + flex-shrink:0 防长标题挤出)**、空态区分「筛选为空」与「账号无记录」);
205
+ - `phase30`:PR 弹窗 Markdown 渲染与滚动适配(markdown-lite 零依赖解析器:标题/粗斜体/行内码/围栏代码块/列表/引用/分隔线/任务列表;链接协议白名单拒绝 javascript:/data:/vbscript:/协议相对;HTML 标签降级为纯文本;estimateContentLines 折叠阈值估算;卡片 flex:0 0 auto 修复「长内容无法滚动」;CollapsibleBody 提升到模块顶层避免内联组件导致展开状态被重置)。
206
+ - `phase31`:Issue 在线查看工作台(getIssueDetail 参数白名单含小数/负数拒绝、**2MB 字符阈值防长讨论被截断成假空详情**、老版本 gh `Unknown JSON field` 的 BASE 字段降级链与 fieldsDegraded 标记、非字段类失败不重试、截断与解析缺失显式失败并给出浏览器直达链接、state/author/labels/milestone/subIssuesSummary/closedByPullRequestsReferences 归一化、SPAM 隐藏评论标记保留、图片清单正文+评论合并去重、/api/github/issue-detail 路由 200/400/405/500 全路径、action 通道 issue_detail 对称可用、主面板与右侧栏 Issue 卡片改点击进弹窗 + 同行 GitHub ↗ 外跳 stopPropagation + 卡片禁内联 flex 布局、onSelectIssue 追加在形参末尾、React Hooks 顺序与 bundle 三模块拓扑顺序防回退)。
207
+ - `phase32`:右侧栏 PR/Issue 状态筛选(**单仓库查询显式 `--state all`**,修复「只显示已打开」根因——gh 默认 state 就是 open,数据在到达浏览器前已被过滤;条数上限 10→20 并抽成 RIGHTBAR_LIST_LIMIT 常量,依据 `gh pr list` 按 number 降序而非 updatedAt,取 10 条时非 OPEN 仅剩 5 条;触顶下发 prsTruncated/issuesTruncated 并渲染提示;PR 四档 all/open/merged/closed 与 Issue 三档 all/open/closed 胶囊复用既有样式类、计数先于过滤保证数字反映全量;偏好按 cwd 隔离存储、非法值与脏 JSON 归一回退、大写输入归一;空态区分「仓库无记录」与「筛选无结果」并给切换引导;Hook 位置(不可落在 renderGithubRepoView 内)、切仓库对齐只依赖 cwd、**作用域键无碰撞(/a/b 与 /a_b 不得压成同一个键)**、卡片布局纪律防回退)。
208
+ - `phase33`:使用指南内容与代码事实对账(help-modal 从 4 Tab 扩到 5 Tab,新增「18 大工具速查」;**工具数量对 src/main.ts 的 ctx.tools.register 数量、action 总数对 src/tools 的 enum 累计、Tab 名称对 TAB_DEFS 与 workspace-panel 逐个核对**,代码演进而指南没跟上即失败;覆盖 PR/Issue 在线弹窗、状态筛选三档、Markdown 渲染、仓库切换器、提交详情、Diff 下钻、view_images 等近期能力;FAQ 扩至 6 条含「列表为空但网页有记录」与「为何需重启 DSH」;**防编造断言:指南中每条 `gh auth refresh -s <scope>` 必须能在源码找到出处**;**github_help 知识库同步对账:mapping 表声明的每组 action 数逐个对 src/tools 的 enum 长度、每个业务 action 必须在知识库有落点、规模口径统一为「18 工具 / 103 业务 action」(排除 help 的 21 查询主题与 api 的 4 HTTP method),Agent 侧知识库与 UI 侧使用指南必须同口径**)。
package/docs/images/1.png CHANGED
Binary file
@@ -141,8 +141,12 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
141
141
  kind: 'exact',
142
142
  path: '/api/github/refresh',
143
143
  handler: async (req, res) => {
144
- cacheMap.clear();
145
- globalCache.clear();
144
+ // 连容灾底座一起清:用户点「重试刷新」的语义是「宁可空白也重来」。
145
+ // 只清活动缓存会留下一个致命死角——失败分支从不写缓存,于是 fallback
146
+ // 里的旧快照一旦冻结就永久生效(仓库身份改错了也永远出不来),
147
+ // 刷新按钮点了等于没点。
148
+ cacheMap.clear({ keepFallback: false });
149
+ globalCache.clear({ keepFallback: false });
146
150
  sendJson(res, 200, { ok: true, message: '缓存已清空,下次读取将拉取最新数据' });
147
151
  },
148
152
  });
@@ -155,9 +159,30 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
155
159
  sendJson(res, 405, { ok: false, message: '仅支持 POST 请求' });
156
160
  return;
157
161
  }
162
+ // 请求体上限:这些 action 的载荷都是短参数(标题/分支/PR 编号),
163
+ // 正常远小于 1KB。裸 `body += chunk` 无上限意味着一个超大 POST 能把
164
+ // 整个请求体堆进进程内存——本地服务也不该留这种自伤通道。
165
+ const MAX_ACTION_BODY_BYTES = 256 * 1024;
158
166
  let body = '';
159
- req.on('data', (chunk) => { body += chunk; });
167
+ let bodyBytes = 0;
168
+ let bodyTooLarge = false;
169
+ req.on('data', (chunk) => {
170
+ if (bodyTooLarge)
171
+ return;
172
+ bodyBytes += chunk.length;
173
+ if (bodyBytes > MAX_ACTION_BODY_BYTES) {
174
+ bodyTooLarge = true;
175
+ body = '';
176
+ // 报 KB 而非字节数:262144 这种数字对人没有意义
177
+ sendJson(res, 413, { ok: false, error: `请求体超过 ${MAX_ACTION_BODY_BYTES / 1024}KB 上限` });
178
+ req.destroy();
179
+ return;
180
+ }
181
+ body += chunk;
182
+ });
160
183
  req.on('end', async () => {
184
+ if (bodyTooLarge)
185
+ return;
161
186
  try {
162
187
  const payload = body ? JSON.parse(body) : {};
163
188
  const cwd = payload.cwd || process.cwd();
@@ -243,6 +268,28 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
243
268
  sendJson(res, 200, { ok: true, currentBranch: r.currentBranch, message: `已成功切换到分支 ${r.currentBranch}` });
244
269
  break;
245
270
  }
271
+ case 'set_default_repo':
272
+ case 'set_active_repo': {
273
+ const target = typeof payload.repo === 'string' ? payload.repo.trim() : (typeof payload.remote === 'string' ? payload.remote.trim() : '');
274
+ if (!target) {
275
+ sendJson(res, 400, { ok: false, error: '缺少目标仓库全名或 remote 别名' });
276
+ return;
277
+ }
278
+ const r = await executor.setDefaultRepo(cwd, target);
279
+ if (!r.ok) {
280
+ sendJson(res, 400, { ok: false, error: r.error });
281
+ return;
282
+ }
283
+ // 彻底重置缓存(包含容灾快照),使下一次概览请求立即以新基准重新发起探测
284
+ cacheMap.clear({ keepFallback: false });
285
+ globalCache.clear({ keepFallback: false });
286
+ sendJson(res, 200, {
287
+ ok: true,
288
+ target: r.target,
289
+ message: `已成功将基准仓库切换为 ${r.target}`,
290
+ });
291
+ break;
292
+ }
246
293
  case 'file_diff':
247
294
  case 'get_file_diff': {
248
295
  const fileTarget = typeof payload.file === 'string' ? payload.file.trim() : (typeof payload.filePath === 'string' ? payload.filePath.trim() : '');
@@ -264,6 +311,24 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
264
311
  sendJson(res, 200, { ok: true, data: r });
265
312
  break;
266
313
  }
314
+ case 'pr_detail':
315
+ case 'get_pr_detail': {
316
+ const prNumber = Number(payload.number || payload.prNumber);
317
+ const targetRepo = typeof payload.repo === 'string' ? payload.repo.trim() : undefined;
318
+ const r = await executor.getPrDetail(cwd, prNumber, targetRepo);
319
+ sendJson(res, r.ok ? 200 : 400, r);
320
+ break;
321
+ }
322
+ case 'issue_detail':
323
+ case 'get_issue_detail': {
324
+ // 与 pr_detail 对称的 Issue 详情通道:供 POST 调用方(Agent/脚本)复用,
325
+ // 与 GET /api/github/issue-detail 共用同一个 executor 方法。
326
+ const issueNumber = Number(payload.number || payload.issueNumber || payload.issue_number);
327
+ const targetRepo = typeof payload.repo === 'string' ? payload.repo.trim() : undefined;
328
+ const r = await executor.getIssueDetail(cwd, issueNumber, targetRepo);
329
+ sendJson(res, r.ok ? 200 : 400, r);
330
+ break;
331
+ }
267
332
  default:
268
333
  sendJson(res, 400, { ok: false, message: `未知的操作类型: ${payload.action}` });
269
334
  }
@@ -307,9 +372,30 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
307
372
  sendJson(res, 405, { ok: false, message: '仅支持 POST 请求' });
308
373
  return;
309
374
  }
375
+ // 请求体上限:这些 action 的载荷都是短参数(标题/分支/PR 编号),
376
+ // 正常远小于 1KB。裸 `body += chunk` 无上限意味着一个超大 POST 能把
377
+ // 整个请求体堆进进程内存——本地服务也不该留这种自伤通道。
378
+ const MAX_ACTION_BODY_BYTES = 256 * 1024;
310
379
  let body = '';
311
- req.on('data', (chunk) => { body += chunk; });
380
+ let bodyBytes = 0;
381
+ let bodyTooLarge = false;
382
+ req.on('data', (chunk) => {
383
+ if (bodyTooLarge)
384
+ return;
385
+ bodyBytes += chunk.length;
386
+ if (bodyBytes > MAX_ACTION_BODY_BYTES) {
387
+ bodyTooLarge = true;
388
+ body = '';
389
+ // 报 KB 而非字节数:262144 这种数字对人没有意义
390
+ sendJson(res, 413, { ok: false, error: `请求体超过 ${MAX_ACTION_BODY_BYTES / 1024}KB 上限` });
391
+ req.destroy();
392
+ return;
393
+ }
394
+ body += chunk;
395
+ });
312
396
  req.on('end', async () => {
397
+ if (bodyTooLarge)
398
+ return;
313
399
  try {
314
400
  const payload = body ? JSON.parse(body) : {};
315
401
  const cwd = payload.cwd || undefined;
@@ -337,7 +423,52 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
337
423
  });
338
424
  },
339
425
  });
340
- // 5. 本地 Git 元数据定向监听(专供右侧栏开启「本地 Git 改动实时感知」时长轮询)
426
+ // 5. PR 在线详情与代码差异比对(在线代码审阅专用,单次往返聚合元数据、评论流与按文件切片的 Unified Diff)
427
+ bindRoute({
428
+ kind: 'exact',
429
+ path: '/api/github/pr-detail',
430
+ handler: async (req, res) => {
431
+ if (req.method !== 'GET') {
432
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
433
+ return;
434
+ }
435
+ try {
436
+ const u = new URL(req.url || '', 'http://127.0.0.1');
437
+ const number = Number(u.searchParams.get('number') || u.searchParams.get('prNumber'));
438
+ const cwd = u.searchParams.get('cwd') || undefined;
439
+ const repo = u.searchParams.get('repo') || undefined;
440
+ const r = await executor.getPrDetail(cwd, number, repo);
441
+ sendJson(res, r.ok ? 200 : 400, r);
442
+ }
443
+ catch (err) {
444
+ sendJson(res, 500, { ok: false, error: err?.message || '读取 PR 详情异常' });
445
+ }
446
+ },
447
+ });
448
+ // 6. Issue 在线详情(正文 / 评论时间线 / 标签 / 里程碑 / 关联 PR)
449
+ // 与 pr-detail 同构:GET 读语义、单次往返、参数白名单校验全部下沉到 executor。
450
+ bindRoute({
451
+ kind: 'exact',
452
+ path: '/api/github/issue-detail',
453
+ handler: async (req, res) => {
454
+ if (req.method !== 'GET') {
455
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
456
+ return;
457
+ }
458
+ try {
459
+ const u = new URL(req.url || '', 'http://127.0.0.1');
460
+ const number = Number(u.searchParams.get('number') || u.searchParams.get('issueNumber'));
461
+ const cwd = u.searchParams.get('cwd') || undefined;
462
+ const repo = u.searchParams.get('repo') || undefined;
463
+ const r = await executor.getIssueDetail(cwd, number, repo);
464
+ sendJson(res, r.ok ? 200 : 400, r);
465
+ }
466
+ catch (err) {
467
+ sendJson(res, 500, { ok: false, error: err?.message || '读取 Issue 详情异常' });
468
+ }
469
+ },
470
+ });
471
+ // 7. 本地 Git 元数据定向监听(专供右侧栏开启「本地 Git 改动实时感知」时长轮询)
341
472
  // 严格安全纪律:只针对 .git/index 与 .git/HEAD 实施定向非递归监听,严禁监听源码目录防句柄耗尽;
342
473
  // 800ms 防抖合并;连接断开时自动关闭 watcher 释放系统资源(委托给底层 git-watcher 服务)。
343
474
  bindRoute({
@@ -374,7 +505,7 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
374
505
  }
375
506
  },
376
507
  });
377
- // 6. 纯本地 Git 状态毫秒级轻量查询(专供工作区改动时的局部定向刷新)
508
+ // 8. 纯本地 Git 状态毫秒级轻量查询(专供工作区改动时的局部定向刷新)
378
509
  // 严格安全与性能纪律:
379
510
  // 1. 动静解耦:仅执行本地 getLocalGitStatus、getGitCommitLog 与 getGitBranches;
380
511
  // 2. 100% 零网络调用、零远端 GitHub API 请求、零 Token 额度消耗,耗时仅 5~10ms;
@@ -424,7 +555,7 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
424
555
  }
425
556
  },
426
557
  });
427
- // 7. 单个未提交文件的 Git Diff 差异查询(供右侧栏 FileDiffModal 调用)
558
+ // 9. 单个未提交文件的 Git Diff 差异查询(供右侧栏 FileDiffModal 调用)
428
559
  bindRoute({
429
560
  kind: 'exact',
430
561
  path: '/api/github/file-diff',
@@ -458,7 +589,7 @@ export function registerActionRoutes(deps, executor, cacheMap, globalCache) {
458
589
  }
459
590
  },
460
591
  });
461
- // 8. 本地未提交文件列表轻量流式分页查询(供右侧栏滑动加载调用)
592
+ // 10. 本地未提交文件列表轻量流式分页查询(供右侧栏滑动加载调用)
462
593
  bindRoute({
463
594
  kind: 'exact',
464
595
  path: '/api/github/uncommitted-files',