Skip to content

Commit 7b19b9e

Browse files
os-litantclaude
andauthored
docs(pm-dispatch): keep two copies of the decision frame, not four (#14862)
* docs(pm-dispatch): drop the two dev-side copies of the decision frame Batch-3 ruling, item 4, option B: the four-axis decision frame keeps two copies (internal-pm and published-pm); the dev-side copies are replaced by the {decision_frame} placeholder the dispatching PM fills at dispatch time. - skills/objectstack-pm-dispatch/SKILL.md: the embedded dev-agent template loses the frame copy (declaring sentence through binding sentence). The stop trigger stays -- it is the escalation TRIGGER, not the frame, and rules/dev-template.md carries it nowhere else. The paste instruction now names the surviving section by heading. - .claude/agents/os-dev.md: the frame block collapses to one sentence saying the frame arrives in the dispatch prompt, that every option is analyzed on its axes and the recommendation justified on them, and that a prompt without it is a stop-and-ask rather than an invitation to invent axes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * tooling(pm): narrow the frame-sync COPIES table to the two surviving copies check-skill-frame-sync watched four copies across three files; the batch-3 ruling (item 4, option B) leaves two, both PM-side. The dev-side entries are removed and every count that was spelled out is now derived from the table. - COPIES loses internal-dev and published-dev; the header, the COPIES docblock and the count-disagreement message stop spelling "four". - The same-file overlap guard stays. It is vacuous today (no two copies share a file) but re-declaring such a pair is one edit away, and it is the check that makes such a pair prove it compares two different sections. - Self-test: the three cases driven through the removed ids are retargeted at the surviving copies, and a new case pins the reduction itself -- the dev-side text reappearing in the now-undeclared agent definition is refused by the anti-dormancy scan. Its measured limit is written next to it: the scan's population is per FILE, so a second copy pasted back into a file that is already declared is not refused, and was not before this change either. - check-skill-frame-freshness imported COPIES but spelled one frame file's path in five fixtures; those now derive it from the table, which is what the file's own #8024 rule asks for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 * docs(os-dev): rewrap the replacement sentence under the 120-byte line budget check:pm-skill-ratchet reads a per-line byte budget as well as a line ceiling; the first line of the replacement landed at 121B. Rewrapped at legal CJK break points, content byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 365141f commit 7b19b9e

4 files changed

Lines changed: 126 additions & 122 deletions

File tree

.claude/agents/os-dev.md

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -397,29 +397,10 @@ issue 对塑造公开契约的某个决定欠规格 —— spec/Zod schema、API
397397
——
398398
或两种读法通向两种架构时:不猜,不写投机代码。返回 `status: "needs_decision"`,把每个问
399399
题连同选项、成本与你的推荐写进 `open_questions`
400-
**Analyze every option on four fixed axes — this framing is the core of the escalation,
401-
not decoration:**
402-
403-
- **Real business need**(实际业务需求)— 该方案服务的是**真实存在的业务场景**,还是投
404-
机性能力面?证据必须**实测** —— 谁在写这个键、谁在读这个能力、示例应用与真实部署
405-
怎么
406-
用;「读起来像有用」不作数。这条轴会改变结论,不是陪衬。
407-
- **Long-term soundness for THIS project**(项目长远合理性)— 哪个方案符合北极星方向与
408-
可持续架构(no workarounds、contract-first)—— 补丁式选项的长期代价要明说。
409-
- **Making AI-written code — especially AI-authored metadata apps — hard to get wrong**
410-
(防 AI 写代码犯错,尤其是 AI 编写的元数据 app)— 优先选在编写时点就结构性防错的方案
411-
(严格 schema、publish 时响亮拒绝的校验、declared = enforced),而非消费端宽容 —— 宽
412-
容的消费端恰是 AI 生成错误藏身并扩散的地方。
413-
- **Startup scope discipline**(创业阶段不扩散需求)— **创业阶段聚焦原则**(维护者
414-
2026-08-04:这是创业项目,核心能力优先):能力扩张默认从紧,无拉动的声明面按
415-
implementation-first 处置,已发布零消费的能力不因沉没成本获得豁免。
416-
417-
Your recommendation must be justified on all four axes;长远合理性权重恒 ≥50%(2026-09-01
418-
裁「四维分析中,长期合理应该权重最高,至少50%」):推荐以它领起,其余各轴合起来投不翻
419-
它,按本义读(缩小而非扩大特例/契约增生),⛔ 不为投机扩张背书;四轴冲突时推荐按拉动排
420-
序:实测拉动→长远,零拉动→不扩散,防错破平,安全与难逆恒人工(权重与序都只排推荐,⛔
421-
不授权、不移人工地板);权衡如实呈现交维护者拍板。`main` 在你脚下碎了、依赖未合并、CI
422-
基础设施故障⇒`blocked`(附证据),重试到足以排除你的改动。
400+
升级分析的四轴决策框架由派发词携带 —— 派发的 PM 从自己那份副本填入(已发布模板里
401+
`rules/dev-template.md``{decision_frame}` 槽位):每个方案逐轴分析,推荐也按那
402+
些轴给出理由;派发词没带,停下向 PM 索取,⛔ 不自拟一套轴。`main` 在你脚下碎了、依赖
403+
未合并、CI 基础设施故障⇒`blocked`(附证据),重试到足以排除你的改动。
423404

424405
## 终报消息 —— 恰好这段 JSON,不带任何环绕散文
425406

scripts/check-skill-frame-freshness.mjs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
// ruled explicitly that they stay two scripts, and the reason is worth stating
1313
// because "just add it to the other script" is the obvious wrong move:
1414
//
15-
// check:skill-frame-sync "are the FOUR COPIES IN THIS TREE isomorphic?"
15+
// check:skill-frame-sync "are THIS TREE's COPIES isomorphic to each other?"
1616
// → compares copy against copy, one tree.
1717
// check:skill-frame-freshness "is THIS TREE's frame current with origin/main?"
1818
// → compares this tree against a remote ref.
1919
//
20-
// They are independent: a tree that is 173 commits behind, whose four copies are
20+
// They are independent: a tree that is 173 commits behind, whose copies are
2121
// CONSISTENTLY the old two-axis frame, is **green** on the sync gate — correctly
2222
// so, the copies really are isomorphic — while being exactly the defect #5866
2323
// reports. The self-test pins that independence rather than asserting it: the
@@ -37,10 +37,15 @@
3737
// hypothetical, and the channel is still live — at the time this gate was
3838
// written the shared checkout was 59 commits behind on a different branch.
3939
//
40-
// The frame lives in three files / four copies; COPIES and AXIS_MAP are imported
41-
// from check-skill-frame-sync.mjs so that "the frame's structure" has exactly ONE
42-
// definition. Forking those anchors into this script would reproduce, in the
43-
// gates themselves, the hand-copied-text disease they exist to police.
40+
// COPIES and AXIS_MAP are imported from check-skill-frame-sync.mjs so that "the
41+
// frame's structure" has exactly ONE definition — how many copies there are, and
42+
// which files hold them, is that table's answer and never a second list here.
43+
// Forking those anchors into this script would reproduce, in the gates
44+
// themselves, the hand-copied-text disease they exist to police. (It was four
45+
// copies in three files until the 2026-09-03 batch-3 ruling, item 4 option B,
46+
// dropped the two dev-side copies; nothing in this file had to move for that
47+
// except the self-test fixtures below, which used to SPELL one of the dropped
48+
// files instead of deriving it.)
4449
//
4550
// STRUCTURE, NEVER BYTES
4651
// ----------------------
@@ -131,6 +136,21 @@ const DEFAULT_FETCH_TIMEOUT_MS = 20_000;
131136

132137
const FRAME_FILES = [...new Set(COPIES.map((c) => c.file))];
133138

139+
/**
140+
* The copy the self-test's file-level fixtures operate on — the one they delete,
141+
* withhold from a ref, or make unparseable. DERIVED, never spelled: these cases
142+
* named `.claude/agents/os-dev.md` until the 2026-09-03 reduction stopped it
143+
* being a frame file, and a spelled path would have gone on asserting about a
144+
* document the frame no longer lives in. Same rule as everything else in this
145+
* file (#8024): locate the fixture the way the gate locates the real thing.
146+
*
147+
* First entry, so the choice is deterministic and non-empty for any COPIES table
148+
* the sync gate can start up with (an empty one fails that gate first).
149+
*/
150+
const SAMPLE_COPY_ID = COPIES[0].id;
151+
const SAMPLE_FRAME_FILE = COPIES[0].file;
152+
const SAMPLE_FRAME_FILE_RX = new RegExp(SAMPLE_FRAME_FILE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
153+
134154
// ---------------------------------------------------------------------------
135155
// git plumbing
136156
// ---------------------------------------------------------------------------
@@ -827,13 +847,13 @@ function selfTest() {
827847
label: 'stale tree + authoritative ref → ERROR naming the stale files (the #5866 shape)',
828848
run: () => evaluate({ root: dir, ref: current }),
829849
expect: 'error',
830-
wants: [/STRUCTURALLY BEHIND/, /\.claude\/agents\/os-dev\.md/, /3 axes: long-term-soundness/, /4 axes: business-need/, new RegExp(REMEDY.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))],
850+
wants: [/STRUCTURALLY BEHIND/, SAMPLE_FRAME_FILE_RX, /3 axes: long-term-soundness/, /4 axes: business-need/, new RegExp(REMEDY.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))],
831851
});
832852
cases.push({
833853
label: 'the SAME stale tree, fetch impossible → degrades to WARN, exit 0, same diagnosis',
834854
run: () => evaluate({ root: dir }),
835855
expect: 'warn',
836-
wants: [/STRUCTURALLY BEHIND/, /\.claude\/agents\/os-dev\.md/],
856+
wants: [/STRUCTURALLY BEHIND/, SAMPLE_FRAME_FILE_RX],
837857
alsoAssert: (v) => (v.clamped ? null : 'expected the verdict to be marked as clamped'),
838858
});
839859
// The independence proof: the sync gate is GREEN on this very fixture.
@@ -894,14 +914,14 @@ function selfTest() {
894914
// --- 6: a framework file main has and we do not --------------------------
895915
{
896916
const { dir, b: current } = linear('missing-here', real, real);
897-
git(['rm', '-q', '.claude/agents/os-dev.md'], { cwd: dir });
917+
git(['rm', '-q', SAMPLE_FRAME_FILE], { cwd: dir });
898918
const without = commitAll(dir, 'drop a framework file');
899919
setOriginMain(dir, current);
900920
cases.push({
901921
label: 'a framework file exists on the ref but not in the tree → ERROR',
902922
run: () => evaluate({ root: dir, ref: current }),
903923
expect: 'error',
904-
wants: [/MISSING from this working tree/, /os-dev\.md/],
924+
wants: [/MISSING from this working tree/, SAMPLE_FRAME_FILE_RX],
905925
alsoAssert: () => (without ? null : 'fixture did not commit'),
906926
});
907927
}
@@ -910,9 +930,9 @@ function selfTest() {
910930
{
911931
const dir = makeRepo('missing-there');
912932
temps.push(dir);
913-
const partial = new Map([...real].filter(([f]) => f !== '.claude/agents/os-dev.md'));
933+
const partial = new Map([...real].filter(([f]) => f !== SAMPLE_FRAME_FILE));
914934
writeFiles(dir, partial);
915-
const refSha = commitAll(dir, 'main without the dev-agent definition');
935+
const refSha = commitAll(dir, 'main without one framework file');
916936
writeFiles(dir, real);
917937
commitAll(dir, 'tree adds it');
918938
setOriginMain(dir, refSha);
@@ -926,7 +946,7 @@ function selfTest() {
926946

927947
// --- 8: the frame moved on main, our anchors predate it ------------------
928948
{
929-
const moved = withUnreadableCopy(real, 'internal-dev');
949+
const moved = withUnreadableCopy(real, SAMPLE_COPY_ID);
930950
const { dir, b: current } = linear('anchors', real, moved);
931951
git(['checkout', '-q', git(['rev-parse', 'HEAD~1'], { cwd: dir }).stdout.trim()], { cwd: dir });
932952
setOriginMain(dir, current);
@@ -940,7 +960,7 @@ function selfTest() {
940960

941961
// --- 9: our own tree does not parse --------------------------------------
942962
{
943-
const broken = withUnreadableCopy(real, 'internal-dev');
963+
const broken = withUnreadableCopy(real, SAMPLE_COPY_ID);
944964
const { dir, b: current } = linear('broken-here', broken, real);
945965
git(['checkout', '-q', git(['rev-parse', 'HEAD~1'], { cwd: dir }).stdout.trim()], { cwd: dir });
946966
setOriginMain(dir, current);

0 commit comments

Comments
 (0)