Skip to content

docs(pm-dispatch): step 0 扫描判据扩为三个析取,兜住两种不可行动的卡片形状 (#6405) - #6455

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6405-sweep-predicate-blind-spots
Aug 7, 2026
Merged

docs(pm-dispatch): step 0 扫描判据扩为三个析取,兜住两种不可行动的卡片形状 (#6405)#6455
hotlong merged 1 commit into
mainfrom
claude/issue-6405-sweep-predicate-blind-spots

Conversation

@hotlong

@hotlong hotlong commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #6405

step 0 的 backlog sweep 判据只选「无 pm:* 标签」的 issue。两种卡片形状落在判据之外却仍不可行动,今日各实测到停滞。本 PR 把判据扩为三个析取,并给两条最容易漏的护栏写进判据本身。

1. 判据 before / after

Before(.claude/skills/pm-dispatch/SKILL.md,step 0 原文):

The maintainer does not pre-sort the backlog. On every round (and every
idle check-in), sweep issues that carry no pm:* / needs-user-decision
label and classify each:

⚠️ 注意实测到的一处偏差:issue 正文把今天的判据转述为「无 pm:* / domain:* / needs-user-decision / finding」,但文件字面只有 pm:* / needs-user-decisiondomain:* 那一项一直只活在实践里(带车道标签的单被读作「已路由」而跳过)—— 这正是盲区 B 能成立的原因,也是 after 里必须把它写明的原因:析取 ③ 一旦存在,① 不写明 domain:* 就会和 ③ 互相吞掉。

After(改后原文):

The maintainer does not pre-sort the backlog. On every round (and every
idle check-in), sweep every issue matching 任一析取,并逐张分类:

  1. pm:*、无 needs-user-decision、也无 domain:* —— 全裸卡。今天的
    规则,判据不变;domain:* 一项此前只活在实践里(带车道标签的单被读作
    「已路由」而跳过),析取 3 出现后必须写明,否则两条互相吞掉。
  2. pm:queue 但无 domain:* —— ⚠️ 仅限 objectstack-ai/objectstack
  3. domain:* 但无 pm-state(pm:queue / pm:dispatched / pm:blocked /
    pm:on-hold / finding / needs-user-decision)。

析取 2、3 只取 updated_at 早于 ~2 分钟前的卡(析取 1 无此限)。

排除项不变(trackingstatus:parked#4604pm: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_atupdated_at(我实测);(b) 六张 A 组卡的 updated_at 密集落在 16:56:21Z–16:56:29Z 的 8 秒窗口内,而 created_at 分散在 11:00–13:55Z —— 即 domain:* 是 16:56Z 那一轮一次性补的;(c) 其中三张卡在正文里自陈了立单时的标签状态。⇒ 标签组合列为实测+文证,代入结果为论证(判据是纯布尔式,无执行体)。

A 组 —— 盲区 A(应被析取 ② 选中)

立单时标签(实测+文证) 当前标签(实测) created → updated(实测) 选中?
#6317 pm:queue pm:queue domain:devx 13:39:46Z → 16:56:28Z ✅ ②
#6329 pm:queue pm:queue domain:engine-core 13:55:27Z → 16:56:29Z ✅ ②
#6285 pm:queue pm:queue domain:metadata 13:01:07Z → 16:56:26Z ✅ ②
#6283 pm:queue pm:queue domain:spec 13:00:51Z → 16:56:25Z ✅ ②
#6214 pm:queue pm:queue domain:spec 11:00:17Z → 16:56:21Z ✅ ②
#6274 pm:queue pm:queue domain:spec target:v17 12:52:47Z → 16:56:23Z ✅ ②

三张卡的正文自陈(文证,逐字):

⇒ 三张卡是协议要求它们长成这样的,不是谁忘了打标签。这就是 issue 说的「稳定生产者」:旧判据「带了 pm:* 就跳过」恰好把协议自己造出来的形状全部漏掉。年龄下限对它们无碍(被扫时已 3–4 小时)。

B 组 —— 盲区 B(应被析取 ③ 选中)

立单时标签 当前标签(实测) created → 被捞起 选中?
#6378 tooling domain:devx,无 pm-state tooling pm:dispatched domain:devx 15:38:56Z → 16:47Z(~69 min) ✅ ③

① 为 ✗ 是因为改后的 ① 显式要求「也无 domain:*」—— 而这恰好复现了它当初为什么会停滞:旧判据字面上是「无 pm:*」(#6378 确实无 pm:*),但实践把带 domain:* 的单读作已路由而跳过。⇒ 只有析取 ③ 能兜住它,把一条一直靠人记着的实践规则变成判据。

反例组 —— 不该被扫的卡(应全部不被选中)

标签(实测) 为什么不该被选 结果
#6405(本单) pm:dispatched domain:devx 正常流转中 ✗(pm:dispatched 是 pm-state) ✅ 不选
#6283(现在) pm:queue domain:spec 分诊已补完标签 ✗(有 domain) ✗(有 pm-state) ✅ 不选
#6023 pm:seat 座位贴 = 协议载体 ✅ 不选(排除项 + 判据双重)
#4606 status:parked domain:spec parked,状态归别的机制 ⚠️ ③ 命中 ✅ 不选(靠 status:parked 排除项)
#3146 enhancement status:parked domain:engine-core 同上 ⚠️ ③ 命中 ✅ 不选(同上)
#2776 security status:parked tracking 同上 ✗(无 domain) ✅ 不选(双重排除)
objectui#3614 pm:queue 兄弟仓整仓座位 靠 repo scope ✅ 不选(见声明 B)
cloud#1198 pm:queue 同上 靠 repo scope ✅ 不选(见声明 B)

⚠️ 第 4、5 行是这次代入跑出来的真结论,不是装饰:status:parked 的卡正常形状就是「带 domain:*、无 pm-state」(今日实测 3 张 parked,两张正是此形),析取 ③ 一加,排除项从「省一次现场判断」升级为正确性必需 —— 漏判就是每轮把 parked 卡重新扫回来一次。已把这句写进排除项段落。

第 2 行(#6283 的现状)证明判据是自限的:分诊补完 domain:* 后,卡片自动离开扫描面,不会每轮重扫。

4. 声明 B —— 析取 ② 的 repo scope 真的收住了(实测)

若不加 scope,②「有 pm:queue、无 domain:*」在兄弟仓匹配的不是个别卡,而是全部:

实测项 读数 方法
objectui open pm:queue 38 list_issues objectui labels:[pm:queue] state:OPENtotalCount: 38
cloud open pm:queue 19 list_issues cloud labels:[pm:queue] state:OPENtotalCount: 19
objectui 是否存在 domain:* 标签 不存在 get_label objectui domain:devxlabel 'domain:devx' not found in objectstack-ai/objectui

具体样例(实测标签,全部无 domain:*):objectui#3614 pm:queue、objectui#3613 pm:queue、objectui#3593 pm:queue、objectui#3562 pm:queue、cloud#1198 pm:queue、cloud#1197 pm:queue、cloud#1186 pm:queue

关键一步是第三行:domain:* 标签在兄弟仓根本不存在,所以「无 domain:*」在那里是恒真的 —— 不是「这些卡碰巧没打」,而是打不上。⇒ 未加 scope 时 ② 会一次选中 38 + 19 = 57 张设计上完全正常的卡;加了 scope 后这 57 张全部不匹配(仓库不符,② 直接不适用)。这条护栏不加,判据本身就是当天最大的噪声源。

  • 不加 scope:57 张误选 —— 实测(计数与标签不存在性均为实读)。
  • 加 scope:0 张 —— 论证(scope 是仓库名相等判断,无执行体)。

泛化后写进发布件的版本更有用:任何以「某个标签缺席」为条件的析取,都必须限定到该标签真实存在的仓库 —— 判据是「那个仓库里到底有没有这个标签」,而不是「有没有卡碰巧带着它」。

5. 声明 C —— 三轴决策框架未被触碰(实测,逐字节)

check:skill-frame-sync 改前 / 改后各跑一次,输出 diff 无任何差异、cmp 逐字节相同、md5 相同:

frame-sync BEFORE EXIT=0
frame-sync AFTER  EXIT=0
$ diff frame-sync-before.txt frame-sync-after.txt
(无输出,exit 0)
$ cmp frame-sync-before.txt frame-sync-after.txt
(无输出 → byte-for-byte identical)
$ md5sum
6aa2a81538f6ffa6e0d642a216aa9133  frame-sync-before.txt
6aa2a81538f6ffa6e0d642a216aa9133  frame-sync-after.txt

两次输出的正文(相同):

✓ check-skill-frame-sync self-test: 12 cases pass.
✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
  3 axes: business-need → long-term-soundness → ai-authoring-safety
  binding sentence present in all 4; 4 count mention(s) agree; 40 markdown files scanned for undeclared copies.

另跑 check:skill-frame-freshness EXIT=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.md L173-188 是同构的 ### 0. Backlog sweep — classification is a standing duty, not a request,判据同样是「sweep issues carrying no pm:* / needs-user-decision label」⇒ 同一缺陷在发布件里逐字同形,它依赖的不是 domain:* 这个具体标签名,而是「判据只看一类标签,而不可行动性来自另一类标签的缺席」这个结构。
  • 但发布件没有车道 / 座位词汇(实测:grep -i -E 'lane|seat|domain:' 只命中 frontmatter 的 domain: process 与两处 three-axis),且其 pm 标签词汇只有 pm:queuepm:dispatched(实测:grep -o 'pm:[a-z-]+' → queue ×6、dispatched ×4;finding / pm:on-hold / pm:blocked / pm:epic 在该文件里零定义)。
  • ⇒ 泛化版写成机制描述:用 "classification label(area, component, owning team, lane)" 指代那一类标签,queue state 只列该文件真实定义的 pm:queue / pm:dispatched / needs-user-decision 并加「any other pm:* state your setup defines」。⛔ 不带本仓 issue 号、⛔ 不引入该文件不存在的标签字面名。
  • 两条护栏一并泛化(泛化后适用面比本仓版本更宽):任何以标签缺席为条件的析取必须限定到该标签存在的仓库;半标注类析取的年龄下限键在 updated_at
  • role 词:发布件全文计数仍为 0(check:role-word EXIT=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 之后跑)

EXIT 关键输出
check:skill-frame-sync(改前) 0 12 cases pass / 4 copies … 3 axes
check:skill-frame-sync(改后) 0 与改前逐字节相同(md5 一致)
check:skill-frame-freshness 0 the frame itself is unchanged — that is fine
check:doc-authoring 0 365 files clean — no bare metadata literals
check:role-word 0 44 baselined file(s), no new occurrences
check:nul-bytes 0 6072 tracked text file(s) … no raw ASCII control bytes
check:skill-docs(发布件已改,故跑) 0 ✓ skills/README.md / ✓ content/docs/ai/skills-reference.mdx / Skill docs in sync
控制字节自扫 无命中 grep -naP 两个改动文件均 0 命中

9. 不在本 PR 里


Generated by Claude Code

旧判据只选「无 `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
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 9:47pm

Request Review

@github-actions github-actions Bot added the size/s label Aug 7, 2026
@hotlong hotlong added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/s labels Aug 7, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 7, 2026
@hotlong
hotlong marked this pull request as ready for review August 7, 2026 21:58
@hotlong
hotlong added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 4eeb81f Aug 7, 2026
26 checks passed
@hotlong
hotlong deleted the claude/issue-6405-sweep-predicate-blind-spots branch August 7, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SKILL] pm-dispatch step 0: the backlog sweep predicate misses two un-actionable card shapes (pm:queue without domain:*; domain:* without a pm-state)

2 participants