Skip to content

Commit ad2e593

Browse files
committed
docs(pm,agents): write identity follows the channel, not the account — REST-only content writes, the ACCEPT refuses MCP writes, a stale shared checkout re-seats
Equal-line edits under every ratchet (SKILL.md 812, os-dev.md 403, platform-readings 454, rest-channel 82, core-rules 151), every touched line at or under 120 bytes. - facts and invariants: content writes go only through the REST proxy, never an MCP content-write tool; `user.login` on a write names the channel's token (installation ⇒ `claude[bot]`, user-to-server ⇒ the bound user — per session, not the seat's to choose), never the actor; attribution is the session ID in the text carrier. Rewritten in os-dev.md :51/:53, SKILL.md :97–:98, core-rules :25, platform-readings :129, rest-channel :54. - dispatch order and acceptance: every dispatch order carries a `Writes:` line; an os-dev-report whose `mcp_calls` names a write tool (the settings deny list + `update_pull_request`) is refused, on both the seat side (SKILL.md 复核) and the dev side (os-dev.md :369). - propagation: at fire time the seat also reads the latest origin/main touch of the harness-loaded files (.claude/settings.json, .claude/agents/*.md, .claude/hooks/*) against the shared checkout's HEAD; a touch not in HEAD ⇒ close the shift and re-seat in a fresh session, never advance the shared checkout in place. The reading is scripts/pm/check-harness-current.mjs (git only, seat-side, not CI). - fleet: the new-repo registration checklist gains the write-identity locks port (deny + hooks). Density paid inside each file: four SKILL.md bullet pairs merged (终报要求/机器可核字段, mode:cloud/build-heavy, marker spellings/ missing-notification, 报告契约 authority/second-copy); os-dev.md :53 drops the board/wide-search clause already carried by :50 and :54. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
1 parent af3add1 commit ad2e593

6 files changed

Lines changed: 76 additions & 17 deletions

File tree

‎.claude/agents/os-dev.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ model: opus
4848
- 它们进 PR `## Acceptance notes`,报告 `out_of_scope_findings` 记 `noted, not filed: …`,席位 ACCEPT 读。
4949
- 写 `noted, not filed` 前先答哪一个 PR 或人会碰到这个文件;答不出就写明「承接者:无」。
5050
- 立卡者不查重,只在卡面附 3–5 个查重词;查重归分诊席,⛔ 不扫 open issues、不拉板。
51-
- GitHub 写一律走 REST 代理(`curl` 带环境 `GITHUB_TOKEN`),署名恒 App 的 `claude[bot]`。
51+
- GitHub 写一律走 REST 代理(`curl` 带环境 `GITHUB_TOKEN`);归属 = 文本里的 session ID,非 `user.login`。
5252
- 写预算四笔:`git push`、一次 `POST /pulls`(draft)、`POST /issues/{n}/labels`、`os-dev-report` 评论。
53-
- ⛔ 不用任何 MCP GitHub 写工具:用户账号署名,封号即隐;⛔ 不枚举板面、不宽词搜。
53+
- ⛔ 不用 MCP GitHub 写工具;令牌按会话定:installation ⇒ `claude[bot]`,user-to-server ⇒ 用户。
5454
- 卡与线程只走 payload 档(公开仓单卡网页内嵌 JSON,拼写住 platform-readings)或单卡 REST 读。
5555
- 三类发现附查重词进报告交席位代立,dev 不 `POST /issues`;预算外零写,⛔ 不 `PATCH` 正文。
5656
- 通道对照表见 `.claude/skills/pm-dispatch/references/rest-channel.md`,其 ✓ 按座位实测。
@@ -366,7 +366,7 @@ model: opus
366366
"premise_still_valid": true,
367367
"summary": "what was implemented, 2-4 sentences",
368368
"tests": "commands run + pass/fail evidence (real output excerpts); ablation: rebuild + on-disk mutation proof",
369-
"mcp_calls": "<n> — your MCP GitHub call count for the whole run",
369+
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
370370
"api_writes": "<n> — REST proxy writes, each endpoint listed",
371371
"open_questions": [
372372
{ "question": "…", "options": ["A …", "B …"], "recommendation": "A, because …" }

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
8888
- 维护者明示召唤是仲裁:有简报径直坐席;无简报才走保守确认,确认终止即坐席。
8989
- 互斥清 ⇒ fetch 后读三章程文件(本文、core-rules、本席章程)在 `origin/main` 的最新触碰 sha。
9090
- 异于上一开轮标记即先重读;留开轮标记(session ID + fire 时刻 + 该触碰,注明重读)再跑轮。
91+
- 同读 harness 载入面 `.claude/{settings.json,agents/*.md,hooks/*}` 的最新触碰是否已在共享检出 HEAD。
92+
- 否 ⇒ 收班、换新会话再派,⛔ 不推进共享检出;读数走 `scripts/pm/check-harness-current.mjs`。
9193

9294
## 全体座位的不变量
9395

9496
- 状态只经 GitHub 标签、assignee、正文行与 `pm:seat` 座位贴读写;循环须能从全新会话恢复。
95-
- 用户账号仅三用:assignee、授权批准、维护者亲手;⛔ 席位与 dev 永不以用户账号写内容。
96-
- 内容恒经 REST 代理(`claude[bot]`);批准账号永不跑席位、不作席位 claude.ai 的关联用户。
97+
- 用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。
98+
- 内容写只走 REST 代理,⛔ 无 MCP 写;`user.login` 记令牌不记席位,归属 = 文本里的 session ID。
9799
- GitHub 上一切新内容用英文;中文只留四通道(维护者速读、轮报、裁决引文、四维分析)。
98100
- 裁决引文照抄不译;四维中文只管新记录,存量英文块 ⛔ 不迁移;存量中文 ⛔ 不追溯改写。
99101
- 一座位一车道双射:域 X 谁管、PM Y 管什么,各恰好一个答案。
@@ -190,7 +192,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
190192
- 单车道仓 `cloud`、`objectos`、`hotcrm`、`www.objectos.ai`;未来新仓默认此类。
191193
- 单车道仓 `repo:*` 席自理机械三务,⛔ 不产 `domain:*`;决策卡入本仓收件箱,不落 objectstack。
192194
- 机械三务 = 自扫 sweep、自打 `type`、自做 `finding` 首触定级。
193-
- 新仓登记是一张清单:座位贴、标签、类别归属、门禁盘点。
195+
- 新仓登记是一张清单:座位贴、标签、类别归属、门禁盘点、写身份锁移植(deny + hooks)。
194196
- 新仓准入判据一句:这个仓真的需要常设席位吗。
195197
- hotcrm 收卡判据与宪章见 `references/lanes/hotcrm.md`。
196198
- objectui 卡按修复落点分流三流,`domain:ui` 是唯一新增标签。
@@ -532,8 +534,8 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
532534
- 标准非协商条款 ⛔ 不抄进派发词:hook 已机械强制段与 os-dev.md 已载通用段砍掉。
533535
- ⛔ 不砍卡类特有约束:ADR 类 draft-only、tier 推导引用、棘轮实况、释义纪律。
534536
- 无条件条款只住角色文件,冲突时它胜、错了修那里,⛔ 不靠派发词临时覆盖。
535-
- 终报要求随派发词带一句:只收机器可核字段,⛔ 复述 PR body 叙事。
536-
- 机器可核字段 = gates / line_budget / deviations / files_changed。
537+
- 终报要求随派发词带一句:只收机器可核字段(gates / line_budget / deviations / files_changed)。
538+
- 派发令恒带 `Writes:` 行:只走 REST 代理、写预算(端点清单)、`mcp_calls` 计数,dev 两数都报。
537539
- 清单、路径、行号在派发那一刻从树上取,⛔ 不从卡片/上次派发/记忆抄。
538540
- 门禁清单取 `dispatch-gates.mjs --commands` 逐条跑,退出码先落盘,`--ran` 对账;⛔ 不抓人读输出。
539541
- 点名单是线索不是规格,dev 对实际改动重取补跑;行级断言转述前必须自己重验。
@@ -551,8 +553,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
551553
- 翻转公开语义的裁决随卡带全仓 pin 清扫,两句缺一不可,原文见 runbook。
552554
- 拒收用例 ⇒ `code`+`status` 最低断言;过滤/谓词语义 ⇒ 编译面清单逐面申报,⛔ 静默略过。
553555
- 资源与后端:S 级机械 + M ⇒ `mode:subagent`;S 级但不机械(判断面在设计不在门禁)按 M 待遇。
554-
- `mode:cloud` 只保留给 L/XL、必须活过 PM 会话的工作、浏览器/dogfood 验证。
555-
- build 重的 M 卡逐卡判断是否上云。
556+
- `mode:cloud` 只保留给 L/XL、活过 PM 会话的工作、浏览器/dogfood 验证;build 重的 M 卡逐卡判。
556557
- 归档义务只落在云卡;OOM 死的单独重派;判定连同档位写进认领评论。
557558
- 一次性云卡用 `create_session`,⛔ 不用 create_trigger+fire;trigger 流只留给定时/重复型。
558559
- 云会话 `SendMessage` not-reachable 是设计非故障,⛔ 不复测。
@@ -563,8 +564,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
563564

564565
- 报告通道统一:GitHub 是两种模式共用的真相源;dev 终报先落 issue 评论、再作返回消息。
565566
- 收集先扫 GitHub,标记评论在 = 报告完整;两处皆无才进探活/判死。
566-
- 标记两种拼写等效:HTML 注释形与首行纯文本 `os-dev-report`。
567-
- ⛔ 仅凭 HTML 注释形式缺失永不读作报告未达;⛔ 永不把没收到失败通知读作还在跑。
567+
- 标记两种拼写等效(HTML 注释形、首行 `os-dev-report`);⛔ 永不把没收到失败通知读作还在跑。
568568
- 探活是每轮巡检的固定动作;完成通知不可靠,缺席什么都不证明。
569569
- ① 巡检首动作 `list_triggers` 确认 Routine 启用,在飞重挂加速器;唤醒归 Routine,漏挂不断链。
570570
- ② 在飞期间主巡检间隔 ≤45 分钟,待命期 60–70;节奏维护者明示可改。
@@ -599,6 +599,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
599599
- 触 `skills/**` 的 PR 加问整包价值密度:从整包加载的客户 agent 座位读,⛔ 不从作者座位读。
600600
- 超派发预算或小功能大扩写 ⇒ REWORK,⛔ 不因已经写好了放行。
601601
- 判决 ACCEPT:issue 英文短评论,核对清单结论 + 抽查读数 + 偏差,链接 PR,⛔ 不复述其叙事。
602+
- `mcp_calls` 点名写工具(`settings.json` deny 清单 + `update_pull_request`)⇒ 拒收,⛔ 不带注放行。
602603
- 判决 REWORK:逐项反馈,同认领重派;补丁轮优先 SendMessage 续派原 dev;最多 2 轮,第三次升级。
603604
- 判决 ESCALATE:见 升级与决策 节。
604605
- **ACCEPT 之后的路径分叉**:翻 ready / 挂 auto-merge / 入队前先取一次 PR 的路径面。
@@ -771,8 +772,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
771772

772773
## 报告契约
773774

774-
- 终报 JSON 的权威形状住 `.claude/agents/os-dev.md` 终报消息节,字段与拼写以那里为准。
775-
- ⛔ 本文不抄终报形状的第二份。
775+
- 终报 JSON 的权威形状住 `.claude/agents/os-dev.md` 终报消息节,⛔ 本文不抄第二份。
776776
- `premise_still_valid: false` + `pr: null` 是合法终报,当再分诊输入复核,永不当失败派发。
777777
- `status: needs_decision` 时 `open_questions` 必须非空。
778778
- `out_of_scope_findings` 只列三类立卡与 `noted, not filed`;立卡附查重词、归挂、立在修复仓。

‎.claude/skills/pm-dispatch/references/core-rules.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
- 状态住 GitHub 标签、assignee、正文行与座位贴;⛔ 本地零状态;循环须能从新会话恢复。
2323
- PM ⛔ 不写文件也不写代码,唯一例外是维护者逐 PR 明示授权的 `.claude/` 内部工具 PR。
2424
- 该例外须把授权原话引在 PR 正文并由另一座位或维护者复核,⛔ 不得自审自合。
25-
- 用户账号仅三用:assignee、授权批准、维护者亲手;写恒经 REST 代理;批准账号永不跑席位。
25+
- 用户账号仅三用:assignee、授权批准、维护者亲手;写只走 REST 代理,署名随令牌非席位。
2626
- GitHub 新内容一律英文,中文只留轮次报告、派发令裁决引文与四维分析;引文照抄不译。
2727
- 存量中文 ⛔ 不追溯改写;一座位一车道双射,每域恰一 PM;与 AGENTS.md 冲突以之为准。
2828
## 状态模型

‎.claude/skills/pm-dispatch/references/platform-readings.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
- ⇒ MCP 限流先探 REST 再定退避,⛔ 不据一侧限流把整个平台的写都停掉。
127127
- MCP 的读限流与写限流彼此独立,两向各有实测 ⇒ 一侧被拒 ⛔ 不推另一侧也不可用。
128128
- REST 档以本班 repo-scoped 探针绿为前提;403 会话改按降级梯读。
129-
- 容器 curl 的 REST 通道 = App installation token,core 15,000/时,与 GraphQL 池独立计。
129+
- 容器 curl 的 REST 通道令牌按会话定:installation(`claude[bot]`)或 user-to-server(用户),core 15,000/时。
130130
- GraphQL 池 5000/时,只留给没有 REST 对应物的几件。
131131
- 那几件 = draft 翻转、auto-merge 挂载、语义 `/search/*`、Projects field_values、`issue transfer`。
132132
- 逐操作通道归属、写侧配方与队列路由三读法见 `rest-channel.md`,⛔ 不在本表复述。

‎.claude/skills/pm-dispatch/references/rest-channel.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
- ✓ auto-merge 挂载 `PUT .../pulls/{n}/ccr/auto_merge` 带 `{"merge_method":"SQUASH"}`,`DELETE` 卸载。
5252
- 入队读 timeline `added_to_merge_queue`,落地读 `git rev-list --parents`;⛔ `auto_merge` 与回显都不作数。
5353
- 两条 2026-09-12 两席实调;备用通道 MCP `update_pull_request` 与 `enable_pr_auto_merge`。
54-
- 直合仓另有 `PUT .../pulls/{n}/merge`;ccr 的 timeline actor 记 `claude[bot]`,MCP 记席位账号。
54+
- 直合仓 `PUT .../pulls/{n}/merge`;actor 记通道令牌:REST 按会话为 `claude[bot]` 或用户,MCP 恒用户。
5555

5656
## 不可迁移 —— 只有这三件,围着它们排计划;红窗守候规则住 `platform-readings.md` 配额段
5757

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env node
2+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
3+
4+
/**
5+
* check-harness-current -- is every harness-loaded file on `origin/main` already in the
6+
* shared (primary) checkout's HEAD? A seat-side fire-time reading; ⛔ not a CI gate.
7+
*
8+
* node scripts/pm/check-harness-current.mjs [--shared <dir>] [--ref origin/main]
9+
*
10+
* The harness reads `.claude/settings.json`, `.claude/agents/*.md` and `.claude/hooks/*` from
11+
* the PRIMARY checkout when a session starts and never reloads them, so a touch that lands on
12+
* `origin/main` after that clone is inert for the running session -- measured: a session whose
13+
* primary checkout predated the MCP-write deny list still carried every denied tool. Worktrees
14+
* do not help (the harness never reads them) and the primary checkout is never advanced in place
15+
* (worktree-first), so the remedy is to close the shift and re-seat in a fresh session.
16+
*
17+
* Reads git only -- fetch first. Exit 0 = current; 1 = stale (each stale path printed with its
18+
* touch); 2 = undecidable: the ancestry test is negative on a SHALLOW clone and the touch is not
19+
* newer than HEAD, so the missing ancestor path may be a truncation, not a fact -- deepen, rerun.
20+
*/
21+
22+
import { execFileSync } from 'node:child_process';
23+
import process from 'node:process';
24+
25+
const PATHS = ['.claude/settings.json', '.claude/agents/*.md', '.claude/hooks/*'];
26+
const argv = process.argv.slice(2);
27+
const opt = (flag, fallback) => (argv.includes(flag) ? argv[argv.indexOf(flag) + 1] : fallback);
28+
const git = (dir, ...args) => {
29+
try {
30+
return { ok: true, out: execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() };
31+
} catch (error) {
32+
return { ok: false, out: String(error.stdout ?? '').trim() };
33+
}
34+
};
35+
36+
const common = git(process.cwd(), 'rev-parse', '--path-format=absolute', '--git-common-dir');
37+
const shared = opt('--shared', common.ok ? common.out.replace(/\/\.git$/, '') : undefined);
38+
const ref = opt('--ref', 'origin/main');
39+
const head = shared ? git(shared, 'rev-parse', 'HEAD') : { ok: false, out: '' };
40+
if (!head.ok) { console.error(`check-harness-current: no git checkout at ${shared ?? '(cwd)'} -- pass --shared <dir>`); process.exit(2); }
41+
const headTime = Number(git(shared, 'log', '-1', '--format=%ct', 'HEAD').out);
42+
const shallow = git(shared, 'rev-parse', '--is-shallow-repository').out === 'true';
43+
const short = (sha) => sha.slice(0, 10);
44+
let stale = 0;
45+
let undecided = 0;
46+
for (const path of PATHS) {
47+
const touch = git(shared, 'log', '-1', '--format=%H %ct %cI', ref, '--', path);
48+
if (!touch.ok || !touch.out) { undecided += 1; console.log(`? ${path}: no touch of ${ref} visible from ${shared} (unfetched ref, or a shallow window) -- UNDECIDED`); continue; }
49+
const [sha, ct, ci] = touch.out.split(' ');
50+
if (git(shared, 'merge-base', '--is-ancestor', sha, 'HEAD').ok) { console.log(`✓ ${path}: latest touch ${short(sha)} (${ci}) is in the shared HEAD`); continue; }
51+
if (Number(ct) > headTime || !shallow) { stale += 1; console.log(`✗ ${path}: latest touch ${short(sha)} (${ci}) is NOT in the shared HEAD ${short(head.out)} -- STALE`); continue; }
52+
undecided += 1;
53+
console.log(`? ${path}: touch ${short(sha)} (${ci}) is not under HEAD in a SHALLOW clone and not newer than HEAD -- UNDECIDED, deepen and rerun`);
54+
}
55+
const verdict = stale
56+
? `STALE -- ${stale} harness-loaded path(s) on ${ref} are not in ${shared} HEAD ${short(head.out)}: close the shift and re-seat in a fresh session; ⛔ never advance the shared checkout in place`
57+
: undecided ? `UNDECIDED -- ${undecided} path(s) could not be placed; fetch/deepen ${shared} and rerun` : `CURRENT -- every harness-loaded path on ${ref} is in ${shared} HEAD ${short(head.out)}`;
58+
console.log(`check-harness-current: ${verdict}`);
59+
process.exit(stale ? 1 : undecided ? 2 : 0);

0 commit comments

Comments
 (0)