Skip to content

Commit e43d326

Browse files
feat(pm): count outside demand in the daily summary and put human-opened cards first within a priority (#29572)
domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J <!-- claude-projects-attribution: {"github_login":"huangyiirene"} --> _Requested by **Irene** · [project thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xCXvQy52cxubnvrWgeRv8k1)_ Fixes #29467 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:总监每日摘要加一行「外部需求行」三个数;取卡全序同级内非车队卡排在车队卡之前(车队 = App 作者或正文首行是席位签名);H67 读序同步。 - 为什么改:维护者对「车队在给自己造活」的评估回了「同意」;目前没有任何读数说明一周的工作有多少回应车队之外的需求。 - 风险与回滚:只改规则文本与一个巡检的读序,不升级、不越 p0;回滚即 revert 本 PR。 - 先看:`lanes/director.md` 职责四新增三行;`execution-duties.md` 同级平手规则;`check-half-states.mjs` 的 `h67ReadOrder`。 - 两分钟验证:`node scripts/pm/check-half-states.mjs --self-test`,预期末行 H67 组 158/158 全过。 - 需要你做的:无,席位落地(Tier S)。 ## What changes - **Daily summary** (`lanes/director.md`, duty four): the summary lists an outside-demand line, defined in three new lines. The three numbers are read when the summary is written: (1) cards opened in the window by non-fleet authors; (2) the share of the window's closed cards that non-fleet authors opened; (3) the open non-fleet cards with no `pm:*` state, each named with its age. The query is spelled out and posted with the line. For 1 and 2 it takes the all-state `since` listing from the window start, which is a superset, and counts a card only when its `created_at` (for 1) or `closed_at` (for 2) falls in the window. For 3 it takes the open listing. Pull requests are excluded from all three. - **Fleet card**, per the seat's ruling as amended in its contract review: a card whose author is an App (`user.type` `Bot`, every `[bot]` login, `github-actions[bot]` included), OR whose body's first non-empty line is a seat signature. Seat-operated human accounts file cards too, and the card's own measurement excluded seat posts. Every other card is non-fleet. - **Candidate order** (`execution-duties.md`): within one level of the order, a non-fleet card sorts first, then `Bug`, then card age. The rule never raises a card's level and never jumps `priority:p0`. - **`h67ReadOrder`** (`scripts/pm/check-half-states.mjs`) is the only `scripts/pm` reader that sorts candidates by priority. It now orders by rank, then fleet status, then age. It reuses two readers from the same file: `artefactAuthor`, and `readSignatureLine`, the signature reader `record-recognisers.mjs` imports from this file. No new regex is added. An unreadable author counts as the fleet's, so it is never promoted. No new patrol row, battery or gate. ### Ratchet payment (both files stay at their ceilings, no ceiling row moved) | file | before / after / ceiling | added | paid by | |:--|:--|:--|:--| | `lanes/director.md` | 86 / 86 / 86 | 3 summary lines plus 1 moved line (the fleet definition and the window test were later edited in place) | removed the one-bullet section `## 与 skills 席的分界`: its heading, its blank line and the blank line above it. Its bullet moved unchanged into 授权与形态 next to the scope line. | | `execution-duties.md` | 188 / 188 / 188 | 1 tie-break line (the seat-signature clause was later edited in place) | deleted the separate ruling-implementation re-check of the action surface. Its content moved into the work-item surface line, which now names the ruling's items next to the card's and the triage comment's. | Every line in both files is at most 120 bytes; the longest new line is 119. ## Tests - `node scripts/pm/check-half-states.mjs --self-test` on cbc61eb: 5219 cases pass, and the battery `H67 queued merged-delivery reading` reads 158/158. The pin moved from 142 to the count, so none of the six new cases can be removed without turning the battery red. - New cases: - A human-opened p2 card is reached before an older fleet-opened p2 card. - Two fleet cards of one priority stay oldest-first. - The fleet leg never lifts a card out of its priority or past p0. - `github-actions[bot]` counts as fleet, and an unreadable author is not promoted. - A human-account card whose body opens with a seat signature stays behind an older fleet card. - The same human card with the signature moved off its first line is reached first again. - **Reverse verification**, two ablations, each on a committed head and each restored from HEAD and green afterwards: - On 1d7e6fb, with `fleet(a) - fleet(b)` replaced by `0`, the run printed `1 of 5189 case(s) failed`: `⭐ H67 order: within ONE priority a human-opened card is reached before an OLDER fleet-opened one (got "21,22", want "22,21")`. - On cbc61eb, with the signature test replaced by `false`, the run printed `1 of 5219 case(s) failed`: `⭐ H67 order: a HUMAN account whose body opens with a seat signature is the fleet's — it stays behind the older fleet card (got "22,21", want "21,22")`. ## Acceptance notes - The director's payment removes a section heading and two blank lines, not prose; the bullet moved byte-identically. The seat's contract review ruled this a real deletion. - The seat's should-fixes S1 (seat-signed cards are the fleet's) and S2 (the summary query names its local window test) are in cbc61eb, paid in place in both texts. The delta review's should-fix is in f64ec72: the query line names the all-state listing again, because number 2 needs closed cards and the default listing is open-only. It was edited in place, and director.md stays at 86 lines. - PR #29515 landed first and touched `lanes/director.md` and `check-half-states.mjs`. `main` was merged in with no conflict, and both files were re-measured on the merged tree. ## Verification <details><summary>Local gates on cbc61eb: 54 derived, 54 run, all exit 0 (`dispatch-gates --ran`)</summary> - On head cbc61eb, `node scripts/pm/dispatch-gates.mjs --commands` derived 54 commands. All 54 ran, each with a 900 s timeout, with the exit codes recorded before any pipe. All 54 exited 0. - The `--ran` reconciliation printed: `54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED`. - Both ratcheted files read headroom 0 at their ceilings (86 and 188), and every line is at most 120 bytes. - On f64ec72, a one-line text edit to director.md, these gates were re-run: `check:pm-skill-ratchet`, `check:pm-skill-id-lint`, `check:doc-authoring`, `check:nul-bytes`. All exited 0, and director.md read 86 lines (ceiling 86, headroom 0). - Earlier, on 7d105ed: `pnpm check:pm-skill-ratchet` had exited 1 on a 125-byte line, which was trimmed to 119 bytes. The full run on cbc61eb above supersedes those readings. - Prerequisites: `pnpm install --frozen-lockfile`, and a `turbo` build of `@objectstack/formula` and `@objectstack/lint` for `check:doc-formula-expressions`. - Families CI runs beyond the derived list are read from this PR's check runs, not re-run locally. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [ ] 3. No formatter ran; `pnpm lint` passes. (no formatter ran; `pnpm lint` was not run locally on this diff, so the CI lint job decides it) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (publishes nothing: `.claude/**` and `scripts/pm/**`; `skip-changeset`) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. (except the 维护者速读 block, which the seat contract writes in Chinese) - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01J8VVAjYZRbZ8TG8TRzG5zd --- _Generated by [Claude Code](https://claude.ai/code/session_01J8VVAjYZRbZ8TG8TRzG5zd)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7b293c4 commit e43d326

3 files changed

Lines changed: 48 additions & 18 deletions

File tree

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
- 维护者裁决逐字引入派发词裁决分区作约束,无则写明无裁决。
1313
- 派发前做前提过时检查,裁决同罪,三面都查:动作面、卡引用面、工作项面。
1414
- 动作面:`git log --oneline -20 -- <paths>`。
15-
- 裁决实施卡的动作面再核被点名的动作在 `origin/main` 上还没被做掉。
1615
- 卡引用面:裁决/卡片点名的每张 issue 逐个读当前 open/closed/assignee。
1716
- 引用读到的现状,⛔ 不引用裁决写作时的描述。
18-
- 工作项面:卡片与分诊评论列的每条工作项逐条对树核验是否仍未完成,⛔ 不对卡核验。
17+
- 工作项面:卡片、裁决与分诊评论列的工作项逐条对树核验是否仍未完成,⛔ 不对卡核验。
1918
- `batch` 等待不填槽,四态见 SKILL.md 该行;同批独立按文件面不相交判,⛔ 不按包或 check 名。
2019
- 软顶:本席已验收等人批的 Tier H PR 逾 5 ⇒ 停派 Tier H,S/非受管照常,轮报列积压;两周复测。
2120
- single-writer 路径 = `SINGLE_CLAIM_PATHS` 所枚举;共享其它路径是普通并发,后落地方解冲突。
@@ -52,7 +51,8 @@
5251
- 取卡全序:维护者直派插队卡(出处三件)> 契约面卡(判据见 `references/lanes/spec.md`)> 标签序。
5352
- 标签序:`priority:p0` > `pm:blocking` > 功能点位次 > `target:` 板上项 > p1 > p2 > p3 > 无级。
5453
- `pm:blocking` 级内先按解锁扇出(从 `Blocked-by:` 反向索引现算,⛔ 扇出数不落标签)。
55-
- 全序每级现读/现算,零逐卡维护;同级先 `Bug` 再卡龄;无级/缺 `Path:` 轮报记分诊缺口。
54+
- 全序每级现读/现算,零逐卡维护;无级/缺 `Path:` 轮报记分诊缺口。
55+
- 同级先非车队卡(作者非 `Bot` 且首行非席位签名),再 `Bug`、卡龄;⛔ 不升级、不越 p0。
5656
- 一车道多席按全序交替取卡:席 1 奇位、席 2 偶位;只为破交替才发 `split`;认领戳仲裁。
5757
- 空闲杂务:无卡可认领或等分诊的车道席对本车道卡做下四件;⛔ 不定级,自分诊例外照旧。
5858
- ① 交付卫生:交付卡补收尾记录;离手摘 `pm:*` 同笔设下一态;`Blocked-by:` 全关即重验回队。

‎.claude/skills/pm-dispatch/references/lanes/director.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- 开场读一次服役档记入会话审计行,读法见 `../platform-readings.md`;⛔ 不因档位拒裁停三职。
2020
- 双射 carve-out:一张 `pm:seat` 座位贴,四段照旧;状态块逐场一行,各记己锚,⛔ 无占席人。
2121
- 不占 `domain:*` 车道,范围按职责标签跨车道跨仓;永不认领 backlog、永不写码。
22+
- 改 skill、指令或治理文本归 skills 席;出裁决、清复审标、清点待人工清单归本席。
2223
- 项目内卡线程报 `needs_decision` ⇒ 席位写四维分析加推荐,并入决裁批次呈维护者人工裁。
2324
- 维护者令「必须人工决裁」撤销「有把握自己决定」;⛔ 无裁决线程、代裁、置信门。
2425
- 本席任何形态都纯召唤制,⛔ 无任何 routine;分诊席默认同项目召唤,职责见 `triage.md`。
@@ -70,7 +71,10 @@
7071

7172
## 职责四:每日摘要、发版板与总表
7273

73-
- 每日摘要:在飞/待裁/待落地/撞墙、等复审逾 24h 的 PR、发版板、无卡 fork PR。
74+
- 每日摘要:在飞/待裁/待落地/撞墙、复审逾 24h 的 PR、发版板、无卡 fork PR、外部需求行。
75+
- 外部需求行三数现查并贴查询;非车队 = 作者 `user.type` 非 `Bot` 且正文首行非席位签名。
76+
- ① 窗内非车队开卡数;② 窗内关卡中非车队所开占比;③ open 无 `pm:*` 非车队卡逐张带龄。
77+
- 查法:①② 取 `since` 窗起全态列表,本地 `created_at`/`closed_at` 落窗才计;③ 取 open;皆排 PR。
7478
- 座位贴与在飞活性复查归本摘要,见 `../dispatch-runbook.md` 巡查常设项与 `../triage-duties.md`。
7579
- 发版板 `target:<major>` 主仓归本席;判据二元,⛔ 不做优先级渐变:不修它,RC 能不能发。
7680
- 判阻塞四类:① 用户今天就撞的已发布面缺陷;② 公开契约 declared≠enforced。
@@ -80,7 +84,3 @@
8084
- 见新 tag 族发布 ⇒ 在 cloud 与每个应用仓立平台版本 bump 单;单张封顶,已有则追评刷新。
8185
- bump 单等待卡清单 = hold 索引(`Restart-when:`/`Blocked-by:` 点名该发布的 open 卡),查询落正文。
8286
- 车队总表随本摘要重生成,坐席/接管/收班同笔;一席一行,⛔ 不手改;见 `../project-forms.md`。
83-
84-
## 与 skills 席的分界
85-
86-
- 改 skill、指令或治理文本归 skills 席;出裁决、清复审标、清点待人工清单归本席。

‎scripts/pm/check-half-states.mjs‎

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17541,14 +17541,19 @@ export function h66ReleasedNonDispatchableRemainder(issue, commentRows) {
1754117541
// force is OLDEST FIRST. The cards a seat will reach for next are exactly the
1754217542
// cards this row must have read. So the plan sorts by the two legs of that
1754317543
// order this file can see for free off the label page — the priority rank, then
17544-
// card AGE — and the cap eats the tail a seat reaches LAST.
17544+
// the AUTHOR (a human-opened card before a fleet-opened one), then card AGE —
17545+
// and the cap eats the tail a seat reaches LAST.
1754517546
//
1754617547
// ⛔ It models the full 取卡全序 only in part, and says so rather than implying
1754717548
// otherwise: `target:` board membership is a board read this file does not
17548-
// make, and the same-priority 「先 `Bug`」 tiebreak is not taken. An unreadable
17549-
// `created_at` sorts LAST rather than first (#4690's direction on every
17550-
// timestamp here), with `number` as a total-order tiebreak so two runs over one
17551-
// board agree.
17549+
// make, and the same-priority 「先 `Bug`」 tiebreak is not taken. The author leg
17550+
// IS taken: within one rank a non-fleet card sorts before a fleet card, and ⛔
17551+
// it never lifts a card out of its rank. A card is the fleet's when an App
17552+
// opened it (`user.type` `Bot`, every `[bot]` login) OR its body opens with a
17553+
// seat signature (`readSignatureLine`), since seat-operated human accounts file
17554+
// cards too. An unreadable author reads as the fleet's, ⛔ never promoted. An unreadable `created_at` sorts LAST rather than first (#4690's
17555+
// direction on every timestamp here), with `number` as a total-order tiebreak
17556+
// so two runs over one board agree.
1755217557
// ---------------------------------------------------------------------------
1755317558

1755417559
/**
@@ -17656,7 +17661,8 @@ export function h67SpeaksAbout(issue) {
1765617661

1765717662
/**
1765817663
* Which candidates this run buys a page for, and which it defers — OLDEST
17659-
* FIRST, by the two legs of 取卡全序 this file can read for free.
17664+
* FIRST, by the legs of 取卡全序 this file can read for free: rank, then a
17665+
* non-fleet card before a fleet one (App author or seat-signed body), then age.
1766017666
*
1766117667
* ⛔ The opposite of `h66BuyOrder`, deliberately, and the header carries the
1766217668
* argument: H66 chases a fresh write, this one chases the card a dispatching
@@ -17668,7 +17674,7 @@ export function h67SpeaksAbout(issue) {
1766817674
* what gets READ AT ALL is where a silent hole would live, and a plan the
1766917675
* self-test can drive offline is the only kind that can be pinned.
1767017676
*
17671-
* @param {{ number?: number, created_at?: string, labels?: object[] }[]} issues
17677+
* @param {{ number?: number, created_at?: string, labels?: object[], user?: object }[]} issues
1767217678
* @param {number} [cap]
1767317679
* @returns {{ read: object[], deferred: object[] }}
1767417680
*/
@@ -17679,13 +17685,23 @@ export function h67ReadOrder(issues, cap = H67_TIMELINE_READ_CAP) {
1767917685
const at = H67_SELECTION_ORDER_LABELS.findIndex((l) => labels.includes(l));
1768017686
return at === -1 ? H67_SELECTION_ORDER_LABELS.length : at;
1768117687
};
17688+
// Non-fleet before fleet, inside one rank only; a seat-signed body or an
17689+
// unreadable author reads as the fleet's.
17690+
const fleet = (issue) => {
17691+
if (readSignatureLine(issue?.body) !== null) return 1;
17692+
const author = artefactAuthor(issue);
17693+
return author && !author.isApp ? 0 : 1;
17694+
};
1768217695
const age = (issue) => {
1768317696
const parsed = Date.parse(issue?.created_at ?? '');
1768417697
return Number.isFinite(parsed) ? parsed : Infinity;
1768517698
};
1768617699
const ordered = [...rows].sort(
1768717700
(a, b) =>
17688-
rank(a) - rank(b) || age(a) - age(b) || Number(a?.number ?? 0) - Number(b?.number ?? 0),
17701+
rank(a) - rank(b) ||
17702+
fleet(a) - fleet(b) ||
17703+
age(a) - age(b) ||
17704+
Number(a?.number ?? 0) - Number(b?.number ?? 0),
1768917705
);
1769017706
const limit = Number.isFinite(cap) && cap > 0 ? Math.floor(cap) : 0;
1769117707
return { read: ordered.slice(0, limit), deferred: ordered.slice(limit) };
@@ -25234,8 +25250,9 @@ export const SELF_TEST_BATTERIES = Object.freeze({
2523425250
// `Part of` card must never read as a candidate for closing, and an OPEN PR
2523525251
// must stand the row down. A listing whose discriminators quietly stopped
2523625252
// being asserted would still print rows, and every one of them would read as
25237-
// 「close this」, which is the one thing the card forbids.
25238-
'H67 queued merged-delivery reading': 142,
25253+
// 「close this」, which is the one thing the card forbids. Pinned AT the count
25254+
// since the read order took the author leg, so the same-priority pair cannot go.
25255+
'H67 queued merged-delivery reading': 158,
2523925256
// Registered with the decorated ownership-marker reading (#18680); the pin
2524025257
// sits just under the count on its neighbours' grounds. What this battery
2524125258
// floors is a WIDENING of the two markers' READING, so the FIRING CONTROLS
@@ -36112,6 +36129,19 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
3611236129
b(BATTERY67, 'H67 order: `priority:p0` is reached FIRST, whatever its age', order67(CAND67, 4), '3,2,1,4');
3611336130
b(BATTERY67, 'H67 order: …then OLDEST-FIRST among equals, which is the order in force', order67(CAND67.filter((i) => i.number !== 3), 3), '2,1,4');
3611436131
b(BATTERY67, 'H67 order: ⛔ an unreadable `created_at` sorts LAST, ⛔ never promoted', order67([CAND67[3], CAND67[1]], 2), '2,4');
36132+
const human67 = { login: 'outside-user', type: 'User' };
36133+
const fleet67 = { login: 'objectstack-fleet[bot]', type: 'Bot' };
36134+
const PAIR67 = [
36135+
{ number: 21, created_at: '2026-08-01T00:00:00Z', labels: [{ name: 'priority:p2' }], user: fleet67 },
36136+
{ number: 22, created_at: '2026-09-20T00:00:00Z', labels: [{ name: 'priority:p2' }], user: human67 },
36137+
];
36138+
b(BATTERY67, '⭐ H67 order: within ONE priority a human-opened card is reached before an OLDER fleet-opened one', order67(PAIR67, 2), '22,21');
36139+
b(BATTERY67, 'H67 order: …and the author leg is the ONLY reason — two fleet cards of that priority stay oldest-first', order67([PAIR67[0], { ...PAIR67[1], user: fleet67 }], 2), '21,22');
36140+
b(BATTERY67, '⛔ H67 order: the author leg never lifts a card OUT of its priority, and never past p0', order67([{ ...PAIR67[1], labels: [{ name: 'priority:p3' }] }, PAIR67[0], { ...PAIR67[0], number: 23, labels: [{ name: 'priority:p0' }] }], 3), '23,21,22');
36141+
const SIGNED67 = 'domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J\n\nFiling gate ③ …';
36142+
b(BATTERY67, '⭐ H67 order: a HUMAN account whose body opens with a seat signature is the fleet\'s — it stays behind the older fleet card', order67([{ ...PAIR67[1], body: SIGNED67 }, PAIR67[0]], 2), '21,22');
36143+
b(BATTERY67, 'H67 order: …while the same human card with the signature off its first line is still reached first', order67([{ ...PAIR67[1], body: `A user report.\n\n${SIGNED67}` }, PAIR67[0]], 2), '22,21');
36144+
b(BATTERY67, 'H67 order: every App author is the fleet\'s (`github-actions[bot]` too), and an unreadable author ⛔ is never promoted', order67([{ ...PAIR67[1], user: { login: 'github-actions[bot]', type: 'Bot' } }, { ...PAIR67[1], number: 24, user: undefined }, PAIR67[0]], 3), '21,22,24');
3611536145
b(BATTERY67, 'H67 order: `pm:blocking` ranks above p1, as the selection order does', order67([{ number: 8, created_at: '2026-09-10T00:00:00Z', labels: [{ name: 'priority:p1' }] }, { number: 9, created_at: '2026-09-11T00:00:00Z', labels: [{ name: 'pm:blocking' }] }], 2), '9,8');
3611636146
b(BATTERY67, 'H67 order: ⭐ it is the OPPOSITE of H66\'s, and that is the point', order67(CAND67.filter((i) => i.number !== 3 && i.number !== 4), 2) === h66BuyOrder(CAND67.filter((i) => i.number !== 3 && i.number !== 4).map((i) => ({ ...i, updated_at: i.created_at })), 2).buy.map((i) => i.number).join(','), false);
3611736147
b(BATTERY67, 'H67 cap: the rest are DEFERRED, ⛔ not dropped silently', h67ReadOrder(CAND67, 2).deferred.map((i) => i.number).join(','), '1,4');

0 commit comments

Comments
 (0)