docs(pm-dispatch): step 0 扫描判据扩为三个析取,兜住两种不可行动的卡片形状 (#6405) - #6455
Merged
Conversation
旧判据只选「无 `pm:*` 标签」的 issue,两种卡片形状落在判据之外却仍不可行动,今日 各实测到停滞: - 盲区 A(实测 6 张,11:00–13:55Z 立、16:47Z 才靠两个 PM 会话手工报告捞起; #5794 / #5744 各停滞整日 ⇒ 第三、第四次同形):有 `pm:queue`、无 `domain:*`。 跨座位转移卡与队列管家队列健康卡按协议**天然预先带 `pm:queue`**,又按单一 生产者规则不准自打 `domain:*` ⇒ 看板显示可派、实际谁都不能认领。生产者是 协议本身,不是某个车道的坏习惯。 - 盲区 B(实测 1 张,停滞 ~69 分钟):有 `domain:*`、无 pm-state。同一条纪律 当天早上刚写在另一张卡上,几小时后 PM 立单时照犯 ⇒ 纪律写在别处不够, 判据本身必须兜住。 判据改为三个析取:① 无 `pm:*` 且无 `domain:*`(今天的规则,不变;`domain:*` 一项此前只活在实践里,析取 ③ 出现后必须写明);② 有 `pm:queue` 但无 `domain:*`, **仅限 objectstack**;③ 有 `domain:*` 但无 pm-state。排除项不变。 两条护栏: - 析取 ② **repo-scoped**。兄弟仓是整仓座位,车道标签在那里根本不存在(实测 objectui 查无 `domain:devx` 标签),「有 `pm:queue` 无 `domain:*`」是它们每一张 队列卡的正常形状 —— 不限定即一次扫进 objectui 38 + cloud 19 张。 - 析取 ②③ 的年龄下限键在 **`updated_at` 而非 `created_at`**:半标注状态既可能 来自刚立的卡,也可能来自一次标签写入把老卡打成半标注(分诊自己打标签就是 分开的两次写),`created_at` 漏掉后者。 发布件 `skills/objectstack-pm-dispatch/SKILL.md` 有同构的 step 0 面(L173-188 同样 以「carrying no `pm:*` / `needs-user-decision`」为判据),按 #5451 route B 加泛化版: 写成机制描述,不带本仓 issue 号,不引入该文件不存在的标签词汇(`domain:*` / `finding` / `pm:on-hold` 在发布件里均无定义),`role` 词计数仍为 0。 三轴决策框架未触碰:`check:skill-frame-sync` 改前改后输出 **byte-for-byte 相同** (md5 6aa2a81538f6ffa6e0d642a216aa9133,cmp 无差异)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
hotlong
marked this pull request as ready for review
August 7, 2026 21:58
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6405
step 0 的 backlog sweep 判据只选「无
pm:*标签」的 issue。两种卡片形状落在判据之外却仍不可行动,今日各实测到停滞。本 PR 把判据扩为三个析取,并给两条最容易漏的护栏写进判据本身。1. 判据 before / after
Before(
.claude/skills/pm-dispatch/SKILL.md,step 0 原文):pm:*/domain:*/needs-user-decision/finding」,但文件字面只有pm:*/needs-user-decision。domain:*那一项一直只活在实践里(带车道标签的单被读作「已路由」而跳过)—— 这正是盲区 B 能成立的原因,也是 after 里必须把它写明的原因:析取 ③ 一旦存在,① 不写明domain:*就会和 ③ 互相吞掉。After(改后原文):
排除项不变(
tracking、status:parked、#4604、pm:seat座位贴),另补一句:析取 ③ 使排除项更吃重(见第 3 节反例 3-5)。年龄下限为什么键在
updated_at而不是created_at派单要求「卡应在存在一两分钟后才被扫」。实现时发现按
created_at判兜不住一半:半标注状态有两个来源 —— ① 刚立还没打完标签的新卡(created_at能兜),② 一次标签写入把老卡打成半标注。而 ② 不是边角情况:分诊自己打标签就是分开的两次写(domain:*一次、pm:queue一次),中间那几秒的卡created_at可能是几天前,却正落在析取 ②/③ 里。updated_at两种都兜住;代价是一条评论也会把卡推迟一轮(下一轮即取到,可接受)。2. 反向验证的方向申报(先申报后执行)
3. 声明 A —— 判据可判定(实测标签 + 论证代入)
方法:取今日实测的 7 张停滞卡 + 8 张不该被扫的对照卡,把标签组合逐一代入新判据。
取证边界(如实说明):本会话的直连
api.github.com被会话策略拒绝(GitHub access is not enabled for this session,非配额),timeline API 读不到,所以「停滞时刻的标签组合」不是我从 label 事件流直接读出的。它由三项实测证据支撑:(a) 卡片当前标签、created_at、updated_at(我实测);(b) 六张 A 组卡的updated_at密集落在 16:56:21Z–16:56:29Z 的 8 秒窗口内,而created_at分散在 11:00–13:55Z —— 即domain:*是 16:56Z 那一轮一次性补的;(c) 其中三张卡在正文里自陈了立单时的标签状态。⇒ 标签组合列为实测+文证,代入结果为论证(判据是纯布尔式,无执行体)。A 组 —— 盲区 A(应被析取 ② 选中)
pm:queuepm:queuedomain:devxpm:queuepm:queuedomain:engine-corepm:queuepm:queuedomain:metadatapm:queuepm:queuedomain:specpm:queuepm:queuedomain:specpm:queuepm:queuedomain:spectarget:v17三张卡的正文自陈(文证,逐字):
domain:*/repo:*label applied — routing labels are the triage seat's single-producer territory. Onlypm:queueis set here.」health-monitor.test.ts的「leaves no ref'd timer behind when the health check wins the race」(#4875)在合并队列全量跑间歇红 —— 踢掉 PR #6269 一次,重排即过 #6329:「未指派,标签交分诊(落点packages/core→ 域待分诊判)」⇒ 三张卡是协议要求它们长成这样的,不是谁忘了打标签。这就是 issue 说的「稳定生产者」:旧判据「带了
pm:*就跳过」恰好把协议自己造出来的形状全部漏掉。年龄下限对它们无碍(被扫时已 3–4 小时)。B 组 —— 盲区 B(应被析取 ③ 选中)
toolingdomain:devx,无 pm-statetoolingpm:dispatcheddomain:devx① 为 ✗ 是因为改后的 ① 显式要求「也无
domain:*」—— 而这恰好复现了它当初为什么会停滞:旧判据字面上是「无pm:*」(#6378 确实无 pm:*),但实践把带domain:*的单读作已路由而跳过。⇒ 只有析取 ③ 能兜住它,把一条一直靠人记着的实践规则变成判据。反例组 —— 不该被扫的卡(应全部不被选中)
pm:dispatcheddomain:devxpm:dispatched是 pm-state)pm:queuedomain:specpm:seatstatus:parkeddomain:specstatus:parked排除项)enhancementstatus:parkeddomain:engine-coresecuritystatus:parkedtrackingpm:queuepm:queuestatus:parked的卡正常形状就是「带domain:*、无 pm-state」(今日实测 3 张 parked,两张正是此形),析取 ③ 一加,排除项从「省一次现场判断」升级为正确性必需 —— 漏判就是每轮把 parked 卡重新扫回来一次。已把这句写进排除项段落。第 2 行(#6283 的现状)证明判据是自限的:分诊补完
domain:*后,卡片自动离开扫描面,不会每轮重扫。4. 声明 B —— 析取 ② 的 repo scope 真的收住了(实测)
若不加 scope,②「有
pm:queue、无domain:*」在兄弟仓匹配的不是个别卡,而是全部:pm:queue卡list_issues objectui labels:[pm:queue] state:OPEN→totalCount: 38pm:queue卡list_issues cloud labels:[pm:queue] state:OPEN→totalCount: 19domain:*标签get_label objectui domain:devx→label 'domain:devx' not found in objectstack-ai/objectui具体样例(实测标签,全部无
domain:*):objectui#3614pm:queue、objectui#3613pm:queue、objectui#3593pm:queue、objectui#3562pm:queue、cloud#1198pm:queue、cloud#1197pm:queue、cloud#1186pm:queue。关键一步是第三行:
domain:*标签在兄弟仓根本不存在,所以「无domain:*」在那里是恒真的 —— 不是「这些卡碰巧没打」,而是打不上。⇒ 未加 scope 时 ② 会一次选中 38 + 19 = 57 张设计上完全正常的卡;加了 scope 后这 57 张全部不匹配(仓库不符,② 直接不适用)。这条护栏不加,判据本身就是当天最大的噪声源。泛化后写进发布件的版本更有用:任何以「某个标签缺席」为条件的析取,都必须限定到该标签真实存在的仓库 —— 判据是「那个仓库里到底有没有这个标签」,而不是「有没有卡碰巧带着它」。
5. 声明 C —— 三轴决策框架未被触碰(实测,逐字节)
check:skill-frame-sync改前 / 改后各跑一次,输出 diff 无任何差异、cmp 逐字节相同、md5 相同:两次输出的正文(相同):
另跑
check:skill-frame-freshnessEXIT=0:「the decision frame in this tree is current with origin/main … the frame itself is unchanged」。6. 发布件核实结论(去读了文件,不凭印象)
结论:发布件有同构结构面 ⇒ 按 #5451 route B 加泛化版。
skills/objectstack-pm-dispatch/SKILL.mdL173-188 是同构的### 0. Backlog sweep — classification is a standing duty, not a request,判据同样是「sweep issues carrying nopm:*/needs-user-decisionlabel」⇒ 同一缺陷在发布件里逐字同形,它依赖的不是domain:*这个具体标签名,而是「判据只看一类标签,而不可行动性来自另一类标签的缺席」这个结构。grep -i -E 'lane|seat|domain:'只命中 frontmatter 的domain: process与两处three-axis),且其 pm 标签词汇只有pm:queue与pm:dispatched(实测:grep -o 'pm:[a-z-]+'→ queue ×6、dispatched ×4;finding/pm:on-hold/pm:blocked/pm:epic在该文件里零定义)。pm:queue/pm:dispatched/needs-user-decision并加「any otherpm:*state your setup defines」。⛔ 不带本仓 issue 号、⛔ 不引入该文件不存在的标签字面名。updated_at。role词:发布件全文计数仍为 0(check:role-wordEXIT=0,该文件不在 baseline,新增一次即红)。7. 相邻但不合并 —— #6393 / #6410 未搭车
#6393(一次轮次复盘带出的三条 fleet-wide hazards)与本单同文件、不同评审面:本单改的是 step 0 的选择判据(哪些卡进入分诊),#6393 是跨车道的风险条款。issue 正文与派单都明示本单不是 #6393 的 rider,本座位对 #6405 / #6393 / #6410 串行派发。
⇒ 本 PR 只有 #6405 一条
Fixes,git diff --stat两个文件、全部改动都在 step 0 扫描判据及其排除项段落之内;⛔ 未顺手改 #6393 / #6410 的任何文本,即使它们最终会落在同一份文件的邻近段落。若三者文本相邻处将来产生冲突,按合并序解决,不在本 PR 预先合并。8. 门禁 EXIT 表(均在
git add之后跑)check:skill-frame-sync(改前)12 cases pass/4 copies … 3 axescheck:skill-frame-sync(改后)check:skill-frame-freshnessthe frame itself is unchanged — that is finecheck:doc-authoring365 files clean — no bare metadata literalscheck:role-word44 baselined file(s), no new occurrencescheck:nul-bytes6072 tracked text file(s) … no raw ASCII control bytescheck:skill-docs(发布件已改,故跑)✓ skills/README.md/✓ content/docs/ai/skills-reference.mdx/Skill docs in syncgrep -naP两个改动文件均 0 命中9. 不在本 PR 里
.claude/agents/os-dev.md、其它 skill、content/docs/releases/**。skip-changeset标签。Generated by Claude Code