Repository navigation
Commit d63f24a
pm-dispatch(mailbox): one mailbox PR per seat with a closed doorbell vocabulary; seat-online/offline notices; alternate pick for multi-seat lanes (#27143)
Fixes #27077
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:信箱 PR 从「每项目一个」改成「每席一个」(`pm:mailbox`+`pinned`,正文首行 `To:
LOGIN`),信箱上只允许七种一行式门铃(3 种 Request、4 种 Notify),配四条防刷屏硬规则;坐席发
seat-online、收班发 seat-offline;多席车道按优先序交替取卡;`record-recognisers.mjs`
能读这七键,`mailbox-open.mjs` 开信箱时带 `pinned` 并把 `To:` 写在正文首行。
- 为什么改:只有 PR 评论能唤醒订阅的会话,issue 上写什么都没人醒;之前跨席请求全靠对方下次巡查来「拉」(PR #26766
的契约复审等了 9 小时)。改成每席一个信箱后,席位之间可以互相按门铃「推」,而且铃声只带七种键与编号,不会演变成 AI 互聊。
- 风险与回滚:纯协议文本与两个 PM 脚本,不碰产品包、门禁、workflow、队列守卫;回滚 = revert 本 PR。已测得
`pinned` 标签在本仓并不存在(404),首次开信箱时需要看标签是否落上(见 Acceptance notes)。
- 先看:① `references/project-forms.md` 〈信箱 PR〉(七键与四规则,每行 ≤120 字节);②
`scripts/pm/record-recognisers.mjs` 的 `readMailboxLine`(拒收面:表外键、第二行、超
200 字节);③ `scripts/pm/mailbox-open.mjs` 的 `PR_LABELS` 与 `mailboxBody`。
- 两分钟验证:`node scripts/pm/record-recognisers.mjs --self-test` 预期末行 `✓
record-recognisers self-test: 254 cases pass (…)`;`pnpm
check:pm-skill-ratchet` 预期四个受管文件各自 `headroom 0` 且绿。
- 需要你做的:无,席位落地(Tier S:席位契约复核 PASS 后入队)。
## What changes
One PR, six files, the four ruled sections A–E of the card:
- **A. One mailbox PR per seat** — `references/project-forms.md` 〈信箱
PR〉: one draft PR per seat on `mailbox/LOGIN`, labels `pm:mailbox` +
`pinned`, body first line `To: LOGIN`, opened at seating by
`mailbox-open.mjs` and subscribed by the seated session, the seat post's
stub pointer line carries its number, never merged, closed at hand-over
or account switch with the successor opening a new one.
- **B. The closed doorbell vocabulary** — the same section: the first
line of a mailbox comment is one of the seven keys (three `Request:`,
four `Notify:`), each key one line with its from → to and when; the four
hard rules: one event one line ≤200 bytes keys-and-numbers only,
anything outside the table is a half-state; no acknowledgements (the
action is the receipt); no re-send and no nagging (the existing
escalation rules take over); ≤5 per seat per round, p0 exempt, every
send counts against the write pace; a mailbox comment is a colleague's
statement, never an authorisation.
- **C. Alternate pick for multi-seat lanes** —
`references/execution-duties.md` 〈候选与批次〉, one line beside the take
order: seat 1 takes the odd positions, seat 2 the even ones over the
lane's total order; `split` is sent only to break the alternation; the
claim timestamp stays the tie-breaker. Hot-file serial is untouched (its
rule stands at L26 of the same section).
- **D. Online / offline** — `references/seat-lifecycle.md` 〈座位贴协议〉, two
lines: seating sends `seat-online`, the 收班简报 act sends `seat-offline`; a
forced takeover's newcomer sends `seat-online` for the walled account
(which can write nothing); a Routine seat cannot be woken, so its patrol
reads the mailbox (the card's P4, said in the text). `SKILL.md` L74 is
corrected to the maintainer's form: seats including triage are resident;
an account out of tokens is replaced by one that reopens the seat and
its mailbox; the triage seat's default tier and 「裁决归总监席」 stay.
- **E. Recogniser** — `scripts/pm/record-recognisers.mjs`:
`MAILBOX_KEYS` (the seven templates, spelled exactly as the protocol
text prints them), `readMailboxLine(body)` (pure: the first line in, a
`read` / `refused` / `unreadable` reading out — kind, key, numbers, head
sha, login, lists) and `mailboxEvents(rows)` (each comment stamped with
its id, `created_at` and author, plus the half-states by comment id).
Exported beside `contractReviewHandoff`; no CLI flag; nothing fetched. ⛔
No gate, workflow or queue guard reads a mailbox — the battery pins that
a doorbell comment beside a `## Contract review` record leaves the
locator's reading untouched.
- **Tooling** — `scripts/pm/mailbox-open.mjs`: sends `pinned` with
`pm:mailbox` and `skip-changeset`; writes `To: LOGIN` as the body's
first line (the protocol text has said `正文首行 To:` since the mailbox was
born, while the tool wrote `Mailbox-of:` — one spelling now, the ruled
one) and reads it back for refusal ①; the body names the seven-key rule;
the close comment says hand-over or account switch. Refusal ① (one
mailbox per login per repo) is unchanged: measured on the seat posts, a
login holds one seat at a time and a lane's second seat is a second
login, so per-login already equals per-seat — the card's open question
answered by measurement, no seat name needed.
- **Companion registration (outside the claim's declared file surface —
the gate's own remedy, named here as the bounded in-place rule
requires)** — the doorbell battery reads `project-forms.md` to pin the
templates, which is a population claim on a governed file, and
`check:pm-dispatch-gates` reds a new governed read until it is
classified. Three mechanical edits, each the shape the gate prints: the
`self-test-reads` declaration line in `record-recognisers.mjs` (line
77); the `declared: true` row for it in `GOVERNED_READ_FLOOR`
(`scripts/pm/dispatch-gates.data.mjs`, with a docblock paragraph); and
the ninth line of the live-declaration roster in
`scripts/pm/dispatch-gates.self-test.mjs` (eight → nine). Measured both
ways: before, `check:pm-dispatch-gates` failed 2 of 1935 (`unpinned:
record-recognisers.mjs::project-forms.md` and
`check:pm-record-recognisers … (silent)`); after, 1935 pass, and
`dispatch-gates --commands … project-forms.md` alone derives `pnpm
check:pm-record-recognisers`, keyed `declared self-test read by
scripts/pm/record-recognisers.mjs`. No open PR touches either
dispatch-gates file (file scan of the 32 open PRs, 2026-10-28).
Angle-bracket placeholders are spelled `LOGIN` / `N` in this body (the
platform strips tag-shaped fragments); the files keep the corpus's
`mailbox/<login>` spelling.
## Line budget (every ceilinged file stays at its ceiling;
`check:pm-skill-ratchet` green, headroom 0 on all four)
| file | before | after | ceiling |
|:--|--:|--:|--:|
| `.claude/skills/pm-dispatch/SKILL.md` | 319 | 319 | 319 |
| `.claude/skills/pm-dispatch/references/project-forms.md` | 54 | 54 |
54 |
| `.claude/skills/pm-dispatch/references/seat-lifecycle.md` | 96 | 96 |
96 |
| `.claude/skills/pm-dispatch/references/execution-duties.md` | 188 |
188 | 188 |
Every new line is paid by deleting a line whose content has a home
elsewhere — never by re-wrap. Prose lines are ≤120 bytes (`wc -c`),
measured per line below.
**project-forms.md** — 〈信箱 PR〉 grows from 6 lines (heading, blank, 4
bullets) to 16 (heading, blank, 14 bullets): +10, paid by 10 deletions
elsewhere in the file. The three old bullets of the section are
rewritten in place (their facts survive in the new lines); the fourth
(`pm:mailbox PR 不欠 CI…`, 67 B) is kept verbatim.
| deleted line (bytes) | home of its content |
|:--|:--|
| L9 `车道席:认领、派发、PR、跨账号复审;总监席:决策箱与决裁批、发版、日报、总表。` (112 B) | SKILL.md 入口与角色
table (lane and director rows) + `lanes/director.md` 职责一–四 headings |
| L10 `分诊席:只写标签与审计评论;分诊与总监可召进任一项目,⛔ 不绑定项目。` (99 B) | `lanes/triage.md`
L4 (「由维护者一句召唤到任一项目…只写标签与审计评论」) + SKILL.md L55 (director: 任一车队项目可召) |
| L11 `唯一不变量在账号不在项目:批准账号永不跑席位(SKILL.md 全体座位的不变量);本文不另加。` (120 B) |
SKILL.md 全体座位的不变量 L93 (「批准账号永不跑席位或作其关联用户」) — a self-declared restatement
|
| L26 `一张也没有 ⇒ 项目闲置;每席恰一条席位线程、恰一车道,一席一贴。` (93 B) | same file L30
(每席开一条席位线程) + `seat-post-protocol.md` L9 (一席一贴) + `coordinator.md` §一
(每条车道开一个席位线程) |
| L29 `协调者每次开场把指令行与座位贴对照:逐席按 seat-post-protocol.md 坐席或接管。` (106 B) |
`coordinator.md` §一 L10–L13 (lanes declared by the instruction line,
seat the empty post, takeover per project-forms) |
| L32 `总监席只被维护者召唤与 PR 订阅唤醒;摘要与收件箱扫描在召唤内做;见 lanes/director.md。` (118 B) |
`lanes/director.md` 授权与形态 (L7 人工召唤, L24 纯召唤制 ⛔ 无任何 routine, L12 入口);
same file L31 keeps 「总监席不建」 |
| L33 `并发上限是维护者在该项目定的姿态;⛔ 不是协议常量,本文不写数字。` (95 B) | `coordinator.md` §三
L23 (卡线程上限全项目一个数,由维护者按项目言明) |
| L38 `撞墙账号写不出任何东西,接管永不等它的注记或简报。` (75 B) | `seat-lifecycle.md` L29
(强制接管令…⛔ 不等原认领者注记) + `execution-duties.md` L83 (认领人不可达 ⇒ 接管…⛔ 不判死活); the
section heading 「接管不等撞墙账号」 keeps the thesis |
| L39 `跨账号 Release: 点名旧会话与出处三件;新 Claim: 带 Resume-from: BRANCH@SHA,新 dev
续旧分支。` (120 B) | `execution-duties.md` 认领 ①–④ (L84–L86), which L40's
pointer 「四件形状见 execution-duties.md 认领节」 already names |
| L54 `读自座位贴、标签、assignee 与 PR 查询;生成视图 ⛔ 不手改(cross-repo-coordination.md
规则 5)。` (120 B) | `lanes/director.md` L82 (车队总表随本摘要重生成…一席一行,⛔ 不手改) +
SKILL.md L92 (状态只经标签、assignee、正文行与座位贴读写) |
New 〈信箱 PR〉 lines, bytes: 107 · 115 · (67 kept) · 115 · 119 · 118 · 120
· 119 · 118 · 116 · 119 · 115 · 118 · 118. The seven key lines carry the
card's spellings verbatim inside the code span; the from → to and when
after the colon are compressed to fit the cap, and the seat-online /
seat-offline rows carry only from → to because their when lives in
seat-lifecycle.md (section D).
**seat-lifecycle.md** — +2 (115 B, 101 B) after the 收班 line of 〈座位贴协议〉,
paid by: L47 `接管/移交 = 改正文 + 审计评论(只作审计);收尾清单等细则见
references/seat-post-protocol.md。` (119 B) → home
`seat-post-protocol.md` L27 and L33 (回收 / 当场 = 改正文 + 审计评论) and its
交接收尾清单 section; L48 `epic 委托不入座位贴体系;新 packages/spec 工作恒归 spec 座位。` (82
B) → home same file L61 (新立 spec/公共契约 sub-issue 照旧转 spec 座位队列) +
`cross-repo-coordination.md` (协议恒归 domain:spec).
**execution-duties.md** — +1 (115 B) after the 全序 lines of 〈候选与批次〉, paid
by L37 `文件面不相交只保证文本可合并;跨文件语义耦合由队列 CI 逮住,⛔ 不读作不可能冲突。` (117 B) → home
AGENTS.md Multi-agent discipline §10 (「A clean merge is not a working
merge … that second CI round is where joint breakage surfaces」).
**SKILL.md** — L74 replaced in place, 114 B → 116 B:
`分诊席跑直连会话、默认判断档;直连会话按召唤跑,⛔ 无自醒不常驻;裁决归总监席。` →
`分诊席与车道席常驻,断粮即换账号重开该席与信箱;分诊席默认判断档;裁决归总监席。`. The line sits outside the
regions `check:pm-governed-prose` (路径分叉) and `check:skill-frame-sync`
(升级与决策) anchor; both green.
## Tests
- `node scripts/pm/record-recognisers.mjs --self-test` → exit 0, `✓
record-recognisers self-test: 254 cases pass` (233 before + the new
battery of 21; `SELF_TEST_BATTERY_FLOOR` 13 → 14). The battery reads one
example of each of the seven keys (kind, numbers, head sha, login,
lists, verdict), pins every template verbatim in `project-forms.md` and
that the text lists exactly seven, tolerates the platform's appended
footer and key decoration, and refuses: free text, an acknowledgement
(`收到,谢谢!`), an empty comment, an eighth key of either kind, a lowercase
kind, a line over 200 bytes, a second line of content, and a known key
whose line does not spell its template (no head, 6-hex head, bad login,
missing mailbox, missing lane, non-numeric record). `mailboxEvents`
carries comment id / time / author and lists the half-states by comment
id.
- `node scripts/pm/mailbox-open.mjs --self-test` → exit 0, `✓
mailbox-open self-test: 45 cases in 8 batteries pass` (two new pins:
`pinned` is read off `stale.yml`'s `exempt-pr-labels` and `pm:mailbox`
is not in it; the body opens with `To:` and names the closed vocabulary;
the open-plan battery floor 8 → 9).
- `node scripts/pm/check-governed-queue-guard.mjs --self-test` → exit 0,
401 cases (it loads the recogniser through the real lazy import).
- No ablation: no tested product behaviour changed (scripts and protocol
text — ⛔ 不消融). No package build or `pnpm test` owed: the diff touches no
package (`turbo ls` has no root package, so this is 「不在图上」, not 「动不了」).
## Acceptance notes
- **`pinned` does not exist as a label on objectstack-ai/objectstack** —
`GET /repos/objectstack-ai/objectstack/labels/pinned` → 404 on
2026-10-28, and so does `work-in-progress`; of `stale.yml`'s three
`exempt-pr-labels` only `security` exists (200). The text makes `pinned`
mandatory and the tool sends it, as ruled; whether the relay's labels
leg (a POST to the pull's issue-labels path) creates a missing label is
a platform reading the first mailbox open takes, not one this PR asserts
(a write). `scripts/pm/ensure-pm-labels.sh` is where a label the fleet
relies on is created — out of this card's file surface, left for the
seat (carrier: seat). Dedupe words: pinned, exempt-pr-labels, stale.yml,
work-in-progress.
- **The half-state patrol has no mailbox row.** The text says 「表外即半态」
(as ruled); `scripts/pm/check-half-states.mjs` has no H-row reading a
mailbox, and E forbids a gate reading one. The reading a patrol takes is
`mailboxEvents(rows).halfStates`; wiring it into the report-only patrol
is a follow-up, not a merge condition.
- **`To:` vs `Mailbox-of:`** — the tool's `Mailbox-of:` body line is
retired for the protocol's `To:` spelling: 0 `pm:mailbox` PRs are open
(measured 2026-10-28), so there is no stored instance to migrate and no
dual spelling to keep (创业阶段不渐进).
- **PR #27082 and #26749** both edit `SKILL.md` (L161/L162/L183 and
L166) — region-disjoint from L74; neither had landed at `07d18e69` (this
branch merged `origin/main` at that sha before opening; the merge
brought nothing on this file surface and did not move the lockfile).
- Not filed, noted: the routine line `首次坐席自建本项目的 routine…`
(project-forms L31) was kept because its 「分诊席加每小时裸卡轮」 clause has no
other home; the 总表 section pays only one line because its `objectstack
置顶 issue「车队总表」` fact has no other home.
## Verification
Local subset = the derivation `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` on the final head (no
paths passed; change set from the merge base `07d18e69`: 8 paths, 0
uncommitted), 41 families, every command run in the foreground with its
exit captured before any pipe and reconciled with `--ran` (ran-file
lines carry ` :: exit N`). Every family CI runs rides the PR's
check-runs (`Lint & Repo Gates` carries the `check:*` set); they are not
re-run here beyond that subset and read `in_progress` at report time.
Head `d82ca533`. `--ran` verdict: `✓ dispatch-gates --ran: 41 derived
famil(ies) accounted for — 40 run, 1 NOT-MEASURED (0 DERIVED from a
recorded exit 3, 1 claimed).`.
<details><summary>41 derived families on head d82ca53 — command · exit
(captured before any pipe) · wall</summary>
| command | exit | wall |
|:--|--:|--:|
| `node scripts/check-closing-keyword-parity.mjs` | 0 | 3s |
| `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | 7s |
| `node scripts/check-comment-mask-corpus.mjs` | 0 | 200s |
| `node scripts/check-declaration-mirrors.mjs` | 0 | 0s |
| `node scripts/check-declaration-mirrors.mjs --self-test` | 0 | 0s |
| `node scripts/check-self-test-wired.mjs` | 0 | 2s |
| `node scripts/check-self-test-wired.mjs --self-test` | 0 | 5s |
| `node scripts/check-self-test-workflow-commands.mjs` | 0 | 158s |
| `node scripts/check-self-test-workflow-commands.mjs --self-test` | 0 |
4s |
| `node scripts/check-whole-set-label-write.mjs` | 0 | 2s |
| `node scripts/check-whole-set-label-write.mjs --self-test` | 0 | 0s |
| `node scripts/ci/plan-test-core-shards.mjs --self-test` | 0 | 0s |
| `node scripts/pm/bare-root-worklist.mjs --self-test` | 0 | 33s |
| `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0 | 1s
|
| `node scripts/pm/check-harness-current.mjs --self-test` | 0 | 7s |
| `pnpm --filter @objectstack/lint run check:doc-formula-expressions` |
3 (PREREQUISITE NOT MET: `@objectstack/lint`/`formula` unbuilt — nothing
measured, NOT MEASURED; CI builds them) | 1s |
| `pnpm check:agent-test-spelling` | 0 | 4s |
| `pnpm check:bash32-floor` | 0 | 2s |
| `pnpm check:ci-filter-parity` | 0 | 2s |
| `pnpm check:cli-command-ids` | 0 | 17s |
| `pnpm check:cross-package-test-inputs` | 0 | 40s |
| `pnpm check:doc-authoring` | 0 | 7s |
| `pnpm check:driver-memory-census` | 0 | 6s |
| `pnpm check:entry-guard` | 0 | 45s |
| `pnpm check:gitlink-declared` | 0 | 1s |
| `pnpm check:nul-bytes` | 0 | 5s |
| `pnpm check:parse-guard` | 0 | 3s |
| `pnpm check:pm-dispatch-gates` | 0 | 416s |
| `pnpm check:pm-expected-skips` | 0 | 6s |
| `pnpm check:pm-governed-merges` | 0 | 10s |
| `pnpm check:pm-governed-prose` | 0 | 1s |
| `pnpm check:pm-record-recognisers` | 0 | 5s |
| `pnpm check:pm-skill-id-lint` | 0 | 1s |
| `pnpm check:pm-skill-ratchet` | 0 | 1s |
| `pnpm check:pnpm-filter-targets` | 0 | 3s |
| `pnpm check:pnpm-flag-spellings` | 0 | 7s |
| `pnpm check:ratchet-remedy-authority` | 0 | 5s |
| `pnpm check:refd-timer-probe` | 0 | 25s |
| `pnpm check:scripts-symbol-anchors` | 0 | 9s |
| `pnpm check:skill-frame-sync` | 0 | 1s |
| `pnpm check:watch-hint-literal` | 0 | 7s |
</details>
Local-only readings (CI runs their `--self-test` only): `node
scripts/check-skill-frame-freshness.mjs --no-fetch` → exit 0 (`0/1
framework files are byte-identical to the ref (wording differences are
not drift)` — the frame itself is unchanged; L74 is outside it). `node
scripts/pm/mailbox-open.mjs --self-test` → exit 0, 45 cases (not a
derived family: no `check:` script names it; run because the diff edits
it). `node scripts/pm/check-governed-queue-guard.mjs --self-test` → exit
0, 401 cases.
No package is touched, so there is no build closure (①), no package test
(②) and no changeset: `.claude/**` and `scripts/pm/**` publish nothing
(`skip-changeset` declared on the create). No dogfood: no `!` in the
title and no `(narrowing)` arm.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent fbe344b commit d63f24a
8 files changed
Lines changed: 331 additions & 46 deletions
File tree
- .claude/skills/pm-dispatch
- references
- scripts/pm
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
38 | 37 | | |
39 | 38 | | |
40 | 39 | | |
| |||
51 | 50 | | |
52 | 51 | | |
53 | 52 | | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | 9 | | |
13 | 10 | | |
14 | 11 | | |
| |||
23 | 20 | | |
24 | 21 | | |
25 | 22 | | |
26 | | - | |
27 | 23 | | |
28 | 24 | | |
29 | | - | |
30 | 25 | | |
31 | 26 | | |
32 | | - | |
33 | | - | |
34 | 27 | | |
35 | 28 | | |
36 | 29 | | |
37 | 30 | | |
38 | | - | |
39 | | - | |
40 | 31 | | |
41 | 32 | | |
42 | 33 | | |
43 | 34 | | |
44 | 35 | | |
45 | | - | |
46 | | - | |
47 | | - | |
| 36 | + | |
| 37 | + | |
48 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
54 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
47 | | - | |
48 | | - | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
499 | 499 | | |
500 | 500 | | |
501 | 501 | | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
502 | 511 | | |
503 | 512 | | |
504 | 513 | | |
| |||
517 | 526 | | |
518 | 527 | | |
519 | 528 | | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
520 | 534 | | |
521 | 535 | | |
522 | 536 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7535 | 7535 | | |
7536 | 7536 | | |
7537 | 7537 | | |
7538 | | - | |
| 7538 | + | |
7539 | 7539 | | |
7540 | 7540 | | |
7541 | 7541 | | |
| |||
7557 | 7557 | | |
7558 | 7558 | | |
7559 | 7559 | | |
| 7560 | + | |
| 7561 | + | |
| 7562 | + | |
| 7563 | + | |
| 7564 | + | |
7560 | 7565 | | |
7561 | 7566 | | |
7562 | 7567 | | |
7563 | 7568 | | |
7564 | 7569 | | |
7565 | 7570 | | |
7566 | | - | |
| 7571 | + | |
7567 | 7572 | | |
7568 | 7573 | | |
7569 | 7574 | | |
| |||
0 commit comments