@joekytc/dsh-swarm 0.3.6 → 0.3.8

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.
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  dsh-swarm 是 DSH 的一个插件:把一个需求变成一条严格、证据可核验的交付流水线。
10
10
  编排者(V)把已批准的规格拆成严格有序的相位链(`p → (pt?) → w2 → d → dt → w3 → summary`);
11
11
  六个单一职责的角色(V / P / W / D / PT / DT)以隔离、受权限约束的工具面各执行一个相位;
12
- 每份交接都经机器校验;故障通过幂等重试与人工把关的评审恢复;实时 Workflow 看板标签页通过
12
+ 每份交接都依据证据契约经机器校验;故障通过幂等重试与人工把关的评审恢复;实时 Workflow 看板标签页通过
13
13
  SSE 把全部状态流式同步到浏览器。设计灵感源自 [Hermes Agent kanban](https://github.com/NousResearch/hermes-agent)。
14
14
 
15
15
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.8-blue)
@@ -23,10 +23,10 @@ SSE 把全部状态流式同步到浏览器。设计灵感源自 [Hermes Agent k
23
23
  蜂群模式让主会话成为一个「领队」:**你说需求,它负责澄清、规划、确认、派工、跟进**。全程自然语言,不用记任何命令。
24
24
 
25
25
  - **免记命令** —— 直接说需求即可,不需要 `/plan:`、`/openspec:` 前缀。
26
- - **意图自动识别** —— 开发需求 → 进入澄清规划并建链;沉淀经验/复盘 → 记入记忆库;发通知到群 → 投递企微;问答/闲聊 → 直接回答。
27
- - **自由投递** —— `/sms <意图>`(如「把当前进度发到群」):先实查看板取事实基准,再按意图生成正文投递;`-s` 或自然语言「私聊」→ 发送私聊。群聊/私聊目标各自仅且只有一个(0 目标或多个目标均报错,到 dsh-im 设置清理或配置 `imDelivery.dmTargetId`)。
28
- - **确认闸防误建** —— 规划清单落库后,必须等你明确回复「确认 / 开干 / 开跑 / 开始 / go」等肯定语义才会建链;模糊、岔开话题、只提修改意见 = 未确认。
29
- - **领队只读** —— 主会话不能写/改仓库源码,也不能执行 git 变更(push/commit/checkout 等);写代码由流水线中的执行者(D)在隔离工作区完成,这是设计使然。
26
+ - **意图自动识别** —— 开发需求 → 进入澄清规划并建链;沉淀经验/复盘 → 记入记忆库;发通知到群 → 投递企微;问答/闲聊 → 直接回答。意图由模型自判,非代码级分类器;误判由下方确认闸拦住。
27
+ - **自由投递** —— `/sms <意图>`(如「把当前进度发到群」):先实查看板取事实基准,再按意图生成正文投递;`-s` 或自然语言「私聊」→ 发送私聊。`/sms` 空参=重发最近完成链的完成汇报,`/sms blocked [chainId]`=阻塞通知,这两类正文由系统按看板事实渲染,领队不复述、不改写。群聊/私聊目标自动解析为唯一已保存目标(0 个或多个报错;到 dsh-im 设置清理,或显式指定 `imDelivery.targetId` / `imDelivery.dmTargetId`——显式指定不再校验数量,但 `imDelivery.botId` 留空时该目标仍须归属自动发现的机器人,否则报错)。
28
+ - **确认闸防误建** —— 规划清单落库后,领队被要求仅在你明确回复「确认 / 开干 / 开跑 / 开始 / go」等肯定语义后才建链;判定由模型完成,非代码级校验。模糊、岔开话题、只提修改意见 = 未确认。
29
+ - **领队只读** —— 主会话不能写/改仓库源码,也不能执行 git 变更(push/commit/reset 等);裸 `git checkout`/`git switch` 已存在分支放行。写代码由流水线中的执行者(D)在隔离工作区完成,这是设计使然。
30
30
  - **进度实查** —— 任何时刻问「进度怎么样」,领队都以看板实查结果播报,绝不虚构。
31
31
 
32
32
  ### 为什么这样设计
@@ -37,7 +37,7 @@ SSE 把全部状态流式同步到浏览器。设计灵感源自 [Hermes Agent k
37
37
  - **不可验证的交接** —— agent 说「做完了」却拿不出可复现的证据,下游在流沙上继续盖楼。
38
38
  - **静默死锁** —— agent 停住不吭声,管线挂起;坏代码没经评审就被合入。
39
39
 
40
- dsh-swarm 用一份「契约」对症下药:每个角色只有一项机器强制的职责;每次交接必须携带
40
+ dsh-swarm 用一份「契约」对症下药:每个角色只有一项职责,由权限矩阵、工具面与任务体指令共同约束;每次交接必须携带
41
41
  结构化证据,缺了卡就过不去;每次停滞或评审失败都落入可见、可恢复的状态——而最终的
42
42
  信任锚是你(人类)。技术底座是正确性优先的:确定性状态机、只追加事件日志、幂等调度器,
43
43
  并有一套红队测试重放事件日志、拒绝任何非法流转(机制细节见[进阶](#进阶--开发者))。
@@ -47,7 +47,7 @@ dsh-swarm 用一份「契约」对症下药:每个角色只有一项机器强
47
47
  | 模式 | 怎么用 | 说明 |
48
48
  |---|---|---|
49
49
  | **蜂群模式**(推荐) | 自然语言直接说需求 | 免记命令,意图自动识别,确认即跑 |
50
- | **命令模式**(兼容) | `/plan: <需求>` → 澄清 → `/openspec: 确认执行` | 兼容保留,功能等价;未来可能移除,新用户请用蜂群模式 |
50
+ | **命令模式**(兼容) | `/plan: <需求>` → 澄清 → `/openspec: confirm` | 兼容保留,功能等价;未来可能移除,新用户请用蜂群模式 |
51
51
 
52
52
  ---
53
53
 
@@ -57,13 +57,21 @@ dsh-swarm 用一份「契约」对症下药:每个角色只有一项机器强
57
57
 
58
58
  前置:可用的 DSH 运行时(`@deepseek-ai/*`)、Node.js ≥ 22.19 与 npm。可选:wiki-vault HTTP 服务(知识库功能,见[配置](#配置))。
59
59
 
60
+ 从 npm 安装(发布包内已带构建产物 `lib/`):
61
+
62
+ ```bash
63
+ dsh plugin --profile web add @joekytc/dsh-swarm
64
+ ```
65
+
66
+ 从源码检出安装(先重建,保证 `lib/` 与源码一致):
67
+
60
68
  ```bash
61
69
  npm install
62
70
  npm run build # tsc -p tsconfig.build.json + client bundle (lib/client.js)
63
- dsh plugin --profile web add @joekytc/dsh-swarm
71
+ dsh plugin --profile web add .
64
72
  ```
65
73
 
66
- > 从 GitHub 源码安装:`dsh plugin --profile web add github:joekytc/dsh-swarm`
74
+ > 从 GitHub 源码安装:`dsh plugin --profile web add github:joekytc/dsh-swarm`——仓库已跟踪构建产物 `lib/`。
67
75
 
68
76
  ### 2. 切换主会话预设
69
77
 
@@ -93,22 +101,22 @@ dsh plugin --profile web add @joekytc/dsh-swarm
93
101
  ```
94
102
 
95
103
  - **看板**:会话中心第三个标签页(对话 → 轨迹 → 看板),点卡片查看 概览 / 轨迹 / 交接 / 规格 / 评论。
96
- - **完成**:链路完成时系统审计工作区,并(对 D 链)把特性分支自动合并到规格声明的目标分支;若触发审计警告,需先在 GUI 确认归属。
104
+ - **完成**:链路完成时系统审计工作区,并(对 D 链)把特性分支自动合并到规格声明的目标分支;触发审计警告会阻塞最终汇报、直到你在 GUI 确认归属(不阻塞合入)。
97
105
  - **查进度**:直接问「进度怎么样」,领队实查看板播报;阻塞会如实转述原因。
98
106
 
99
107
  ---
100
108
 
101
109
  ## 它替你做了什么
102
110
 
103
- 六个角色,各管一件事,机器强制边界、绝不越权:
111
+ 六个角色,各管一件事——边界由权限矩阵、裁剪后的工具面与任务体指令共同约束,绝不越权:
104
112
 
105
113
  | 角色 | 一句话职责 | 绝不做什么 |
106
114
  |---|---|---|
107
- | **V** 编排者 | 逐相位建卡、驱动流水线、停滞时给指引 | 不执行任务 |
115
+ | **V** 编排者 | 逐相位建卡、驱动流水线、停滞时给指引 | 绝不执行 |
108
116
  | **P** 规划者 | 读规格 + 仓库事实,写实施计划 | 不写代码 |
109
117
  | **PT** 计划评审 | 只读评审 P 的计划(按需出现) | 不改任何东西 |
110
118
  | **W** 知识官 | 规划/完成阶段同步知识库 | 不碰代码/git |
111
- | **D** 执行者 | 唯一写代码的角色:实现 → 验证 → 提交 → 推特性分支 | 不直接合入目标分支 |
119
+ | **D** 执行者 | 唯一写代码的角色:实现 → 验证 → 提交 → 推特性分支 | 绝不自行合入目标分支 |
112
120
  | **DT** 实现评审 | 实证验证 D 的交付(测试/构建/类型/diff) | 对仓库只读 |
113
121
 
114
122
  流水线(链路内严格串行,链路间并行):
@@ -131,20 +139,40 @@ p ──> (pt?) ──> w2 ──> d ──> dt ──> w3 ──> summary
131
139
  |---|---|---|
132
140
  | `storageDir` | `$DSH_HOME/storages/kanban` | 事件日志(`events.jsonl`)、编排状态、每任务工作区、`dispatcher.log`。取值须用不加引号的 `!!js dshHomePath("storages/kanban")` 写法,加引号会退化成字面量字符串 |
133
141
  | `wikiVault.baseUrl` | `''`(空) | 知识库读写用的 wiki-vault HTTP 服务——知识库功能必需,填你自己的服务地址 |
142
+ | `wikiVault.pagePrefix` | `projects/` | 生成知识库页面的命名空间前缀 |
134
143
  | `roles.models.<role>` | `{}` | 每角色模型:`{ provider, model, reasoningEffort?, fallbacks?[] }` |
135
144
  | `roles.models.<role>.reasoningEffort` | `high` | 所有角色默认推理强度 |
136
145
  | `roles.models.<role>.fallbacks` | `[]` | 静默回退候选(经 `[model-fallback]` 评论审计) |
137
146
  | `dispatcher.staleTimeoutSeconds` | `14400` | 心跳超时;无心跳的 running 任务被回收 |
138
147
  | `dispatcher.maxRetries` | `3` | 失败重试上限,超出进入熔断 → `blocked(gave_up)` |
139
148
  | `dispatcher.heartbeatIntervalSeconds` | `300` | 看门狗心跳周期 |
140
- | `dispatcher.maxProtocolViolations` | `2` | 协议违规护栏:连续违规超过该次数后,下一次即终局(`gave_up`) |
149
+ | `dispatcher.maxProtocolViolations` | `2` | 协议违规护栏:连续违规达到该次数后,下一次即终局(`gave_up`) |
141
150
  | `dispatcher.maxReworksPerRole` | `{ pt: 3, dt: 3 }` | 评审返工轮数上限,超出进入 `review/gave-up` + `[review-final]` |
142
151
  | `prefixRoutes.plan` | `/plan:` | 命令模式阶段 0 规划前缀 |
143
152
  | `prefixRoutes.openspec` | `/openspec:` | 命令模式批准并执行前缀 |
144
- | `ui.enabled` | `true` | 启用看板 Web 标签页 |
145
- | `ui.contentMinWidth` | `715` | 看板内容最小宽度(px) |
146
- | `ui.contentMaxWidth` | `780` | 看板内容最大宽度(px) |
153
+ | `prefixRoutes.learning` | `/learning` | 经验 / 复盘沉淀前缀 |
154
+ | `prefixRoutes.send` | `/sms` | 自由投递前缀(`-s` 发私聊) |
155
+ | `memory.enabled` | `true` | 记忆召回索引;`false` 时 `planning_memory_recall` 返回 disabled 提示 |
156
+ | `memory.maxIndexEntries` | `8` | 记忆召回条数上限(1–20) |
157
+ | `ui.enabled` | `true` | 声明的开关;尚未被消费——标签页恒定注册 |
158
+ | `ui.contentMinWidth` | `715` | 声明的宽度下界(px);客户端尚未消费——标签页宽度沿用宿主会话宽度 |
159
+ | `ui.contentMaxWidth` | `780` | 声明的宽度上界(px);客户端尚未消费 |
147
160
  | `ui.sseHeartbeatSeconds` | `20` | SSE 心跳间隔 |
161
+ | `gates.enabled` | `true` | TDD 实测闸开关;`false` → 静默跳过(不发事件) |
162
+ | `gates.timeoutMs` | `600000` | 单条闸命令超时(ms),到点 SIGKILL |
163
+ | `gates.forbidden` | `['rm -rf /', 'git push']` | 命令黑名单子串(纵深防御) |
164
+ | `evidenceReplay.enabled` | `false` | L3 重放模型写的命令——非沙箱,见[评审问题取证](#评审问题取证pr2默认关) |
165
+ | `evidenceReplay.timeoutMs` | `600000` | 单条重放命令超时(ms) |
166
+ | `evidenceReplay.allowPrefixes` | `['npx --no-install vitest', 'npm test', 'npm run build', 'npm run typecheck', 'tsc', 'eslint']` | 已知工具前缀白名单(词边界匹配) |
167
+ | `imDelivery.enabled` | `false` | 经 dsh-im 投递企微(W3 收尾 / 链阻塞 / 评审超限) |
168
+ | `imDelivery.botId` | `''` | 留空 = 自动发现唯一 wecom bot |
169
+ | `imDelivery.targetId` | `''` | 留空 = 自动发现唯一已保存群目标 |
170
+ | `imDelivery.dmTargetId` | `''` | `/sms -s` 私聊目标 |
171
+ | `imDelivery.fallbackBotId` | `''` | 预设匹配未命中时的落点机器人 |
172
+ | `reviewEngine.mode` | `delegate` | `delegate` 或 `managed`——见[评审引擎(ocr)](#评审引擎ocr) |
173
+ | `reviewEngine.managed.provider` | `''` | 托管模式使用的模型链 provider id |
174
+ | `reviewEngine.managed.model` | `''` | 托管模式使用的模型链 model id |
175
+ | `wikiWritePresets` | `['swarm', 'kanban-w', 'ptc']` | 放行 `wiki_write` 的 preset 白名单(页面路径仍受命名空间白名单约束) |
148
176
 
149
177
  ---
150
178
 
@@ -168,13 +196,15 @@ p ──> (pt?) ──> w2 ──> d ──> dt ──> w3 ──> summary
168
196
  1. 在 dsh Web 顶部把会话切换为「交付评审官(DT)」,直接对话;
169
197
  2. 说清评审对象:本地目录 / 分支 range(from…to)/ 单个 commit / 工作区未提交 diff / 公开仓库 URL(自动 clone 到临时目录,评完即弃);
170
198
  3. 报告先完整输出到对话;
171
- 4. 你确认后再写入知识库 `projects/<仓库>/reviews/<主题>-<日期>/`。全程只读,不改被评审代码。
199
+ 4. 你确认后再写入知识库 `projects/<仓库>/reviews/<主题>-<日期>/`——仅远程知识库模式;`wikiVault.baseUrl` 留空(本地模式)时报告不落 wiki 页。
200
+
201
+ 只读口径:bash/run_code 写入与 reviews 命名空间之外的 wiki 写入被护栏拦截;独立 DT 会话的 fs `write`/`edit` 未被拦截。
172
202
 
173
203
  ### 配置要点
174
204
 
175
205
  - 模式、提供方与模型都在「评审引擎(ocr)」卡选择,提供方/模型下拉与「模型链」同一目录;
176
206
  - 选好后点「应用到 ocr」,系统自动把接入点写入 ocr 自定义配置(`dsh-managed`);API key 由 dsh 模型配置解析后写入 ocr,面板不展示明文;解析失败会降级并指引你在终端手动执行 `ocr config provider`;
177
- - 托管未就绪时评审静默按委托模式兜底,不阻断。
207
+ - 托管未就绪时 ocr 工具拒绝托管调用并返回委托指引,由评审者改走委托模式,不阻断。
178
208
 
179
209
  官方文档:[安装指南](https://open-codereview.ai/docs/installation) · [模型配置](https://open-codereview.ai/docs/configuration) · [委托模式](https://open-codereview.ai/docs/delegate)
180
210
 
@@ -183,9 +213,10 @@ p ──> (pt?) ──> w2 ──> d ──> dt ──> w3 ──> summary
183
213
  ## 信任与护栏(使用者视角)
184
214
 
185
215
  - **领队只读硬闸** —— 蜂群模式主会话写/改源码与 git 变更被系统硬闸拦截;被拦时向领队说明即可,执行由 D 角色完成。
186
- - **确认闸** —— 未经你明确确认,不会建链。
216
+ - **确认闸** —— 领队只在收到你明确肯定回复后才建链(由领队指令约束,非代码级校验)。
187
217
  - **TDD 硬闸** —— 实现必须带测试(或说明跳过原因);评审会机器核验「测试真的跑过、且先写」。
188
- - **人工信任锚** —— 规格审批、解除阻塞、审计确认、整链删除仅人类可做;主会话与角色 agent 都不能建链或批准规格。
218
+ - **人工信任锚** —— 规格审批、解除阻塞、审计确认、整链删除仅人类可做;角色 agent 不能批准规格,链只在你的确认路由调用下创建(主会话以 `human` 身份路由)。
219
+ - **护栏是约束,不是沙箱** —— PT/DT 写保护依赖路径/命令正则(评审者没有 git 凭据),且评审证据是存在性检查:字段必须存在且格式合法;真去重放命令证明「确实跑过」只在打开 `evidenceReplay.enabled` 时才发生。
189
220
  - 详细机制(权限矩阵、交付契约、评审链、返工、故障恢复)见[进阶 / 开发者](#进阶--开发者)。
190
221
 
191
222
  ---
@@ -196,16 +227,16 @@ p ──> (pt?) ──> w2 ──> d ──> dt ──> w3 ──> summary
196
227
 
197
228
  ### 角色与执行管线(详表)
198
229
 
199
- 六个角色由调度器作为一次性 agent 会话派发(确定性会话 id `kbn-<taskId>`,重试/返工时经 `resumeSessionId` 恢复)。每个角色 agent 会话绑定到恰好一个任务(`boundTaskId`),并获得裁剪后的工具面。V 是例外:链级编排会话(`kbn-v-<chainId>`),无 `boundTaskId`。
230
+ 六个角色由调度器作为一次性 agent 会话派发(确定性会话 id `kbn-<taskId>`;重试在同一会话上恢复——`resumeSessionId` 在当前实现中恒为 null;返工则另起 `kbn-<reworkId>` 新会话)。每个角色 agent 会话绑定到恰好一个任务(`boundTaskId`),并获得裁剪后的工具面。V 是例外:链级编排会话(`kbn-v-<chainId>`),无 `boundTaskId`。
200
231
 
201
232
  | 角色 | 别名 | 职责 | 工具面(要点) |
202
233
  |---|---|---|---|
203
234
  | **V** | 编排者 | 驱动相位机,逐相位建卡,停滞时发布 `[blocked-review]` 指引。绝不执行。 | `kanban_create` + 任务工具 + 规格查看 |
204
235
  | **P** | 规划者 | 读取规格 + 仓库事实(含只读自查),编写 OpenSpec 实施计划,用 `pt_decision.needed` 决定是否需要 PT。绝不执行。 | 任务工具 + 规格查看,只读(仅写 `openspec/changes/`) |
205
236
  | **PT** | 计划评审者 | 对 P 的计划做只读评审(需求对齐、完整性、逻辑)。输出裁决 + 问题清单。 | 任务工具 + 规格查看,**只读 ToolGuard** |
206
- | **W** | 知识官 | W2/W3 知识库同步(`w:kb`)。绝不碰代码/git。 | 任务工具 + 远程 `wiki_search/read/write` / 本地 `skill`→llm-wiki + 只读规格查看 |
237
+ | **W** | 知识官 | W2/W3 知识库同步(`w:kb`)。绝不碰代码/git。 | 任务工具 + 远程 `wiki_search/read/write` / 本地 `skill`→llm-wiki + `prefetch_file`/`prefetch_external`/`prefetch_kb` + 只读规格查看 |
207
238
  | **D** | 执行者 | *唯一*写代码的角色:worktree → 实现 → 验证 → `[AI-GEN]` 提交 → 推送特性分支(合入规格声明的目标分支由 system 在 DT 通过后执行)。 | 任务工具 + wiki 只读 + bash/fs/run_code(完整开发面)+ subagent(spawn/fork/list-agents)+ goal |
208
- | **DT** | 实现评审者 | 实证验证 D 的工作(test/build/typecheck/diff/git + open-code-review),把评审页写入知识库。对仓库只读。 | 任务工具 + wiki 读写(评审命名空间)+ bash/fs/run_code,**只读 ToolGuard** |
239
+ | **DT** | 实现评审者 | 实证验证 D 的工作(test/build/typecheck/diff/git + open-code-review),把评审页写入知识库。对仓库只读。 | 任务工具 + wiki 读写(评审命名空间)+ `ocr_review` + bash/fs/run_code,**只读 ToolGuard** |
209
240
 
210
241
  ### 护栏详解
211
242
 
@@ -235,37 +266,56 @@ p ──> (pt?) ──> w2 ──> d ──> dt ──> w3 ──> summary
235
266
  | prefetch | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
236
267
  | audit-confirm | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
237
268
  | create-rework-task | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
269
+ | reopen-chain | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
270
+ | waive-review | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
238
271
 
239
272
  关键保证(两点):
240
273
 
241
- - **主会话不能执行。** 它只拿到 `kanban_show`/`kanban_list`/`kanban_comment` +
242
- `spec_card_view` + `kanban_route` —— 绝无 `kanban_create`/`kanban_complete`/
243
- `kanban_block`。建链/建规格只经蜂群模式意图或 `/plan:`+`/openspec:`;GUI 只观察与变更任务状态,
244
- 从不建链/建任务——"谁决定运行什么"保持显式、可审计。
274
+ - **主会话不能执行。** 它的看板工具面是只读子集——`kanban_show`/`kanban_chain`/`kanban_list`/
275
+ `kanban_comment`,外加人工恢复双工具 `kanban_reopen_chain`/`kanban_waive_review`;再加
276
+ `spec_card_view`、`kanban_route`、`planning_*` 四工具与 `sms_send`;绝无
277
+ `kanban_create`/`kanban_complete`/`kanban_block`。建链/建规格只经蜂群模式意图或
278
+ `/plan:`+`/openspec:`;GUI 只观察与变更任务状态,从不建链/建任务——"谁决定运行什么"保持显式、可审计。
245
279
  - **会话绑定阻止跨任务越权**(绑定到任务 A 的 W agent,即使任务 B 同为 W 任务,也
246
280
  不能 complete/block 任务 B);DT 的写入被矩阵之上的 ToolGuard 限定在
247
- `projects/<repoSlug>/<chain>/review/` 命名空间;且任何角色 agent 都不能批准规格、解除阻塞或
248
- 确认审计——这些是人类信任锚;`system` 只做机械性记账。
281
+ `projects/<repoSlug>/<chain>/review/` 命名空间;且任何角色 agent 都不能批准规格、解除阻塞、
282
+ 确认审计、豁免评审或恢复链——这些是人类信任锚;`system` 只做机械性记账。
249
283
 
250
284
  #### 交付契约(上游欠下游)
251
285
 
252
286
  每个相位的交接必须携带下游真正会读到的键(`src/domain/delivery-contract.ts`)。
253
- 缺键会立即阻塞当前角色的卡(且编排者不会在阻塞的父任务上建下游卡):
287
+ 缺交付键会立即阻塞 W/P 卡且无 human 豁免;D/PT/DT 证据闸则是拒绝 `complete` 并抛错
288
+ (卡留 running),human 可豁免。编排者绝不在阻塞的父任务上建下游卡:
254
289
 
255
290
  | 卡 | 必需交接键 |
256
291
  |---|---|
257
- | W2 / W3(`w:kb`) | `kb_url` + `page_path` |
292
+ | W2 / W3(`w:kb`) | `kb_url` + `page_path`——仅非空不足以通过:配置了 `wikiVault.baseUrl` 时 `kb_url` 必须带该前缀(本地模式必须为空串),`page_path` 必须落在白名单命名空间(本地为 `wiki/**`) |
258
293
  | P(`p:openspec`) | `artifacts_path` + `pt_decision`(`needed` 布尔必填;`needed: true` 时 `reason` 必填) |
259
- | D(`d:execute`) | `changed_files` +(`commit_hash` 或 `push`)——`hasDeliveryEvidence`;`branch`(特性分支)是合并闸门的期望输入,非硬性完成阻塞项;`tdd`(`test_files` 或 `skipped.reason`,二选一) |
294
+ | D(`d:execute`) | `changed_files` +(`commit_hash` 或 `push`)——`hasDeliveryEvidence`;`branch`(特性分支)不在交付键内,但实测闸真跑测试时会核对分支,缺失或不一致即打回该卡;`tdd`(`test_files` 或 `skipped.reason`,二选一) |
260
295
  | PT / DT | `review_evidence`(schema 合法)——`validateReviewEvidence` |
261
296
 
262
297
  #### TDD 硬闸(证据门槛)
263
298
 
264
299
  D 只有带 `tdd` 才能完成——`test_files`(含 `test_first`)或 `skipped.reason`
265
300
  (二选一,见 `delivery-evidence.ts`)。DT 的 `review_evidence` 必须携带 `tdd`;
266
- `pass` 裁决下 runner 必须是 `vitest`(`test.runner`)且 `test_first === true`
267
- (见 `review-evidence.ts`)。这让"测试确实跑过、且先写测试"成为机器校验的属性,
268
- 而非一句声明。
301
+ 只要声明了 `tdd.test_files`,runner 就必须是 `vitest`(`test.runner`),而在 `pass`
302
+ 裁决下还须 `test_first === true`(见 `review-evidence.ts`)。这让"测试确实跑过、且先写测试"
303
+ 成为机器校验的属性,而非一句声明。
304
+
305
+ #### 闸门跳过警报与声明互证
306
+
307
+ 声明与 diff 不一致时实测闸不再无声放行(仍有三类静默跳过、不发事件:
308
+ `gates.enabled=false`、交接缺 `worktree_dir`、非 `d:execute` 卡):
309
+
310
+ - **`task/gate-skipped` 事件**——声明 `tdd.skipped` 在 diff 为纯文档/配置时放行,diff 取不到时保守放行(alarm-skip);两者都留痕审计。否则闸门**打回**(同会话修复重交)。
311
+ - **声明↔实际互证**——声明 `test_files` 但 diff 无测试文件变更(拿旧测试交差)、`test_files` 为空、路径违规、分支不一致,一律**打回**而非静默跳过。
312
+ - 累计打回 3 次后任务 **blocked 转人工**(`gave_up: gate bounced 3 times`)。
313
+ - 闸门实测输出原文落盘至 `<storageDir>/gate-logs/<taskId>.log`(路径写入 gate 事件 detail)。
314
+ - `review_evidence.lint` 必须为结构化对象(与 `build`/`typecheck` 同口径);`null`/标量拒收。
315
+
316
+ #### 评审问题取证(PR2,默认关)
317
+
318
+ 评审者报告的问题(仅 DT 卡触发,PT 问题不做取证核验)可附 `evidence = { file, command, exit }`(逐字输出存档,首行 `[exit code: N]`)。核验三级:缺证 → 标记(`not-provided`;critical/high → `could-not-replay` + 转人工);存档纸面核对(零执行)→ `matches`;对不上时**重放**再跑一次命令——**仅在 `evidenceReplay.enabled` 开启时**,且仅放行已知工具前缀(词边界匹配;`npm run` 仅固定脚本名)。重放执行的是模型写的命令,**不是沙箱**;不接受该风险就保持开关关闭。结果汇总为 `review/evidence-check` 事件;`differs` 绝不自动判评审失败——转人工核对。
269
319
 
270
320
  #### 阶段 0 规划清单
271
321
 
@@ -276,15 +326,16 @@ D 只有带 `tdd` 才能完成——`test_files`(含 `test_first`)或 `skipp
276
326
 
277
327
  #### 评审质量链
278
328
 
279
- - **P** 完成后,仅当 P 的交接交付 `pt_decision.needed = true` 时才创建 **PT** 卡;
280
- 编排者从不覆盖该判定(V 只负责建卡)。
329
+ - **P** 完成后,仅当 P 的交接交付 `pt_decision.needed = false` 时才跳过 **PT**;
330
+ `true` 或判定缺失都会建 PT 卡(fail-safe)——编排者从不覆盖该判定(V 只负责建卡)。
281
331
  - **D** 完成后**总是**创建 **DT** 卡。
282
332
  - **PT/DT 只读**:ToolGuard 机械性拒绝写仓库源码、git 变更,以及(对 DT)评审命名空间
283
333
  之外的 wiki 写入。
284
334
  - **DT 评审引擎**:`open-code-review`(ocr,双模:委托/托管,见[评审引擎(ocr)](#评审引擎ocr));
285
335
  DT 卡启动前自动探活,ocr 未安装即 block `review-tool-unavailable`(原因注明可在 GUI 安装),不消耗重试。
286
336
  - `review_evidence` 必须通过 `validateReviewEvidence`,否则评审卡无法完成:PT 需要
287
- verdict + issues + 计划引用;DT 额外需要 test(通过时退出码 0)、build/typecheck、
337
+ verdict + issues + 计划引用,且 `fail` 裁决还需至少一条未解决的 `critical`/`high`
338
+ (否则应判 `pass`);DT 额外需要 test(通过时退出码 0)、build/typecheck、
288
339
  lint、非空 diff、git、ocr/回退结论,以及 `tdd`。
289
340
 
290
341
  #### 返工(评审失败)
@@ -292,11 +343,11 @@ D 只有带 `tdd` 才能完成——`test_files`(含 `test_first`)或 `skipp
292
343
  评审失败**从不改写** `done` 卡。系统改为记录 `review/failed`,创建**返工任务**
293
344
  (`[返工] ...`)——独立会话(`kbn-<reworkId>`,`resumeSessionId = null`)、
294
345
  `reviewAttempt + 1`,初始为 `todo`(`reviewStatus: 'pending'`);返工卡 body 追加
295
- 「本轮修复清单」节(携带上一轮 issues 全文),然后为返工重新派发全新评审卡。
346
+ `## 本轮修复清单` 节(携带上一轮 issues 全文),然后为返工重新派发全新评审卡。
296
347
  当 `reviewAttempt` 达到 `maxReworksPerRole`(PT 3 / DT 3)时,系统记录
297
- `review/gave-up` 并发布 `[review-final]` 证据链评论,同时(启用 IM 投递时)发出
348
+ `review/gave-up` 并发布 `[review-final]` 证据链评论(该标记亦被下方收敛闸降级复用),同时(启用 IM 投递时)发出
298
349
  `[评审超限待裁决]` 通知,附两条出口:豁免评审(`kanban_waive_review` / GUI「豁免评审」)
299
- 或人工恢复链(`kanban_reopen_chain` / GUI「人工恢复」)。**收敛闸(2026-09-15)**:
350
+ 或人工恢复链(`kanban_reopen_chain` / GUI「人工恢复」)。**收敛闸**:
300
351
  PT 复审轮中旧账清零且无未解决 `critical` 时,fail 自动降级为 pass(新发现转下游
301
352
  非阻塞建议),循环必然收敛。
302
353
 
@@ -314,72 +365,80 @@ PT 复审轮中旧账清零且无未解决 `critical` 时,fail 自动降级为
314
365
  `staleTimeoutSeconds` 内停止心跳的 `running` 任务(心跳本身只是*状态*信号,绝不是
315
366
  业务变更;SSE 心跳从不携带看板状态)。每角色模型候选(主模型 + 回退,默认
316
367
  `reasoningEffort: high`)静默回退(经 `[model-fallback]` 评论审计);*所有*候选都失败
317
- 则 block `model-unavailable` 等待人类。单个挂起的 V 唤醒不会卡死调度器——每次派发
318
- 都被包在超时里。
368
+ 则 block `model-unavailable` 等待人类。单个挂起的 V 唤醒不会卡死调度器——V 唤醒包了
369
+ 60s 超时(角色任务派发仍直接 await 会话 whenIdle)。
319
370
 
320
371
  #### 链路完成:审计闸门 + 合并闸门
321
372
 
322
373
  机械性链路完成规则触发时,两个闸门在 `chain/completed` 钩子中运行:
323
374
 
324
- 1. **完成审计闸门**:`ChainAuditor` 交叉核对链路工作区中是否存在已知任务输出
325
- 之外的产物。发现孤儿写入即发出 `chain/audit-warning`;UI 显示警告横幅并阻塞最终
326
- 汇报,直到人类确认归属(`chain/audit-confirmed`,仅限人类)。
375
+ 1. **完成审计闸门**:`ChainAuditor` 扫描非角色活会话中指向链路工作区的写能力工具
376
+ 调用(`kanban-*` preset 子代理豁免),再以产物归属核对兜底。发现孤儿写入即发出
377
+ `chain/audit-warning`;UI 显示警告横幅并阻塞最终汇报,直到人类确认归属
378
+ (`chain/audit-confirmed`,仅限人类)。
327
379
  2. **合并闸门(DT 通过后的系统合并)**:D 从不合并到目标分支,也不推送它——D 只提交到
328
380
  (可选推送)自己的特性分支,并在交接中携带 `branch`。目标分支是规格中声明的分支
329
381
  (V 写入 D 任务体)。DT 批准且链路完成后,`merge-gate.ts` 以 `system` 身份执行:
330
382
  `git checkout <目标分支> → git merge --no-ff <特性分支> → git push`。结果以幂等评论记录:
331
383
  `[merge-done]`(带 hash)、`[merge-skip]`(合并输入无法解析)、`[merge-failed]`
332
- (checkout/merge/push 失败,例如冲突)。失败绝不抛错——坏合并*不执行*,这是安全方向;
333
- 人类事后可修复。
384
+ (checkout/merge/push 失败,例如冲突)。失败绝不抛错,闸门只记 `[merge-failed]`;
385
+ 注意 push 失败时目标分支本地可能已合入,冲突合入会把工作区留在半合并态——修复由人类事后处理。
334
386
 
335
387
  ### 事件溯源与领域模型
336
388
 
337
389
  每次状态变更都追加到 `<storageDir>/events.jsonl`,每行一个 JSON 事件。`seq` 由存储
338
- 分配(每次追加时从文件尾部重读,并发实例永不冲突)。**轨迹即事件日志本身**;重启回放
339
- 日志即可重建看板。
390
+ 分配(每次追加时重读文件尾行的 `seq` + 1)——只追加写;唯一例外是人类整链删除会物理
391
+ 删掉该链的行并重排剩余 `seq`。**轨迹即事件日志本身**;重启回放日志即可重建看板。
340
392
 
341
393
  ```jsonc
342
394
  // events.jsonl 中的一行
343
395
  { "seq": 12, "chainId": "ch_x_...", "taskId": "t_y_...",
344
396
  "kind": "task/completed",
345
- "payload": { "summary": "...", "metadata": { /* 交接证据 */ } },
397
+ "payload": { "summary": "...", "metadata": { /* 交接证据 */ }, "completedAt": 1760000000000 },
346
398
  "author": "w", "at": 1760000000000 }
347
399
  ```
348
400
 
349
- 事件族:`chain/*`(created, executing, completed, aborted, root-task-set,
350
- audit-warning, audit-confirmed, title-updated)、`spec-card/*`(created, edited,
351
- approved)、`task/*`(created, claimed, heartbeat, commented, completed, blocked,
352
- unblocked, failed, archived, renamed)、`review/*`(passed, failed, gave-up)。
401
+ 实际发射的事件族:`chain/*`(created, executing, completed, blocked, reopened,
402
+ root-task-set, audit-warning, audit-confirmed, title-updated, im-delivery-failed)、
403
+ `spec-card/*`(created, edited, approved)、`task/*`(created, claimed, heartbeat,
404
+ commented, completed, blocked, unblocked, failed, archived, renamed, gate-passed,
405
+ gate-failed, gate-skipped)、`review/*`(passed, failed, gave-up, waived,
406
+ evidence-check)。
353
407
 
354
- 回放是**严格**的:投影把每个事件经过状态机应用,任何非法转换都会抛错——损坏或被篡改的
355
- 日志会响亮失败,而不是静默产出不一致的看板(由 `tests/redteam/anti-escalation.test.ts`
356
- 与 `tests/domain/projection.test.ts` 覆盖)。
408
+ 回放是**严格**的:投影把每个状态迁移事件经过状态机重放,任何非法迁移都会抛错——损坏或
409
+ 被篡改的日志会响亮失败,而不是静默产出不一致的看板(`task/commented` 等非迁移事件为
410
+ 记录型 no-op)。由 `tests/redteam/anti-escalation.test.ts` 与
411
+ `tests/domain/projection.test.ts` 覆盖。
357
412
 
358
413
  服务通过串行队列发布事件(先落盘再发布),订阅方(SSE)按序收到每个事件且恰好一次。
359
414
  UI 与调度器消费的是同一份持久化事件——不存在第二个真相源。
360
415
 
361
416
  ### Web 客户端(Workflow 看板标签页)
362
417
 
363
- 注册为第三个 `conversation.view` 槽位的浏览器半 React 标签页(`id=kanban`、`order=20`,
364
- 位于 对话 与 轨迹 之后)。它**不**注册 shell 级浮层、侧栏或详情抽屉。
418
+ 注册进 `conversation.view` 的浏览器半 React 标签页(`id=kanban`、`order=20`,
419
+ 排在 对话 与 轨迹 之后)。它**不**注册 shell 级浮层、侧栏或详情抽屉。
365
420
 
366
421
  - **数据路径**:初始快照(`GET /kanban/board`)→ SSE 流(`GET /kanban/events?after=<seq>`)
367
422
  → board-store 增量应用事件、按 `seq` 去重,任何缺口都重拉完整快照。**无业务轮询。**
368
- - **布局**:多链路垂直轨道;内容宽度固定 715–780 px,整高;当前链路展开,阻塞链路
369
- 始终显示警告摘要。页内改名/删除用轻量弹窗(无 shell 浮层);无拖拽、无宽度记忆。
423
+ - **布局**:多链路垂直轨道;宽度沿用宿主会话宽度(`--dsh-chat-content-width`,缺失时
424
+ 兜底 780 px),整高;当前链路展开,阻塞链路始终显示警告摘要。页内改名/删除用轻量弹窗
425
+ (无 shell 浮层);无拖拽、无宽度记忆。
370
426
  - **卡片**:紧凑双行卡片 + 按 profile 着色的节点;状态线为 绿实线(完成)/ 蓝实线(当前)/
371
427
  灰虚线(等待)/ 红断点(阻塞)。
372
428
  - **详情抽屉**:五区——概览 / 轨迹 / 交接 / 规格 / 评论;`Esc` 或返回回到列表。
373
429
  - **动作**(`POST /kanban/action`):block / unblock / retry / complete / archive /
374
- comment,外加链路级 `confirm-audit`、`rename`(链或任务)与 `delete`(整链,仅 human,
375
- GUI 二次确认)。人类动作应用带回滚的乐观更新;store 在任何分歧时对权威快照重新对账。
430
+ comment / `waive-review`,外加链路级 `confirm-audit`、`rename`(链或任务)、
431
+ `reopen-chain` 与 `delete`(整链,仅 human,GUI 二次确认)。状态类动作
432
+ (block / unblock / complete / archive / retry)应用带回滚的乐观更新;其余动作等待
433
+ 服务端事件收敛,出现分歧时 store 重拉权威快照。
376
434
  - **构建**:`npm run build:client` 生成 `lib/client.js`,采用 `window.__ModuleLoader__.load()`
377
435
  格式(与 `dsh-client-*` 相同的约定)。把 dsh-swarm 加入 web profile 会自动把它嵌入
378
436
  `__DSH_BOOT__`。
379
437
 
380
438
  ### 架构
381
439
 
382
- 五层结构,领域层**不依赖任何 DSH**,因此可以被完全单测并独立回放。
440
+ 分层结构,领域层不依赖任何 DSH 运行时(仅 `prefetch-manifest.ts` 一处 type-only
441
+ `ObjectJsonSchema` 导入),因此可以被完全单测并独立回放。
383
442
 
384
443
  ```mermaid
385
444
  flowchart TB
@@ -389,7 +448,7 @@ flowchart TB
389
448
  Model["workflow-model: pure view projection"]
390
449
  end
391
450
 
392
- subgraph Domain ["domain/ (pure TS, zero DSH deps)"]
451
+ subgraph Domain ["domain/ (pure TS; no runtime DSH deps)"]
393
452
  ES["event-store (JSONL append-only, monotonic seq)"]
394
453
  SM["state-machine (task/chain/spec transitions)"]
395
454
  PJ["projection (events → BoardState)"]
@@ -419,6 +478,14 @@ flowchart TB
419
478
  WK["wiki-worker (W prefetch worker)"]
420
479
  end
421
480
 
481
+ subgraph Services ["services/"]
482
+ PROVIDER["kanban-provider (gate + evidence wiring)"]
483
+ GATERUN["gate-runner / gate-evidence / evidence-replay"]
484
+ OCRCLI["ocr-cli"]
485
+ IMD["im-delivery"]
486
+ CFG["config-provider"]
487
+ end
488
+
422
489
  subgraph Wiki ["wiki/"]
423
490
  WVC["wiki-vault-client (search/read/write)"]
424
491
  end
@@ -438,6 +505,11 @@ flowchart TB
438
505
  MG --> KS
439
506
  KS --> ES --> PJ --> SM --> PM
440
507
  EC --> KS
508
+ PROVIDER --> KS
509
+ GATERUN --> PROVIDER
510
+ OCRCLI --> TOOLSETS
511
+ IMD --> KS
512
+ CFG --> VORCH
441
513
  ```
442
514
 
443
515
  #### 各层职责
@@ -447,6 +519,9 @@ flowchart TB
447
519
  单一权威的 `KanbanService` 门面。单测充分覆盖。
448
520
  - **集成层**(`src/tools/`、`src/routes/`)—— cordis 工具与路由:角色工具面、主会话
449
521
  工具(`kanban_route` + 只读子集)、`/kanban/*` HTTP/SSE 桥。
522
+ - **服务层**(`src/services/`)—— provider 装配(`kanban-provider` 安装实测闸与取证核验
523
+ 钩子)、闸门执行与证据重放、`ocr-cli`、IM 投递与配置 provider;调度器本体
524
+ (`src/dispatcher/dispatcher.ts`)归入调度层。
450
525
  - **调度层**(`src/dispatcher/`)—— 事件唤醒、相位编排、一次性 agent 运行器(persona
451
526
  preset 挂载、模型候选链、ToolGuard 安装)、看门狗、链路审计器、合并闸门。
452
527
  - **角色层**(`src/roles/`、`personas/`)—— 安装到 `$DSH_HOME/.agent-presets/` 的裁剪
@@ -459,7 +534,7 @@ flowchart TB
459
534
 
460
535
  ```bash
461
536
  npm run typecheck # tsc -p tsconfig.json --noEmit (0 errors)
462
- npm test # npx vitest run (全绿)
537
+ npm test # vitest run(全绿)
463
538
  npm run build # tsc -p tsconfig.build.json + build:client (lib/client.js)
464
539
  ```
465
540
 
@@ -471,34 +546,6 @@ python tests/e2e/gui-check.py --url http://127.0.0.1:3080/
471
546
 
472
547
  > 部署到运行中的 DSH 实例需要插件重载/重启;仅构建不会热重载正在运行的插件。
473
548
 
474
- ### 已实现与已知限制
475
-
476
- #### 已实现(v0.1.0)
477
-
478
- - [x] **蜂群模式**:自然语言意图识别(plan/openspec/learning/send)+ 确认闸 + 主会话只读硬闸
479
- - [x] 事件溯源领域 + 确定性状态机(红队回放)
480
- - [x] 6 角色相位管线 + 裁剪 preset + 会话绑定权限
481
- - [x] 交付契约 + 评审证据闸门 + 返工生命周期
482
- - [x] TDD 硬闸(D `tdd` 交接 + DT `test_first` / `runner=vitest` 核验)
483
- - [x] 协议违规恢复、心跳看门狗、失败熔断
484
- - [x] 链路完成审计闸门 + 人工确认
485
- - [x] DT 通过后合并闸门(D 只推送特性分支)
486
- - [x] 阶段 0 规划清单 + `file-prefetch` 附件
487
- - [x] GUI 链/任务改名 + 整链删除(仅 human)
488
- - [x] 模型候选链:静默回退 + High 推理强度
489
- - [x] 实时 SSE 看板标签页(对话 → 轨迹 → 看板)
490
-
491
- #### 已知限制
492
-
493
- - **蜂群模式意图识别依赖模型自判**:误判有确认闸兜底(未确认不建链),非零误判风险。
494
- - **写保护是字符串启发式,不是硬隔离。** PT/DT ToolGuard 依赖路径/命令正则,评审者
495
- 没有 git 凭据;这是软约束加审计轨迹,而非挂载级沙箱。
496
- - **`open-code-review`(ocr)按机器可选**:未安装时链上评审在 DT 启动前即 block
497
- `review-tool-unavailable` 并给出安装指引(可在 GUI 一键安装),不消耗重试。
498
- - **评审证据是存在性检查,而非回放证明。** 字段必须存在且格式合法;证明测试确实运行尚未实现。
499
- - **配置默认值里只有一个 wiki-vault 主机**——请把 `wikiVault.baseUrl` 指向你的部署。
500
- - **PT 建卡依赖 P 自报的 `pt_decision.needed`**——从仓库信号做系统辅助检测尚未实现。
501
-
502
549
  ---
503
550
 
504
551
  ## 许可证
@@ -26,7 +26,7 @@ export type TimelineStatus = 'neutral' | 'running' | 'success' | 'exception';
26
26
 
27
27
  /** 异常事件全集(红系高亮)。 */
28
28
  const EXCEPTION_KINDS: ReadonlySet<EventKind> = new Set<EventKind>([
29
- 'task/blocked', 'task/failed', 'task/gate-failed', 'review/failed', 'review/gave-up', 'chain/audit-warning', 'chain/aborted',
29
+ 'task/blocked', 'task/failed', 'task/gate-failed', 'task/gate-skipped', 'review/failed', 'review/gave-up', 'chain/audit-warning', 'chain/aborted',
30
30
  ]);
31
31
 
32
32
  /** 四态状态映射:异常集优先,其余按成功/进行中/中性归类。 */
@@ -57,10 +57,12 @@ const STATUS_OF: Record<EventKind, TimelineStatus> = {
57
57
  'task/failed': 'exception',
58
58
  'task/gate-passed': 'success', // P1 实测闸
59
59
  'task/gate-failed': 'exception',
60
+ 'task/gate-skipped': 'exception', // PR1 互证警报:闸本该跑却没跑
60
61
  'task/renamed': 'neutral',
61
62
  'review/passed': 'success',
62
63
  'review/failed': 'exception',
63
64
  'review/gave-up': 'exception',
65
+ 'review/evidence-check': 'neutral', // PR2 证据核验汇总(differs 详情在 summary,事件本体中立)
64
66
  };
65
67
 
66
68
  export function timelineStatusOf(kind: EventKind): TimelineStatus {
@@ -94,11 +96,13 @@ const KIND_LABEL: Record<EventKind, string> = {
94
96
  'task/failed': '任务失败',
95
97
  'task/gate-passed': '实测闸通过', // P1 实测闸
96
98
  'task/gate-failed': '实测闸失败',
99
+ 'task/gate-skipped': '实测闸跳过(声明)',
97
100
  'task/renamed': '任务改名',
98
101
  'review/passed': '评审通过',
99
102
  'review/failed': '评审驳回',
100
103
  'review/gave-up': '评审超限放弃',
101
104
  'review/waived': '评审豁免',
105
+ 'review/evidence-check': '证据核验(重放)',
102
106
  };
103
107
 
104
108
  /** author id → 友好名(复用 ROLE_NAME 语义,system/human 另映射)。 */
@@ -148,6 +152,8 @@ export function eventSummary(e: KanbanEvent): string {
148
152
  case 'task/gate-passed':
149
153
  case 'task/gate-failed': // P1 实测闸:payload.detail(命令串/失败码 + 末条输出尾巴)
150
154
  return truncate(strField(payload, 'detail'));
155
+ case 'task/gate-skipped': // PR1 互证警报:payload.reason
156
+ return truncate(strField(payload, 'reason'));
151
157
  case 'task/commented':
152
158
  return truncate(strField(payload, 'body'));
153
159
  case 'task/renamed':
@@ -169,6 +175,13 @@ export function eventSummary(e: KanbanEvent): string {
169
175
  if (reason) parts.push(reason);
170
176
  return truncate(parts.join(' · '));
171
177
  }
178
+ case 'review/evidence-check': { // PR2 证据核验汇总:四态计数
179
+ const results = Array.isArray(payload['results']) ? (payload['results'] as Array<Record<string, unknown>>) : [];
180
+ const counts = results.reduce<Record<string, number>>((acc, r) => {
181
+ const s = String(r['state'] ?? '?'); acc[s] = (acc[s] ?? 0) + 1; return acc;
182
+ }, {});
183
+ return truncate(Object.entries(counts).map(([k, v]) => `${k}:${v}`).join(' · '));
184
+ }
172
185
  default:
173
186
  return '';
174
187
  }
package/lib/client.js CHANGED
@@ -946,6 +946,7 @@ window.__ModuleLoader__.load({
946
946
  "task/blocked",
947
947
  "task/failed",
948
948
  "task/gate-failed",
949
+ "task/gate-skipped",
949
950
  "review/failed",
950
951
  "review/gave-up",
951
952
  "chain/audit-warning",
@@ -982,10 +983,14 @@ window.__ModuleLoader__.load({
982
983
  "task/gate-passed": "success",
983
984
  // P1 实测闸
984
985
  "task/gate-failed": "exception",
986
+ "task/gate-skipped": "exception",
987
+ // PR1 互证警报:闸本该跑却没跑
985
988
  "task/renamed": "neutral",
986
989
  "review/passed": "success",
987
990
  "review/failed": "exception",
988
- "review/gave-up": "exception"
991
+ "review/gave-up": "exception",
992
+ "review/evidence-check": "neutral"
993
+ // PR2 证据核验汇总(differs 详情在 summary,事件本体中立)
989
994
  };
990
995
  function timelineStatusOf(kind) {
991
996
  return STATUS_OF[kind] ?? "neutral";
@@ -1017,11 +1022,13 @@ window.__ModuleLoader__.load({
1017
1022
  "task/gate-passed": "\u5B9E\u6D4B\u95F8\u901A\u8FC7",
1018
1023
  // P1 实测闸
1019
1024
  "task/gate-failed": "\u5B9E\u6D4B\u95F8\u5931\u8D25",
1025
+ "task/gate-skipped": "\u5B9E\u6D4B\u95F8\u8DF3\u8FC7\uFF08\u58F0\u660E\uFF09",
1020
1026
  "task/renamed": "\u4EFB\u52A1\u6539\u540D",
1021
1027
  "review/passed": "\u8BC4\u5BA1\u901A\u8FC7",
1022
1028
  "review/failed": "\u8BC4\u5BA1\u9A73\u56DE",
1023
1029
  "review/gave-up": "\u8BC4\u5BA1\u8D85\u9650\u653E\u5F03",
1024
- "review/waived": "\u8BC4\u5BA1\u8C41\u514D"
1030
+ "review/waived": "\u8BC4\u5BA1\u8C41\u514D",
1031
+ "review/evidence-check": "\u8BC1\u636E\u6838\u9A8C\uFF08\u91CD\u653E\uFF09"
1025
1032
  };
1026
1033
  var AUTHOR_NAME = {
1027
1034
  v: "orchestrator",
@@ -1065,6 +1072,8 @@ window.__ModuleLoader__.load({
1065
1072
  case "task/gate-passed":
1066
1073
  case "task/gate-failed":
1067
1074
  return truncate(strField(payload, "detail"));
1075
+ case "task/gate-skipped":
1076
+ return truncate(strField(payload, "reason"));
1068
1077
  case "task/commented":
1069
1078
  return truncate(strField(payload, "body"));
1070
1079
  case "task/renamed":
@@ -1086,6 +1095,15 @@ window.__ModuleLoader__.load({
1086
1095
  if (reason) parts.push(reason);
1087
1096
  return truncate(parts.join(" \xB7 "));
1088
1097
  }
1098
+ case "review/evidence-check": {
1099
+ const results = Array.isArray(payload["results"]) ? payload["results"] : [];
1100
+ const counts = results.reduce((acc, r) => {
1101
+ const s = String(r["state"] ?? "?");
1102
+ acc[s] = (acc[s] ?? 0) + 1;
1103
+ return acc;
1104
+ }, {});
1105
+ return truncate(Object.entries(counts).map(([k, v]) => `${k}:${v}`).join(" \xB7 "));
1106
+ }
1089
1107
  default:
1090
1108
  return "";
1091
1109
  }