Repository navigation
[devx] the spec-property-retirement skill still prescribes the withdrawn "rewrite it automatically" tombstone sentence — a pin test reds anything that follows it #10848
Description
Activity
Triage first-touch by the
domain:devxPM seat (#6023, sessionsession_01DdCnBGcHeufjrq7drTD3wt), routing todomain:skills+needs-user-decision.I verified both halves in source rather than accepting the report, and the finding is accurate — in one respect more precisely than it was filed.
1. The skill does prescribe the withdrawn sentence
.claude/skills/spec-property-retirement/SKILL.md, convention 5 of the tombstone-message rules:Run `os migrate meta --from <N-1>` to rewrite it automatically.—— 仅当有 conversion 重写 sources。
⭐ The conditional is the sharp part, and it makes this worse rather than better. The convention guards itself with "only when a conversion rewrites sources" — but #9529 established that the
--fromarm never rewrites a source file at all; bothwriteFileSynccalls inpackages/cli/src/commands/migrate/meta.tsareif (flags.out)-guarded and the header declines the AST rewrite as "unsafe and lossy". So the guard's condition can never be satisfied by the arm the sentence names. A reader who checks the condition in good faith still cannot tell the sentence is dead.2. The pin exists, and the skill is outside its population
packages/spec/src/shared/retired-key-migrate-sentence.test.ts:240:it('every prescription sentence in packages/spec/src and packages/lint/src is house-form or MIXED two-clause', …)Population confirmed:
packages/spec/src+packages/lint/src..claude/**is not in it. So the class pin that exists precisely to keep this sentence from reappearing cannot see the file that teaches it.Why this is the dangerous ordering
An agent following the skill authors a tombstone carrying the withdrawn sentence, and the pin reds in a package the agent did not edit. The failure mode is not the red — it is what a reasonable agent does next: read the pin as the broken thing and weaken it, which would restore the promise across the ~90 prescriptions #9529 cleaned. The skill is upstream of every site PR #10847 just repaired; leaving it makes those repairs a treadmill.
⚠️ Note the census that missed it:docs/qa/platform-checklist/areas/cli.jsonitemcli.migrate-meta-codemodrevision 3 states "the only other carriers are the deliberate pin test and four CHANGELOGs" — it did not count the skill, which is likely how this survived #9529's sweep.Why it is
needs-user-decisionrather than dispatched.claude/skills/**is a governed surface (scripts/pm/check-governed-merges.mjs:274-280), human-merge-only. The fix is one sentence, but two things about it are the maintainer's call, not mine:- What replaces it. The
os migrate metanever rewrites the authored sources that 144 shipped retirement messages promise it will #9529-blessed form — "Runos migrate meta --from Nto list the mechanical edits for existing sources; apply them by hand" — is the obvious candidate, but this convention governs the wording of every future tombstone, so it is a house-style ruling with a long tail. - Whether the pin's population should widen to
.claude/**. That would make the class self-enforcing instead of relying on the next sweep to remember. It is also a scope change to a gate, with its own fabricated-match risk, and it is the more interesting half of this card.
Recorded, not decided. Ready to dispatch the moment there is a ruling.
Refs: #10831 / PR #10847 (the five downstream carriers, fixed) · #9529 (the class-wide reword) · #10418 / PR #10829 · #9591 (the unbuilt in-place codemod)
Generated by Claude Code
huangyiirene commented
on Aug 21, 2026 CollaboratorMore actions决策箱勤务(分诊座位):补标准四棱卡面块。devx 席的两项源代码核验结论照原样成立,本块只做卡面序列化,type 定 Task。
一句话问题:教 agent 写「退役提示句」的手册仍在教一句全库已撤回的假承诺(「命令会自动改写你的源文件」),而防止这句话回流的哨兵测试看不到手册本身 —— 要裁定替换成什么话,以及哨兵是否把手册纳入监视范围。
- 项目长远合理性:手册与哨兵各说各话是治理面的自相矛盾;把哨兵人群扩到手册所在目录,让这类回流从「靠下次人工普查记得」变成「机器当场拒绝」,是收敛特例而非增生。
- 实际业务拉动:下一张属性退役卡照手册写就会红门;最坏路径是 agent 把红着的哨兵当坏件削弱 —— 那会把 ~90 条已清洗的提示句整体退回假承诺,客户在报错提示里再次被指去跑一个不会改文件的命令。
- 防 AI 犯错:哨兵双向 pin(要求新句、拒绝旧句)机制是对的,缺的只是人群覆盖;修手册 + 扩人群 = 声明即强制;只修手册不扩人群 = 下次回流仍靠运气。
- 创业阶段不扩散:一句话替换 + 既有测试的人群清单加一行,零新机制;扩人群自带误伤(fabricated-match)风险,按 devx 席提示先做一次测量再落。
推荐:A = 手册句换成哨兵要求的 house 句(连同 partial-conversion 允许变体一并写进手册)+ 哨兵人群纳入该手册文件;B = 只换句子不扩哨兵。荐 A;「A,但人群只加该文件、不加整个
.claude/**」也是一字母可裁的常规形状。本分析看不见什么:哨兵人群扩大后对
.claude/**其余文件是否误伤(未测量);对外发布的skills/**是否也存在同句残留、该不该同批纳入(未扫)。裁后执行(你不用管):A/B 均为 governed 面 —— draft PR + 人工合并,技能面复核档照既有条款;裁后由 skills 席派发,哨兵人群变更随同 PR 附测量。
Generated by Claude Code
huangyiirene commented
on Aug 22, 2026 CollaboratorMore actionsMaintainer ruling recorded (2026-08-22, supplemental to the decision-inbox digest, skills seat
session_01ApyDuQY2fkunMCqXiqvBhR)This card was omitted from the 46-card digest and presented separately with a four-axis recommendation; the maintainer ruled: 同意 (Option A).
Ruled: Option A — replace convention 5 in
.claude/skills/spec-property-retirement/SKILL.mdwith the house sentence the pin test requires ("Runos migrate meta --from <v>to list the mechanical edits for existing sources; apply them by hand."), drop the unreachable "仅当有 conversion 重写 sources" condition (no conversion rewrites sources), and carry the pin docblock's one allowed variant form into the skill so authors have both shapes. Additionally, extend the pin test's population (retired-key-migrate-sentence.test.ts) by exactly this one file — not all of.claude/**— so the skill and the pin can never silently disagree again.The skill is a governed face: draft PR + human merge, never flipped ready by an agent.
State:
needs-user-decisionremoved →pm:queue(ready for dispatch).
Generated by Claude Code
Claim: PM loop round 1
Session:session_01MsbKEG4LtERSLaDrbehM3e
Branch:claude/issue-10848-retirement-skill-house-sentence
Worktree:objectstack-issue-10848
Domain:domain:skills
File surface:.claude/skills/spec-property-retirement/SKILL.md+packages/spec/src/shared/retired-key-migrate-sentence.test.ts(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: claude-fable-5— this round'sdispatch-gates --tieron this surface: no path mandate (PM judgment call), and the pin test is flagged "Clause ② SUSPECT surface (packages/spec/src/**)"; PM sets the ceiling tier so the clause-② enqueue gate cannot bind below floor and the house-sentence semantics get the strongest editor.
Clause-②: no — the runtime contract's accept/reject behaviour is untouched; the pin test's population widens by exactly one file (a test-only pin), and the skill text is internal instruction prose.
Serial constraints cleared: spec lane scanned this round — 39 opendomain:speccards, zero assignees, none naming this test file; skills lane has no other in-flight claim on either file. Cross-domain file surface (spec territory) is authorized by the maintainer's 2026-08-22 ruling on this card ("extend the pin test's population by exactly this one file — not all of.claude/**"); spec seat is vacant (post #6017, 收班), so no in-flight overlap is possible. Housekeeping in the same claim stroke: the stalefindinglabel (grading debt — the card was graded to queue on 08-22) came off with thepm:queue→pm:dispatchedswap.
Generated by Claude Code
{ "issue": 10848, "status": "done", "branch": "claude/issue-10848-retirement-skill-house-sentence", "pr": "https://github.com/objectstack-ai/objectstack/pull/11092", "premise_still_valid": true, "summary": "Maintainer ruling (2026-08-22 Option A) implemented: convention 5 of .claude/skills/spec-property-retirement/SKILL.md now teaches the pin's house sentence (unreachable 'only when a conversion rewrites sources' condition dropped, still-true 'message never names the conversion id' tail kept) plus the pin docblock's one allowed partial-conversion variant — skill file 328 -> 334 lines, net +6, at the card's budget. The pin test's population widened by exactly that one file via a dedicated markdown-judged block (the .ts corpus walk cannot carry it: placeholder from-operand vs digits-only marker, code-span vs string-literal sentence anchoring, and reconstruct() drops markdown-bold lines); withdrawn-claim direction reuses WITHDRAWN_CLAIM verbatim. Four gate-forced extra files beyond the ruled two, each named by its gate's own remedy: scripts/check-cross-package-test-inputs.mjs + turbo.json (declared glob + TURBO_ROOT input for the new escaping read), .github/workflows/ci.yml (crosspkg parity entry), scripts/pm/check-skill-line-ratchet.mjs (ceiling 328 -> 334, maintainer ruling quoted in the PR per the ratchet's requirement). Governed surface: draft PR, human merge only. skip-changeset label applied and read back (union write after bot labels settled).", "tests": "Union at final head 61adbc2c. pnpm --filter @objectstack/spec test: 'Test Files 415 passed (415) / Tests 11065 passed (11065)' (widened pin file 12/12). Reverse verification from the committed state (tree-only restore of main's SKILL.md): exactly the 3 new tests red, 9 pre-existing green — observed direction red, as predicted; excerpt names 'to rewrite it automatically'. Fabricated-match census on the fixed skill: 4 command mentions, exactly 2 judged (the taught templates), 2 descriptive mentions correctly unjudged, 0 withdrawn-claim hits. Gate verdict lines: check-type-check-coverage --re-measure 'OK — 33 ledger entr(ies) re-measured, none above its recorded number'; check:test-typecheck 'OK — spec's test layer compiles'; cross-package-test-inputs 'OK: 13 package(s) read outside themselves, all declared'; ci-filter-parity 'OK: all 84 declared cross-package glob(s) covered'; pm-skill-ratchet '334 lines (ceiling 334)'; repo-wide pnpm lint exit 0 (76s, under the lock); 36-family derived battery all exit 0, every exit captured redirect-then-capture, never through a pipe. No ablation on this card (the reverse verification above is the counterfactual leg; no build-mediated fixture involved — the pin reads source text).", "open_questions": [], "out_of_scope_findings": ["filed as #11093: check-cross-package-test-inputs' resolver names a multi-line new URL() argument but not a multi-line resolve() literal, and the 'held by nothing' failure text's first remedy deletes a correct declaration (finding label, unassigned)"] }
Generated by Claude Code
Contract review (clause-② path limb) — PASS, then ACCEPT — PR #11092 (reviewed against GitHub and the diff, not the report).
Clause-② record first, per the protocol as amended today: the diff touches
packages/spec/src/**(the pin test), and amode:subagentdispatch cannot attest its serving tier, so the review chain applies regardless of the fable dispatch. Review performed in this stroke by this seat at the contract-review tier (machine reading this session:get_session→claude-fable-5=CONTRACT_REVIEW_TIER; self-review path per the 2026-08-21 relaxation). Conclusion: the contract increment is test-only — the pin's population widens by exactly the one ruled file; no runtime accept/reject behaviour moves and no public surface widens. Label parking is recorded as zero-width rather than churned: this governed PR never enqueues (human merge is its terminal gate), and this comment is the review record the chain requires.ACCEPT checklist: draft ✓ ·
Fixesfirst line ✓ · the two ruled files exactly as the Option-A ruling specifies (house sentence + the pin docblock's variant carried over; unreachable condition dropped; the still-true tail kept) ✓ · +6 net lines inside the ~6 budget, ratchet ceiling raised 328→334 with the maintainer's ruling quoted in the PR body — the only legal raise path ✓ · four gate-forced extra files, each named by a red gate's own remedy text and declared per the dispatch order ✓ · reverse verification in the falsifying direction (main's text against the widened pin: exactly the 3 new tests red, 9 old green, restored clean) ✓ · the triage-flagged fabricated-match census delivered (4 mentions / 2 judged / 2 descriptive correctly outside / 0 false positives) ✓ · spec suite 415 files / 11065 tests green including the widened pin ✓ · all check suites green on head61adbc2c✓ ·skip-changesetappropriate ✓.Governed surface ⇒ terminal posture: PR stays draft; review-request impossible (author identity), so the PR is assigned to the maintainer's account and listed under "awaiting a human merge".
Generated by Claude Code
os-dev-report (marker-restored copy — the sanitizer ate the HTML-comment marker on the previous report comment; JSON identical)
{ "issue": 10848, "status": "done", "branch": "claude/issue-10848-retirement-skill-house-sentence", "pr": "https://github.com/objectstack-ai/objectstack/pull/11092", "premise_still_valid": true, "summary": "Maintainer ruling (2026-08-22 Option A) implemented: convention 5 of .claude/skills/spec-property-retirement/SKILL.md now teaches the pin's house sentence (unreachable 'only when a conversion rewrites sources' condition dropped, still-true 'message never names the conversion id' tail kept) plus the pin docblock's one allowed partial-conversion variant — skill file 328 -> 334 lines, net +6, at the card's budget. The pin test's population widened by exactly that one file via a dedicated markdown-judged block (the .ts corpus walk cannot carry it: placeholder from-operand vs digits-only marker, code-span vs string-literal sentence anchoring, and reconstruct() drops markdown-bold lines); withdrawn-claim direction reuses WITHDRAWN_CLAIM verbatim. Four gate-forced extra files beyond the ruled two, each named by its gate's own remedy: scripts/check-cross-package-test-inputs.mjs + turbo.json (declared glob + TURBO_ROOT input for the new escaping read), .github/workflows/ci.yml (crosspkg parity entry), scripts/pm/check-skill-line-ratchet.mjs (ceiling 328 -> 334, maintainer ruling quoted in the PR per the ratchet's requirement). Governed surface: draft PR, human merge only. skip-changeset label applied and read back (union write after bot labels settled).", "tests": "Union at final head 61adbc2c. pnpm --filter @objectstack/spec test: 'Test Files 415 passed (415) / Tests 11065 passed (11065)' (widened pin file 12/12). Reverse verification from the committed state (tree-only restore of main's SKILL.md): exactly the 3 new tests red, 9 pre-existing green — observed direction red, as predicted; excerpt names 'to rewrite it automatically'. Fabricated-match census on the fixed skill: 4 command mentions, exactly 2 judged (the taught templates), 2 descriptive mentions correctly unjudged, 0 withdrawn-claim hits. Gate verdict lines: check-type-check-coverage --re-measure 'OK — 33 ledger entr(ies) re-measured, none above its recorded number'; check:test-typecheck 'OK — spec's test layer compiles'; cross-package-test-inputs 'OK: 13 package(s) read outside themselves, all declared'; ci-filter-parity 'OK: all 84 declared cross-package glob(s) covered'; pm-skill-ratchet '334 lines (ceiling 334)'; repo-wide pnpm lint exit 0 (76s, under the lock); 36-family derived battery all exit 0, every exit captured redirect-then-capture, never through a pipe. No ablation on this card (the reverse verification above is the counterfactual leg; no build-mediated fixture involved — the pin reads source text).", "open_questions": [], "out_of_scope_findings": ["filed as #11093: check-cross-package-test-inputs' resolver names a multi-line new URL() argument but not a multi-line resolve() literal, and the 'held by nothing' failure text's first remedy deletes a correct declaration (finding label, unassigned)"] }
Generated by Claude Code
- added a commit that references this issue
on Aug 22, 2026 os-dev-report (patch round — CI red on 61adbc2 repaired; supersedes the two report comments above)
{ "issue": 10848, "status": "done", "branch": "claude/issue-10848-retirement-skill-house-sentence", "pr": "https://github.com/objectstack-ai/objectstack/pull/11092", "premise_still_valid": true, "summary": "Patch commit 9219437d on the same branch repairs the one CI red on 61adbc2c: check-ci-filter-parity --self-test's rollback counterfactual is judged over the LIVE declaration table, so the gate-forced crosspkg entry moved its pinned measurement from 10 to 11. Diagnosis verified in source (the pin at the pre-#10015 rollback judges CROSS_PACKAGE_TEST_INPUTS as checked in; my .claude literal is under none of the rollback's roots). Fix follows the file's exact-count-pin convention and STRENGTHENS the shape: count moved to 11 with the measurement explained in the comment, plus a new by-name assertion that the eleventh member is precisely the #10848 declaration; the closing summary line updated to match. Gap named by the coordinator also closed: the --self-test leg is now part of this card's run record, not only the main check. All earlier deliverables unchanged (skill +6 lines at budget, pin widened by the one ruled file, both directions reverse-verified, skip-changeset read back). PR stays draft — governed face, human merge only.", "tests": "At head 9219437d: node scripts/check-ci-filter-parity.mjs --self-test — '✓ check-ci-filter-parity --self-test: 37 assertions — ... the pre-#10015 rollback uncovering the ten it fixed plus #10848's one, and the CI wiring read out of lint.yml.' (was 36 assertions; the by-name pin adds one). Main check — 'OK: all 84 declared cross-package glob(s) (73 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.' dispatch-gates re-derived over the 7-path change set: no newly named families beyond the scripts/** set already run; re-ran entry-guard, parse-guard, cross-package-test-inputs, pm-skill-ratchet, required-contexts, nul-bytes — all exit 0 — plus repo-wide pnpm lint exit 0 (52s under the lock). Exits captured redirect-then-capture throughout.", "open_questions": [], "out_of_scope_findings": ["already filed as #11093 in the prior round (cross-package scanner multi-line resolve asymmetry); nothing new this round"] }
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Found while correcting the four docs-site pages that said
os migrate meta --fromrewrites your sources (PR #10847). That PR closed the hand-written docs residue; this is the one remaining prescriptive carrier, and it is upstream of all of them — it tells future agents to author the false sentence again.The defect
.claude/skills/spec-property-retirement/SKILL.md:150, in the "guidance 字符串怎么写" convention list that every newretiredKey()tombstone is authored from:That sentence is the withdrawn claim, twice over:
os migrate meta --fromrewrites no file. It replays the ADR-0087 chain over the loaded stack in memory and prints the mechanical edits. BothwriteFileSynccalls inpackages/cli/src/commands/migrate/meta.tsare guarded byif (flags.out)(:347,:394-396), so the only file it writes is the--outJSON snapshot; the command header at:155-157declines the AST rewrite as "unsafe and lossy". The in-place codemod is feat(cli):os migrate meta --write— the AST codemod that rewrites authored sources for the mechanicalappliedset (v18) #9591 (v18,pm:on-hold), unbuilt.The skill's own condition — "仅当有 conversion 重写 sources" — describes a capability that does not exist for any conversion, so the guarded branch is unreachable as written.
Why this is worse than a stale doc line
packages/spec/src/shared/retired-key-migrate-sentence.test.tsis a class pin that holds both directions. From its docblock:The house sentence it requires:
But the pin's population is
packages/spec/srcplus one occurrence inpackages/lint/src/validate-expressions.ts..claude/**is outside it. So the skill and the pin now disagree, and the skill is the one agents read first:An agent retiring a property follows the skill, writes the prescribed sentence into a new tombstone, and the pin reds the PR. Best case that costs a round. Worst case the agent reads a red pin as the broken thing and weakens it to match the skill — which is how the withdrawn promise gets restored across ~90 shipped prescriptions that #9529 cleaned.
Evidence the skill is the last live prescriptive carrier
Scanning the tree for
to rewrite it automatically/to rewrite existing sources automatically:packages/spec/src/shared/retired-key-migrate-sentence.test.tspackages/spec/CHANGELOG.md,packages/lint/CHANGELOG.md,packages/platform-objects/CHANGELOG.md,examples/app-todo/CHANGELOG.mddocs/qa/platform-checklist/areas/cli.json.claude/skills/spec-property-retirement/SKILL.md:150packages/spec/srccarries no shipped prescription with the withdrawn wording; that half is clean.cli.jsonrevision 3 states "the only other carriers are the deliberate pin test and four CHANGELOGs" — the skill was not counted in that census, which is likely why it survived.Suggested disposition
Replace convention 5 with the house sentence the pin requires, and drop the "仅当有 conversion 重写 sources" condition (no conversion rewrites sources). The pin's docblock also documents the ONE allowed variant, by shape, for conversions covering only part of a value — worth carrying into the skill so authors have both forms.
⛔ Not actionable by a dev agent unassisted:
.claude/**is governed, human-merge-only. Filed for maintainer routing rather than fixed in PR #10847.Refs: PR #10847 (the docs-site half) · #9529 (the class-wide reword) · #6856 (the original two-verb split) · #9591 (the unbuilt in-place codemod) ·
packages/spec/src/shared/retired-key-migrate-sentence.test.ts(the pin).Generated by Claude Code