Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/agents/os-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ model: opus
- 同批 agents 共用一个 scratchpad 目录,自然命名的文件会被彼此静默覆盖。
2. **卡 assignee 归 PM。** 派发原子对已把它设好;共享身份下该字段答不了是谁。
- 你的身份位是认领评论里的分支;仓 CLAUDE.md 的 claim-first 已由 PM 的认领满足。
- 恒不写卡的 assignee(空也开工,报 `summary`);PR ⛔ 不带 `assignees`(中继虽收),席位交棒才派。
- 恒不写卡的 assignee(空也开工,报 `summary`);PR 派认领 `Account:`(`pr_create` 带),交棒归席位。
- 发现与他人在途工作重复,停下报 `blocked`。
- PR 上不是你设置的状态属于另一个 actor:⛔ 永不去纠正;疑问进报告,挡住报 blocked。
- 回退他人的操作(尤其 ready 翻转)永不轮到你;把意外写进 `summary`。
Expand All @@ -60,7 +60,7 @@ model: opus
- 写工具自读所需变量;⛔ 永不打印环境变量的值,转录是 AGENTS.md 禁落令牌的日志行。
- 拼法不限于 `env`、`printenv`、`set`、`export -p`、`echo $VAR`、`cat /proc/*/environ`。
- 判存在只用不印值形式 `[ -n "${X+x}" ] && echo set`;已印出 ⇒ `deviations` 只点名变量名。
- 写预算三笔:`git push`、`pr_create`(draft,含 labels、⛔ 无 assignees)、`os-dev-report` 评论。
- 写预算三笔:`git push`、`pr_create`(draft,含 labels 与 assignees)、`os-dev-report` 评论。
- 卡与线程只走 payload 档(公开仓单卡网页内嵌 JSON,拼写住 platform-readings)或单卡 REST 读。
- 三类发现进报告交席位代立,不 `POST /issues`、⛔ 不静默弃报;PM 去重读数当既有事实用。
- PR 正文 dev 只写一次,在开 PR 那一笔,⛔ 不 `PATCH`;事后要改的报告点名改法,席位代写。
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/pm-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
- 批准判定单源 = 队列守卫常量 `GOVERNED_APPROVERS`:授权账户 APPROVED 即算,⛔ 不卡 `commit_id`。
- 批准后再推亦不过期;席位落地 = 过落地前检、清标、ready、auto-merge,踢出/变基同法。
- ③ 请审 = 席位交棒:ACCEPT 与现 head 同形 PASS 俱在案,同轮中继 `pr_request_reviewers` 请审批人。
- 作者是审批人 ⇒ `assign` 它、请其余(请它必 422);⛔ 永不派/请席位自身账号;Tier S 不交人。
- `pr_create` 不请不派;`pr_ready` 只补交棒漏;非例外 head 移 ⇒ 撤挂回等;脱中继 PR 自读请/派。
- 作者是审批人 ⇒ `assign` 它、请其余(请它必 422);⛔ 永不请席位自身账号;Tier S 不交人。
- `pr_create` 派不请;`pr_ready` 只补交棒漏;非例外 head 移 ⇒ 撤挂回等;脱中继 PR 自读请/派。
- 速读「需要你做的」求批点名 PASS 评论 id+`Head-sha`;无 PASS ⇒ 写 contract review owed 不求批。
- ④ 轮报单列 awaiting a human merge 并现读请审人;⛔ 未请审不在 chat 报「等你批」,先补请。
- skills 车道自有 PR:纯代码面如 `scripts/pm/` 由本席按达档自审(清单不减)后落地。
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/dispatch-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
- 席位留认领文本/派发令/契约复核/ACCEPT/落地;线程实施、开 draft PR、驱动 CI 与可合并。
- 线程 ⛔ 不做 harness 开 PR 后指派/请审:「审核通过才会推送给我审核」,请审是席位交棒。
- 派发令由 coordinator 写进线程开场消息,`Writes:` 行逐字在内,⛔ 不落共享目录;卡上留指针。
- coordinator 开线程即读线程分支交席位(读不到则线程自报);⛔ 线程不认领、不写 assignee。
- coordinator 开线程即读线程分支交席位(读不到则线程自报);⛔ 线程不认领、不写卡 assignee。
- 席位写 assignee+`pm:dispatched`+带该 `Branch:` 的认领;线程核对最新认领指向本分支方开工。
- 分支约定仍是 `claude/issue-<n>-<slug>`;线程只能推指定分支是实测平台例外,认领如实写。
- worktree 照建;仓 `.claude/agents` 类型未加载(实测),线程照读 `os-dev.md`。
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/execution-duties.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
## 认领(先认领后动工)

- 归属 = session ID,⛔ 不认作者字段;`Account:` 派生自 assignee,不等即半态、持卡席下笔补正。
- 卡 assignee 归 PM,dev 恒不写;PR ⛔ 不派席位账号;在飞 PR 读 `head:claude/` 开 PR 正文点名的卡。
- 卡 assignee 归 PM,dev 恒不写;PR 派认领账号,交棒同笔改;在飞 PR = `head:claude/` 正文点名本卡。
- 部分落地(PR 带 `Refs #N (item k)`,⛔ 不 `Fixes`)即释放:合入同笔回 `pm:queue` + 清 assignee。
- `Release:` 行点名已落与余项;余项整落 `ui/**` 外他席树 ⇒ 点名其改标,拆分加 `pm:retriage`。
- 派发前按序执行原子对:① Assign @me,并把 `pm:dispatched` 与摘 `pm:queue` 放进同一次标签写入。
Expand Down
86 changes: 86 additions & 0 deletions scripts/pm/check-half-states.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15720,6 +15720,48 @@ export function h72OffTableMailboxLines(reading, nowMs = Date.now()) {
);
}

// ---------------------------------------------------------------------------
// H76 — an OPEN PR with NO assignee, a `pm:mailbox` PR excepted. Ruling record
// 6674390671 (letter A): a PR names its owner — the dev's `pr_create` carries
// the claim's `Account:` as its assignee, and a hand-over (sibling takeover,
// release, reassignment) re-assigns the PR in the same stroke as the claim
// change (`references/execution-duties.md` 认领). The same ruling makes this
// row report-only, ⛔ no gate. A mailbox is a seat's doorbell, never a delivery
// (`project-forms.md` 〈信箱 PR〉), so it is exempt; its label is
// `mailbox-open.mjs`'s MAILBOX_LABEL, handed in by the sweep, never spelled
// here. When the sweep could not load that label the row cannot tell a mailbox
// from a delivery, so it fires UNJUDGED, never clean (#4690). Drafts are
// INCLUDED: a dev's PR stays a draft until its seat lands it. Free — it reads
// the open listing already in hand.
// ---------------------------------------------------------------------------

/**
* H76 — null when clean or out of scope, else the finding sentence.
*
* @param {object} pr — a `/pulls?state=open` list row.
* @param {string} mailboxLabel — `mailbox-open.mjs`'s MAILBOX_LABEL, handed in by the sweep; anything else is unread.
*/
export function h76OpenPrWithoutAssignee(pr, mailboxLabel) {
if (!pr || pr.state === 'closed') return null;
if ((pr.assignees ?? []).length > 0 || pr.assignee) return null;
if (typeof mailboxLabel !== 'string' || mailboxLabel === '') {
return (
`${UNJUDGED_MARKER} open PR with NO assignee, and the mailbox label could not be read this sweep — ` +
'whether it is an exempt mailbox PR is UNJUDGED, not settled. Read its labels by hand: a mailbox is ' +
'exempt, any other open PR owes its owner as assignee.'
);
}
if (labelNames(pr).includes(mailboxLabel)) return null;
return (
'open PR with NO assignee. A PR names its owner (ruling record 6674390671): the dev\'s `pr_create` carries ' +
'the claim\'s `Account:`, and a hand-over (sibling takeover, release, reassignment) re-assigns the PR in the ' +
'same stroke as the claim change. Remedy — WHO and HOW: the seat holding the card this PR names assigns that ' +
'card\'s assignee through the relay (`assign` op, the PR number as `issue`); a bot PR with no card takes its ' +
'owning lane seat\'s account, or stays unassigned with a note. ⛔ Report-only: nothing is written from this ' +
`script. Out of scope: a \`${mailboxLabel}\` PR (a seat's doorbell, never a delivery).`
);
}

// ---------------------------------------------------------------------------
// H64 (#18069, re-keyed by #18237) — a seat- or dev-signed artefact that names
// no session.
Expand Down Expand Up @@ -19910,6 +19952,11 @@ export const HALF_STATE_FAMILY_BAND = Object.freeze({
// is stopped (not `stall`), and no write on the board repairs a sent comment
// (not `state`) — the repair is the next bell, spelled from the table.
H72: 'inventory',

// H76 is a `state`, H1's band for H1's reason: a live PR half-written — the
// owner the ruling makes it name is missing, and the repair is one relay
// `assign` on the board. ⛔ Not `stall`: an unowned PR can still land.
H76: 'state',
});

const FAMILY_BAND_RANK = new Map(HALF_STATE_FAMILY_BANDS.map((b) => [b.name, b.rank]));
Expand Down Expand Up @@ -23797,6 +23844,22 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, seen
}
}

// H76 — an open PR with no assignee, off the listing above, free. The mailbox
// label is imported LAZILY for H72's reason (banner above `h72NeedsThread`);
// a failed import leaves it unread, and every unowned PR then fires UNJUDGED.
{
let mailboxLabel = null;
try {
({ MAILBOX_LABEL: mailboxLabel } = await import('./mailbox-open.mjs'));
} catch {
mailboxLabel = null;
}
for (const pr of seenPrs.values()) {
const unowned = h76OpenPrWithoutAssignee(pr, mailboxLabel);
if (unowned) findings.push([pr, 'H76', unowned]);
}
}

// H16 — the only predicate here whose input no listing carries:
// `mergeable_state` lives on the single-PR endpoint alone. One GET per
// CANDIDATE (the gathering policy is `h16NeedsDetail`, which answers from
Expand Down Expand Up @@ -25309,6 +25372,9 @@ export const SELF_TEST_BATTERIES = Object.freeze({
// through this predicate — lives in record-recognisers.mjs's doorbell battery,
// because this self-test cannot import that module (H62's banner).
'H72 mailbox off-table bells': 20,
// Registered with the unowned-PR row, pin just under the count: the firing, the assignee/mailbox/closed
// exemptions, the unread-label UNJUDGED leg, the text, the band and the wiring.
'H76 unowned open PR': 11,
// Registered with the ruled-card-in-the-box row (#29386), pin just under the
// count: the firing case, the label / fence / anchoring / id controls each
// beside its positive twin, the threshold boundary, the governing stamp, the
Expand Down Expand Up @@ -28656,6 +28722,26 @@ async function selfTest() {
b(BATTERY28675, '⭐ H72: the sweep takes the ONE reader and the opener\'s label LAZILY — `mailboxEvents` from record-recognisers.mjs, MAILBOX_LABEL from mailbox-open.mjs — and spells no doorbell key of its own', (() => { const src = readFileSync(fileURLToPath(import.meta.url), 'utf8'); return src.includes("import('./record-recognisers.mjs')") && src.includes("import('./mailbox-open.mjs')") && !/^import [^\n]*from '\.\/(?:record-recognisers|mailbox-open)\.mjs'/mu.test(src) && !src.includes(['MAILBOX_KEYS', '='].join(' ')); })(), true);
b(BATTERY28675, 'floor: this battery is DECLARED on the roster, with a positive pin', Object.prototype.hasOwnProperty.call(SELF_TEST_BATTERIES, BATTERY28675) && SELF_TEST_BATTERIES[BATTERY28675] > 0, true);

// -- H76: an open PR with no assignee (ruling record 6674390671, letter A). The fixture is a relay-opened dev
// draft as the card measured them (26 of 27 open PRs unassigned), offline, never a live read.
const BATTERY76 = 'H76 unowned open PR';
const MAILBOX76 = 'pm:mailbox';
const pr76 = (extra = {}) => ({ number: 30778, state: 'open', draft: true, assignees: [], assignee: null, labels: [{ name: 'skip-changeset' }], user: { login: 'objectstack-fleet[bot]', type: 'Bot' }, ...extra });
const row76 = String(h76OpenPrWithoutAssignee(pr76(), MAILBOX76) ?? '');
b(BATTERY76, 'H76 fires: an open dev draft with no assignee and no mailbox label', row76.length > 0 && !isUnjudgedFinding(row76), true);
b(BATTERY76, 'H76 row: names the ruling, the claim\'s `Account:`, the relay `assign` op, the bot-PR arm and the report-only boundary', ['ruling record 6674390671', 'the claim\'s `Account:`', '`assign` op', 'owning lane seat\'s account', 'Report-only'].every((x) => row76.includes(x)), true);
b(BATTERY76, 'H76: a ready (non-draft) PR fires exactly like a draft — drafts are included, not the only population', typeof h76OpenPrWithoutAssignee(pr76({ draft: false }), MAILBOX76), 'string');
b(BATTERY76, '⛔ H76: a PR with an assignee is clean — the list field or the legacy single field', [h76OpenPrWithoutAssignee(pr76({ assignees: [{ login: 'os-elon-musk' }] }), MAILBOX76), h76OpenPrWithoutAssignee(pr76({ assignee: { login: 'os-elon-musk' } }), MAILBOX76)].join(), ',');
b(BATTERY76, '⛔ H76: an unassigned `pm:mailbox` PR is exempt, and its unlabelled twin is not', [h76OpenPrWithoutAssignee(pr76({ labels: [{ name: MAILBOX76 }, { name: 'pinned' }] }), MAILBOX76), typeof h76OpenPrWithoutAssignee(pr76({ labels: [{ name: 'pinned' }] }), MAILBOX76)].join(), ',string');
b(BATTERY76, '⛔ H76: a closed PR and a missing row are silent, never a crash', [h76OpenPrWithoutAssignee(pr76({ state: 'closed' }), MAILBOX76), h76OpenPrWithoutAssignee(undefined, MAILBOX76), h76OpenPrWithoutAssignee(null, MAILBOX76)].join(), ',,');
const unread76 = [null, undefined, ''].map((label) => String(h76OpenPrWithoutAssignee(pr76(), label) ?? ''));
b(BATTERY76, 'H76: an UNREAD mailbox label fires UNJUDGED, never clean — null, undefined and empty alike', unread76.every((t) => t.startsWith(UNJUDGED_MARKER) && isUnjudgedFinding(t)), true);
b(BATTERY76, '⛔ H76: …but an ASSIGNED PR stays clean with the label unread — the label is only the mailbox exemption', h76OpenPrWithoutAssignee(pr76({ assignees: [{ login: 'os-elon-musk' }] }), null), null);
b(BATTERY76, 'H76: not a loud finding — it never escalates a sweep', [isLoudFinding(row76), isLoudFinding(unread76[0])].join(), 'false,false');
b(BATTERY76, 'H76 band: registered as `state` (H1\'s), and the sweep really pushes it', `${familyBand('H76')}/${familyRegistryCoverage().emitted.includes('H76')}`, 'state/true');
b(BATTERY76, 'H76 wiring: every listed open PR is judged with the label taken LAZILY from mailbox-open.mjs, never spelled in the sweep', (() => { const src = readFileSync(fileURLToPath(import.meta.url), 'utf8'); return src.includes("({ MAILBOX_LABEL: mailboxLabel } = await import('./mailbox-open.mjs'));") && src.includes('const unowned = h76OpenPrWithoutAssignee(pr, mailboxLabel);'); })(), true);
b(BATTERY76, 'floor: this battery is DECLARED on the frozen roster with a positive pin', Object.hasOwn(SELF_TEST_BATTERIES, BATTERY76) && SELF_TEST_BATTERIES[BATTERY76] > 0 && Object.isFrozen(SELF_TEST_BATTERIES), true);

// -- H73 (#29386): a RULED decision card still sitting in the decision box.
// Positive controls beside every negative one: the label, the fence, the
// anchoring, the id, the threshold, the governing stamp, the re-entry line.
Expand Down
Loading