Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .claude/agents/os-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ model: opus
- 它们进 PR `## Acceptance notes`,报告 `out_of_scope_findings` 记 `noted, not filed: …`,席位 ACCEPT 读。
- 写 `noted, not filed` 前先答哪一个 PR 或人会碰到这个文件;答不出就写明「承接者:无」。
- 立卡者不查重,只在卡面附 3–5 个查重词;查重归分诊席,⛔ 不扫 open issues、不拉板。
- GitHub 写一律走 REST 代理(`curl` 带环境 `GITHUB_TOKEN`),署名恒 App 的 `claude[bot]`。
- GitHub 写一律走 REST 代理(`curl` `GITHUB_TOKEN`);归属 = 文本里的 session ID,非 `user.login`。
- 写预算四笔:`git push`、一次 `POST /pulls`(draft)、`POST /issues/{n}/labels`、`os-dev-report` 评论。
- ⛔ 不用任何 MCP GitHub 写工具:用户账号署名,封号即隐;⛔ 不枚举板面、不宽词搜
- ⛔ 不用 MCP GitHub 写工具;令牌按会话定:installation ⇒ `claude[bot]`,user-to-server ⇒ 用户
- 卡与线程只走 payload 档(公开仓单卡网页内嵌 JSON,拼写住 platform-readings)或单卡 REST 读。
- 三类发现附查重词进报告交席位代立,dev 不 `POST /issues`;预算外零写,⛔ 不 `PATCH` 正文。
- 通道对照表见 `.claude/skills/pm-dispatch/references/rest-channel.md`,其 ✓ 按座位实测。
Expand Down Expand Up @@ -366,7 +366,7 @@ model: opus
"premise_still_valid": true,
"summary": "what was implemented, 2-4 sentences",
"tests": "commands run + pass/fail evidence (real output excerpts); ablation: rebuild + on-disk mutation proof",
"mcp_calls": "<n> — your MCP GitHub call count for the whole run",
"mcp_calls": "<n> — MCP GitHub calls with tool names; a write tool in the list = this report is refused",
"api_writes": "<n> — REST proxy writes, each endpoint listed",
"open_questions": [
{ "question": "…", "options": ["A …", "B …"], "recommendation": "A, because …" }
Expand Down
22 changes: 11 additions & 11 deletions .claude/skills/pm-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
- 维护者明示召唤是仲裁:有简报径直坐席;无简报才走保守确认,确认终止即坐席。
- 互斥清 ⇒ fetch 后读三章程文件(本文、core-rules、本席章程)在 `origin/main` 的最新触碰 sha。
- 异于上一开轮标记即先重读;留开轮标记(session ID + fire 时刻 + 该触碰,注明重读)再跑轮。
- 同读 harness 载入面 `.claude/{settings.json,agents/*.md,hooks/*}` 的最新触碰是否已在共享检出 HEAD。
- 否 ⇒ 收班、换新会话再派,⛔ 不推进共享检出;读数走 `scripts/pm/check-harness-current.mjs`。

## 全体座位的不变量

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

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

## 报告契约

- 终报 JSON 的权威形状住 `.claude/agents/os-dev.md` 终报消息节,字段与拼写以那里为准。
- ⛔ 本文不抄终报形状的第二份。
- 终报 JSON 的权威形状住 `.claude/agents/os-dev.md` 终报消息节,⛔ 本文不抄第二份。
- `premise_still_valid: false` + `pr: null` 是合法终报,当再分诊输入复核,永不当失败派发。
- `status: needs_decision` 时 `open_questions` 必须非空。
- `out_of_scope_findings` 只列三类立卡与 `noted, not filed`;立卡附查重词、归挂、立在修复仓。
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/core-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
- 状态住 GitHub 标签、assignee、正文行与座位贴;⛔ 本地零状态;循环须能从新会话恢复。
- PM ⛔ 不写文件也不写代码,唯一例外是维护者逐 PR 明示授权的 `.claude/` 内部工具 PR。
- 该例外须把授权原话引在 PR 正文并由另一座位或维护者复核,⛔ 不得自审自合。
- 用户账号仅三用:assignee、授权批准、维护者亲手;写恒经 REST 代理;批准账号永不跑席位
- 用户账号仅三用:assignee、授权批准、维护者亲手;写只走 REST 代理,署名随令牌非席位
- GitHub 新内容一律英文,中文只留轮次报告、派发令裁决引文与四维分析;引文照抄不译。
- 存量中文 ⛔ 不追溯改写;一座位一车道双射,每域恰一 PM;与 AGENTS.md 冲突以之为准。
## 状态模型
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/platform-readings.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
- ⇒ MCP 限流先探 REST 再定退避,⛔ 不据一侧限流把整个平台的写都停掉。
- MCP 的读限流与写限流彼此独立,两向各有实测 ⇒ 一侧被拒 ⛔ 不推另一侧也不可用。
- REST 档以本班 repo-scoped 探针绿为前提;403 会话改按降级梯读。
- 容器 curl 的 REST 通道 = App installation token,core 15,000/时,与 GraphQL 池独立计
- 容器 curl 的 REST 通道令牌按会话定:installation(`claude[bot]`)或 user-to-server(用户),core 15,000/时。
- GraphQL 池 5000/时,只留给没有 REST 对应物的几件。
- 那几件 = draft 翻转、auto-merge 挂载、语义 `/search/*`、Projects field_values、`issue transfer`。
- 逐操作通道归属、写侧配方与队列路由三读法见 `rest-channel.md`,⛔ 不在本表复述。
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pm-dispatch/references/rest-channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
- ✓ auto-merge 挂载 `PUT .../pulls/{n}/ccr/auto_merge` 带 `{"merge_method":"SQUASH"}`,`DELETE` 卸载。
- 入队读 timeline `added_to_merge_queue`,落地读 `git rev-list --parents`;⛔ `auto_merge` 与回显都不作数。
- 两条 2026-09-12 两席实调;备用通道 MCP `update_pull_request` 与 `enable_pr_auto_merge`。
- 直合仓另有 `PUT .../pulls/{n}/merge`;ccr 的 timeline actor `claude[bot]`,MCP 记席位账号
- 直合仓 `PUT .../pulls/{n}/merge`;actor 记通道令牌:REST 按会话为 `claude[bot]` 或用户,MCP 恒用户

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

Expand Down
59 changes: 59 additions & 0 deletions scripts/pm/check-harness-current.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* check-harness-current -- is every harness-loaded file on `origin/main` already in the
* shared (primary) checkout's HEAD? A seat-side fire-time reading; ⛔ not a CI gate.
*
* node scripts/pm/check-harness-current.mjs [--shared <dir>] [--ref origin/main]
*
* The harness reads `.claude/settings.json`, `.claude/agents/*.md` and `.claude/hooks/*` from
* the PRIMARY checkout when a session starts and never reloads them, so a touch that lands on
* `origin/main` after that clone is inert for the running session -- measured: a session whose
* primary checkout predated the MCP-write deny list still carried every denied tool. Worktrees
* do not help (the harness never reads them) and the primary checkout is never advanced in place
* (worktree-first), so the remedy is to close the shift and re-seat in a fresh session.
*
* Reads git only -- fetch first. Exit 0 = current; 1 = stale (each stale path printed with its
* touch); 2 = undecidable: the ancestry test is negative on a SHALLOW clone and the touch is not
* newer than HEAD, so the missing ancestor path may be a truncation, not a fact -- deepen, rerun.
*/

import { execFileSync } from 'node:child_process';
import process from 'node:process';

const PATHS = ['.claude/settings.json', '.claude/agents/*.md', '.claude/hooks/*'];
const argv = process.argv.slice(2);
const opt = (flag, fallback) => (argv.includes(flag) ? argv[argv.indexOf(flag) + 1] : fallback);
const git = (dir, ...args) => {
try {
return { ok: true, out: execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() };
} catch (error) {
return { ok: false, out: String(error.stdout ?? '').trim() };
}
};

const common = git(process.cwd(), 'rev-parse', '--path-format=absolute', '--git-common-dir');
const shared = opt('--shared', common.ok ? common.out.replace(/\/\.git$/, '') : undefined);
const ref = opt('--ref', 'origin/main');
const head = shared ? git(shared, 'rev-parse', 'HEAD') : { ok: false, out: '' };
if (!head.ok) { console.error(`check-harness-current: no git checkout at ${shared ?? '(cwd)'} -- pass --shared <dir>`); process.exit(2); }
const headTime = Number(git(shared, 'log', '-1', '--format=%ct', 'HEAD').out);
const shallow = git(shared, 'rev-parse', '--is-shallow-repository').out === 'true';
const short = (sha) => sha.slice(0, 10);
let stale = 0;
let undecided = 0;
for (const path of PATHS) {
const touch = git(shared, 'log', '-1', '--format=%H %ct %cI', ref, '--', path);
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; }
const [sha, ct, ci] = touch.out.split(' ');
if (git(shared, 'merge-base', '--is-ancestor', sha, 'HEAD').ok) { console.log(`✓ ${path}: latest touch ${short(sha)} (${ci}) is in the shared HEAD`); continue; }
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; }
undecided += 1;
console.log(`? ${path}: touch ${short(sha)} (${ci}) is not under HEAD in a SHALLOW clone and not newer than HEAD -- UNDECIDED, deepen and rerun`);
}
const verdict = stale
? `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`
: 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)}`;
console.log(`check-harness-current: ${verdict}`);
process.exit(stale ? 1 : undecided ? 2 : 0);
Loading