diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index d5fcd775e7..b1ec449a29 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -77,6 +77,7 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout | `needs-user-decision` | 决定**待做** —— 永不派发、除已裁代裁通道外永不代答;维护者的收件箱 | | `pm:on-hold` | 决定**已做**且答案是「不是现在」—— 不派发也不催;hold 评论带**日期、理由、重启条件** | | `pm:blocked` + 正文行 `Blocked-by: #N` | 等上游 —— 选择期跳过,#N 关闭时由解锁扫描放回 | +| `pm:blocking` | 有 open 下游依赖者(分诊 sweep 自 `Blocked-by:` 索引推导的缓存,⛔ 不手工挂);进选择优先级全序 | | `finding` | 观察类记录 —— 持有态,待发现分诊轮定级,不占队列不进收件箱 | | `target:` | 发版阻塞 —— 每个 backlog 恰好一个生产者,见「发版板」 | | `pm:epic`(父单) | 子树已委托 epic PM(会话与领地在父单正文;`label:pm:epic` 即全量索引)—— 其它 PM 不把其 sub-issue 当候选 | @@ -93,19 +94,17 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout 触发文件清单,车道座位贴设「派发前必查」段,派发卡文件面与清单相交时点名该单、顺手活列为申报过的增项。 - **`Blocked-by:` 行是机器可 grep 的反向索引**,一遍读喂三个职责:上游关单时放回被解锁的、按解 锁扇出排序选择、**在合并后的 ref 上重验每张回队卡的文件面**(⛔ 只做第一件)。**一个标签存在 - ,当且仅当有具名读者**,无读者的标签必烂;已议已拒(维护者2026-08-11):「被依赖」是随上游关单衰 - 变的推导属性,从行现算,⛔ 不落存储标签。 + ,当且仅当有具名读者**;2026-08-11 的「⛔ 不落存储标签」已被维护者 2026-08-13 意见取代(原话 + :「被依赖的卡片是不是应该通过label标注提高优先级」):`pm:blocking` = 分诊 sweep 自该索引推导写 + 入/摘除的**缓存**,⛔ 不手工挂 —— 变的是缓存位置,推导本性与具名读者不变量未变(读者:选择优先级全序、列表页扫描)。 - **状态变更不过夜**:标签挂了评论没跟上、assignee 设了没认领评论、结论只在 chat ——都是半状态 - ,结束会话(含限流悬挂)前补齐成对或回滚半边(机械辅助 - :`node scripts/pm/check-half-states.mjs`,report-only 巡查)。**代执行他人指令的关闭/作废,评 - 论带出处三件(谁的指令、原话、在哪说的)** —— 无出处的关闭与误操作在证据上不可区分,会被兄弟 - 席当误扫重开;同理适用于摘标签、回收认领等不可反推理由的动作。 + ,结束会话(含限流悬挂)前补齐成对或回滚半边(report-only 巡查见机械守卫索引)。**代执行他人指令 + 的关闭/作废,评论带出处三件(谁的指令、原话、在哪说的)** —— 无出处的关闭与误操作在证据上不可区 + 分,会被兄弟席当误扫重开;同理适用于摘标签、回收认领等不可反推理由的动作。 - **写后回读**:每次写正文、评论、标签,发出后读回校验 —— sanitizer 会静默吞内容, label bot 的 整组 PUT 会冲掉刚打的手工标签;「API 返回 200」≠「落地内容正确」。 -**一次性建标签**:`bash scripts/pm/ensure-pm-labels.sh`(幂等)。⛔ 退役的`domain:engine` / -`domain:ui` 不在脚本里也不要加回去 —— 重建退役标签就是把无主车道放回自动补全;两者存量已清零 -但**标签对象仍在**,删除对象是 PM 的一步单独动作,删掉前别当它们不存在。 +**一次性建标签**:`bash scripts/pm/ensure-pm-labels.sh`(幂等;退役车道刻意不在脚本里 ⛔ 不加回,理由与对象清理以脚本头为权威)。 ## 平台读数纪律 @@ -164,9 +163,9 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout 5. **One board, no second tracker。** pm 标签就是状态机;org Project 只是维护者的聚合视图,权 威层坚持 issue 正文 + REST。 -**跨座位转移**:工作跨座位线,PM 永不跨 —— 落到对方队列(目标仓立单带 `pm:queue` +出处行),依赖 -走 `Blocked-by:`;后续杂事由消费侧座位立;凡触 `packages/spec` 一律转`domain:spec` 座位(唯一所 -有者),不论谁需要它。 +**跨座位转移**:工作跨座位线,PM 永不跨(唯一豁免:简单阻塞项直接接手,见「候选与批次」)—— 落到 +对方队列(目标仓立单带 `pm:queue` +出处行),依赖走 `Blocked-by:`;后续杂事由消费侧座位立;凡触 +`packages/spec` 一律转`domain:spec` 座位(唯一所有者),不论谁需要它。 ## Domain lanes(同仓多 PM 并发) @@ -207,10 +206,10 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout **单一生产者。** `domain:*` 只由分诊座位产出;打标签 ≠ 认领;**未打标签的 issue 任何人不得认领 ** —— 那意味着分诊还没走到,不是「可以自己判一下」。 -**跨域例外路径(唯一的越界通道)。** 真拆不动的跨域单 PR 由**分诊座位指定**一个车道PM 认领;认 -领评论**申报完整文件面**;只在这条路径上跑**定向在飞检查**(范围 = 该单文件面触及的那几个域的 -在飞单,读各自认领评论的文件面申报,要求不相交,相交即让行)。日常同域批次选择不跑全局在飞检 -查 —— 同域独立性由本域批次选择保证,跨域相交只可能从例外路径进来。 +**跨域例外路径(越界通道之一;另一条:简单阻塞项直接接手,见「候选与批次」)。** 真拆不动的跨域 +单 PR 由**分诊座位指定**一个车道PM 认领;认领评论**申报完整文件面**;只在这条路径上跑**定向在飞 +检查**(范围 = 该单文件面触及的那几个域的在飞单,读各自认领评论的文件面申报,要求不相交,相交即 +让行)。日常同域批次选择不跑全局在飞检查 —— 同域独立性由本域批次选择保证,跨域相交只从这两条越界通道进来。 **合并队列仍是一条共享串行资源。** 车道买到的是并行编写,不是并行落地;flaky 税随PM 数线性放大 ,红队列同时挡住所有车道 —— 谁发现 flake 谁修或立单,不绕行。 @@ -252,7 +251,9 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout 仓没有车道标签,这形状是它们队列卡的常态);③ 有 `domain:*` 无 pm-state。②③ 只取 `updated_at` 早于 ~2 分钟的卡且不是可选项 —— 半标注卡是协议自己按设计生产的(一次标签写入即把老卡打成半标 注),只带路由或状态机其一的卡对两个视图同时不可见。排除:`tracking`、`status:parked`(其正常形 -状恰是「带域标签无 pm-state」)、全部 `pm:seat` 贴;存量大时每轮限量、优先最新。 +状恰是「带域标签无 pm-state」)、全部 `pm:seat` 贴;存量大时每轮限量、优先最新。**紧急卡直接分诊** +(维护者 2026-08-13):维护者点名或 p0 嫌疑 ⇒ 立即起 `claude-fable-5` 分诊子代理,不等 Routine 班 +次;授权面 = 分诊本身(定级/路由/标签/既有评论格式),⛔ 不写码不认领;产出落卡,与 Routine 分诊同格式同效力(细则见 `references/dispatch-runbook.md`)。 **分类动作**(每张三选一 + 一个修复通道):**`pm:queue`** —— 有具名落点或复现的具体缺陷、范围明 确的工具/门禁修复、恢复不变量的 finding、test-only pin,无可问之事; **`needs-user-decision`** @@ -332,12 +333,14 @@ UI 创建并勾 GitHub 连接器、UI 钉模型(会话内 create_trigger 的Rout PR 前合一次 main;③ 兄弟卡落地后再合一次;④ 冲突交合并队列仲裁,⛔ PM 不手动排序;豁免不外溢。** 阻塞解除后给延后单重新定价**:派发前一单时带必答项「你的改动让 #X 变简单/变难/变得不必要还是无 影响?」,派发后一单前用这个回答重读它的选项与成本,⛔ 不沿用立单时那份(前后单共用同一契约或数 -据表示时适用)。 - -**选择优先级**:`priority:p0` 插队 > 解锁扇出 ≥2 的上游单 > `target:` 板上项 > 被任一开 -卡 `Blocked-by:` 点名的上游单 > 普通队列项,项内 `Bug` 先于 `Feature`/`Task`(维护者 2026-08-11 -与 2026-08-12 裁定)—— **平手判据,⛔ 不是优先级阶梯**(P1–P5 细粒度渐变已试已拒:没人维护必烂) -;扇出从`Blocked-by:` 反向索引现算,⛔ 不为此发明存储标签;**优先是排序,不是豁免**。**解锁那一刻两类断言同时最不可信**: +据表示时适用)。**跨车道简单阻塞项直接接手**(维护者 2026-08-13):本车道卡被他车道卡挡住且阻塞项 +**机械、规格清楚、S 级** ⇒ 被挡座位直接接手做掉,⛔ 不持续等待;他车道卡上走完整认领、尊重其热文 +件串行队、完工留收单注记;带设计判断/语义权衡的仍归属地车道(细则见 `references/dispatch-runbook.md`)。 + +**选择优先级 —— 车道取卡全序**(维护者 2026-08-13,并入 2026-08-11/2026-08-12 的 Bug 平手裁定) +:`priority:p0` 插队 > `pm:blocking` > `target:` 板上项 > type `Bug` > 其余;同级按卡龄 +,`pm:blocking` 级内先按解锁扇出(照旧从 `Blocked-by:` 反向索引现算,⛔ 扇出数不落标签)。全序每级 +取既有信号现读/现算,零逐卡维护 —— 与 P1–P5 已拒不矛盾(拒的是手工维护的渐变档:没人维护必烂);**优先是排序,不是豁免**。**解锁那一刻两类断言同时最不可信**: 卡内文件面要在合并后的 ref 上重验(上游已合 ≠ 卡还成立—— 关掉上游的那个合并最可能顺手把你这张 卡也修掉);PM 自己「这条裁决收窄/关掉了那张卡」的判断是假设不是前提,必须以机制假设身份进派发 令,被证伪就在同一张卡公开更正再重新分诊。 @@ -398,9 +401,8 @@ dev 侧推分支要早 —— 远程分支是在飞工作最硬的证据。**Sta 贴 issue 正文 —— 让 dev 自己读 GitHub 全文与全部评论,并**必须**要求 dev 自查正文完整性(截断 风险随「自己读」转移给 dev,自查是对价不是客套)。派发词只带增量: -- **三分区,措辞决定 dev 敢不敢证伪**:「裁决(不可重裁)」执行不重开;「PM 机制假设(须实测,鼓励 - 证伪)」dev 动手前验证,证伪了照实报告并按裁决意图换路径;「PM 建议的路线(可选,实测优先)」有 - 更好的就换。凡「我觉得可以这样」的一律降到第三块 —— 机制假设穿着裁决的衣服,两个方向都是返工。 +- **三分区,措辞决定 dev 敢不敢证伪**:「裁决(不可重裁)」/「PM 机制假设(须实测,鼓励证伪)」/「PM + 建议的路线(可选,实测优先)」;凡「我觉得可以这样」的一律降到第三块 —— 机制假设穿着裁决的衣服,两个方向都是返工(细则见 runbook)。 - **标准非协商条款 ⛔ 不抄进派发词** —— 已下沉进 `.claude/agents/os-dev.md`;无条件条款只能住 在角色文件(派发词与它冲突时它胜),错了就修那里,⛔ 不靠派发词临时覆盖。**清单、路径、行号在 派发那一刻从树上取**,⛔ 不从卡片/上次派发/记忆抄(取数 `node scripts/pm/dispatch-gates.mjs` @@ -427,9 +429,7 @@ Model tiering 显式传参);S 级但不机械(判断面在设计不在门禁)按 create_trigger+fire —— 维护者 2026-08-07 拍板,trigger 流只留给定时/重复型):① 授权面随 source 不随环境;② 派发词必带**自驱条款**;③ 交付通道 = dev 自开 draft PR + 终报以 issue 评论 交付;④ **云卡 draft PR 一存在立即 `subscribe_pr_activity` —— 硬步骤**。云会话 `SendMessage` -not-reachable 是设计非故障(维护者 2026-08-11 裁定),⛔ 不复测。**接手中断的 dev**:先试 -SendMessage 复活,resume 不可用才走接手协议 —— ⛔ 不重跑原派发词;worktree 不新建;逐 hunk 读 -既有工作再决定;验证完整重跑;assignee/认领评论/分支全不动(既有认领的延续,不是新认领)。 +not-reachable 是设计非故障(维护者 2026-08-11 裁定)⛔ 不复测;**接手中断的 dev**:先试 SendMessage 复活,不可用才走接手协议(四条增量见 runbook,⛔ 不重跑原派发词)。 ### 收集 @@ -459,10 +459,9 @@ SendMessage 复活,resume 不可用才走接手协议 —— ⛔ 不重跑原派 判 unreliable 按接手协议重派。这里是消费侧兜底,⛔ 不能写成「派发词写全了就可以不探」。 - **通知重放先算身份再读内容**:去重三元组 `(issue, 分支, PR head sha)`,与已验收那份相同 ⇒ 记 「重放」即结束,⛔ 不重新验收不重复 ACCEPT;⛔ 到达不读作「还活着」,不到达也不读作「已死」。 -- **直接验收兜底(报告丢失 ≠ 验收停摆)**:(a) draft PR 在且 CI 全绿 + (b) 探活确认已死 - 或 ≥2h 无推送 + (c) 报告未达 ⇒ 直接按 PR 验收(逐文件核对 diff 与申报文件面,对 - 照 `origin/main` 复核前提与验证叙述,复核判据不减);先探活后翻 ready。舰队级死因(全账号断粮) - 下取 (a)+(c) 照常收口,PR body 就是报告。 +- **直接验收兜底(报告丢失 ≠ 验收停摆)**:(a) draft PR 在且 CI 全绿 + (b) 探活确认已死或 ≥2h 无 + 推送 + (c) 报告未达 ⇒ 直接按 PR 验收(复核判据不减,细则见 runbook);先探活后翻 ready;舰队级 + 死因(全账号断粮)下取 (a)+(c) 照常收口,PR body 就是报告。 ### 复核 @@ -644,8 +643,8 @@ pull(今天谁撞上;零拉动默认 defer/ remove);③ AI-agent error-resistanc - Every dev agent works in its **own worktree per repo**(hook 强制;os-dev 定义重申);并行度 以 `batch` 封顶,同批 file-disjoint by construction(唯一松动是维护者明示豁免时的替代四条,申 报降到区域级,不是取消不相交)。**分诊座位永不认领、永不派发、永不写代码;执行座位只在自己那 - 一个车道认领**(唯一例外:分诊指定的跨域例外单);**`domain:*` 只有一个生产者**,执行座位不改标 - 签只上报误标。 + 一个车道认领**(例外两条:分诊指定的跨域例外单、简单阻塞项直接接手);**`domain:*` 只有一个生 + 产者**,执行座位不改标签只上报误标。 - 与 AGENTS.md 冲突时,**AGENTS.md wins**。 ## Report contract(os-dev 返回什么) diff --git a/.claude/skills/pm-dispatch/references/dispatch-runbook.md b/.claude/skills/pm-dispatch/references/dispatch-runbook.md index 2a530e2863..5c63a3398a 100644 --- a/.claude/skills/pm-dispatch/references/dispatch-runbook.md +++ b/.claude/skills/pm-dispatch/references/dispatch-runbook.md @@ -1,7 +1,39 @@ # 派发与收集运行细则(references —— 按需加载) -出处:主文件「派发」「收集」。原则住在主文件;本表是派发云卡、接手死 dev、处置停 -摆/重放/失报那一刻查阅的操作细则。⛔ 不引用 issue 编号。 +出处:主文件「候选与批次」「分诊座位职责」「标签纪律」「派发」「收集」。原则住在 +主文件;本表是选卡排序、紧急分诊、接手阻塞项、派发云卡、接手死 dev、处置停摆/重 +放/失报那一刻查阅的操作细则。⛔ 不引用 issue 编号。 + +## 车道取卡全序与 `pm:blocking`(维护者 2026-08-13) + +出处原话(逐字,未译):「被依赖的卡片是不是应该通过label标注提高优先级。项目经理 +处理任务清单时,bug 是不是应该提高优先级。」落地在主文件「选择优先级」与「标签纪 +律」;本节是排序与标签语义的细则。 + +- **全序**:`priority:p0` > `pm:blocking` > `target:` 板上项 > type `Bug` > 其余; + 同级按卡龄,`pm:blocking` 级内先按解锁扇出(从 `Blocked-by:` 反向索引现算)。理 + 由:p0 是维护者显式裁定;blocking 卡住别的车道的吞吐,一张挡多张;bug 是已坏的 + 不变量,先于增强;卡龄防饿死。每级只读既有信号(标签、原生 type 字段、 + created_at),零逐卡维护 —— 这正是「手工 P1–P5 渐变已拒(没人维护必烂)」与本全 + 序可行的差别:被拒的是需要有人持续赋值的档位,不是从现有信号算出来的顺序。 +- **`pm:blocking`** = 「本 open 卡有 open 的下游依赖者」。⛔ 不手工维护:分诊轮 + sweep 从卡身 `Blocked-by:` 引用的反向索引推导,写入与摘除都在 sweep;依赖者全关 + 即摘。写标签只是把决策箱指标早已在现算的结果**缓存**到列表页可见的地方 —— 推导 + 本性不变,「一个标签存在,当且仅当有具名读者」不变(具名读者:取卡全序、列表页 + 扫描)。种子与消费者注记在 `scripts/pm/ensure-pm-labels.sh`;手工挂上的按误标处 + 理,sweep 对着索引校正。 + +## 紧急卡 fable 直接分诊(维护者 2026-08-13) + +出处原话(逐字,未译):「对于比较紧急的卡片,是否应该使用 fable 5 子agent直接分 +诊」。紧急卡 = 维护者点名的、或 `priority:p0` 嫌疑的。座位不等分诊 Routine 的班次 +节奏,立即起一个 `claude-fable-5` 分诊子代理: + +- 授权面 = 分诊本身(定级/路由/标签/分诊评论既有格式),⛔ 不写码、不认领、不派发; +- 产出落在卡上,与 Routine 分诊**同格式同效力**(审计评论照既有分诊格式,原生 + type / `domain:*` / pm-state 照既有纪律); +- 单一生产者纪律照旧:与「代扫」同一条边界 —— 分诊座位在班且已在处理同一张卡即让 + 行;紧急通道改变的是节奏,不是分类生产者的数目。 ## 云卡(`mode:cloud`)四课 @@ -62,6 +94,21 @@ subagent 在 PM 自己的容器内运行,agent 定义与技能文本读的是** - assignee、认领评论、分支**全部不动** —— 这是既有认领的延续,不是新认领;认领评论 记录接手,不被替换。 +## 跨车道简单阻塞项直接接手(维护者 2026-08-13) + +出处原话(逐字,未译):「如果当前任务依赖其他赛道的任务,如果判断简单,是否可以直 +接接手而不必持续等待」。形状:本车道卡被他车道卡挡住,且该阻塞项**判断简单** —— +机械、规格清楚、S 级 ⇒ 被挡座位直接接手做掉,⛔ 不持续等待;本条只覆盖「等一周 vs +做十分钟」的形状。 + +- **纪律不减**:在他车道卡上走完整认领(assign + 会话 ID + 分支评论,`Claim:` 首行 + 拼写照旧)、尊重该车道热文件串行队(读其座位贴具名段)、完工后卡上留收单注记; +- **边界**:凡带设计判断/语义权衡的仍归属地车道;`packages/spec` 恒归 spec 座位, + 本条不豁免(唯一所有者规则更硬); +- **⛔ 不是「借调」回归**:借调(按积压形状把人调去别的车道)已议已删;本条判据是依 + 赖形状 —— 只做被本车道开卡 `Blocked-by:` 点名的那一个阻塞项,做完即回,⛔ 不顺手 + 接第二张。 + ## 停摆复位梯度 停摆的 subagent 永不自愈 —— 携带**任务中途状态**的完成通知本身就是停摆信号,立刻 diff --git a/scripts/pm/ensure-pm-labels.sh b/scripts/pm/ensure-pm-labels.sh index 3b15123768..1b2ff2c5b9 100644 --- a/scripts/pm/ensure-pm-labels.sh +++ b/scripts/pm/ensure-pm-labels.sh @@ -24,6 +24,14 @@ for R in objectstack-ai/objectstack objectstack-ai/objectui objectstack-ai/cloud gh label create needs-user-decision -R "$R" -c d93f0b -d "Blocked on a maintainer decision — do not dispatch" 2>/dev/null || true gh label create pm:on-hold -R "$R" -c e4e669 -d "Decision made, deliberately deferred — do not dispatch, do not nag; restart condition in the hold comment" 2>/dev/null || true gh label create pm:blocked -R "$R" -c b60205 -d "Blocked by another issue/PR — body carries Blocked-by: #N" 2>/dev/null || true + # pm:blocking is a derived CACHE, never hand state (maintainer opinion + # 2026-08-13, superseding the earlier derived-only-no-stored-label ruling): + # the triage sweep writes/removes it from the Blocked-by: reverse index, and + # it comes off when every dependent closes. Named consumers: the lane queue + # pull order (p0 > blocking > target board > Bug > age; SKILL.md 选择优先级) + # and list-page scans. A hand-set instance is mislabeling — the sweep + # corrects it against the index. + gh label create pm:blocking -R "$R" -c 8250df -d "Derived cache from the Blocked-by reverse index: open card with open dependents — never hand-set" 2>/dev/null || true gh label create finding -R "$R" -c c2e0c6 -d "Recorded observation — held, not dispatchable until the findings triage round grades it" 2>/dev/null || true gh label create pm:epic -R "$R" -c 5319e7 -d "Parent delegated to a dedicated epic PM (session + territory in the parent's own body) — other PMs never dispatch into its subtree" 2>/dev/null || true done