Repository navigation
Description
Filed by the domain:devx dev working #8024 (PR #8109). Out of scope there — that card's file surface is scripts/check-skill-frame-freshness.mjs, and this is a workflow change. #8024 remains open until its own PR lands.
The observation
check:skill-frame-freshness is deliberately not wired into lint.yml, and the script's header gives the reason, which is correct as far as it goes: on a fresh CI merge ref the scan is a tautological green, and on any PR that legitimately edits the decision frame it would be a false red, since "differs from main" is that PR's whole purpose.
But package.json runs the gate in two halves:
"check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs"
The reasoning above applies only to the second half. The --self-test half is a pure self-consistency check: it manufactures a specimen from the repo's own files, drives it through temp git repos, and touches neither the network nor origin/main. Nothing about it is tautological on a merge ref, and nothing about it goes red on a PR that edits the frame on purpose (it re-derives from whatever the frame currently is).
So the half that CANNOT run in CI is keeping the half that CAN out of it.
Why it is worth a card
Measured while fixing #8024: the fixture had stranded twice, not once.
| documents | old hand-copied fixture |
|---|---|
pre-rewrite (ab19075^) |
RED — its mention rule spelled 沿三条固定评估轴, missing the bare 三条评估轴 mention in the same file, so the specimen was incoherent and the independence case failed |
today (HEAD) |
RED — the axis-line rule matched nothing after the 2026-08-12 rewrite; this is what #8024 reports |
The first stranding was never noticed by anyone. Both would have been caught the day they landed by running the self-test half in CI. The failure mode is the expensive kind: the gate stays in the gate list reading as coverage while protecting nothing.
Suggested shape
Add a step to lint.yml running node scripts/check-skill-frame-freshness.mjs --self-test on its own — not pnpm check:skill-frame-freshness, which would drag the scan in with it. Worth checking whether other gates in the repo have the same split shape (a CI-hostile scan hiding a CI-safe self-test behind &&); if so this is a family, not a single step.
Establishment
Measured in a clean worktree at d91c558; the pre-rewrite half by reconstructing the three framework documents and both scripts at ab19075^ into a temp tree and running the self-test there. The exact dating of the first stranding could not be established — this clone's history is grafted at f7dceed, so git log -S on the mention attributes it to the graft boundary rather than to a real introducing commit.
Generated by Claude Code
Blocked-by: #8024
Activity
hotlong commentedon Aug 12, 2026
Triage: lands in
domain:devx(.github/workflows/lint.yml); markedpm:blockedwithBlocked-by: #8024appended to the body (line added at triage).Rationale for the block: by this card's own table the self-test is RED at current HEAD — that is exactly what #8024 / PR #8109 fixes — so wiring the step into
lint.ymlbefore that lands would redden CI for every PR in the repo. When #8024 closes, the unlock scan returns this card topm:queue; re-verify at that moment that PR #8109 did not already add the CI step itself.Scope note for whoever takes it: the "same split shape elsewhere" question in Suggested shape is a bounded check worth doing in the same PR, but new gate wiring beyond
--self-teststeps is out of scope — adding a scan half to CI would recreate the false-red problem the script header warns about.Type: Task.
Generated by Claude Code
hotlong commentedon Aug 12, 2026
Claim: PM loop round 7
Session:
session_01PaiisQMhsYxwa5ZX6Mfmv2Branch:
claude/issue-8110-skill-frame-selftest-ciWorktree:
objectstack-issue-8110Domain:
domain:devxFile surface:
.github/workflows/lint.yml(+package.jsononly if the survey below justifies it). ⛔ No.claude/skills/**/skills/**— those are the skills seat's (#7623), and this card does not need to touch them. ⛔ No new scan-half wiring.Container & model:
XS,mode:cloud,model: opus解锁已核实 — 两条读数,都不靠工具返回值
check:skill-frame-freshnessis red onmain— self-test fixture drifted after the principles-only rewrites, so the gate never scans #8024 已关闭(2026-08-12 16:34:55Z),closed_by_pull_requests恰好一条:PR fix(scripts): derive the skill-frame-freshness self-test fixture from the live documents #8109,状态MERGED。origin/main上:旧的手抄 fixture 模式每个方案先问在scripts/check-skill-frame-freshness.mjs里已 0 命中 —— 与 fix(scripts): derive the skill-frame-freshness self-test fixture from the live documents #8109「fixture 改为从活文档推导」一致。所以本卡表格里那行「today (HEAD) — RED」已不再成立,self-test 现在是绿的,可以进 CI 而不会立刻把全仓染红。分诊要求的复验,结果是「没有」
分诊留言要求解锁时确认 「PR #8109 是不是顺手把 CI step 加了」。没有加。 在
origin/main上机械查过:.github/workflows/lint.yml里skill-frame只出现 一次,在:1082的run: pnpm check:skill-frame-sync;.github/workflows/目录扫一遍,只有lint.yml这一处提及;package.json:44的check:skill-frame-freshness仍是--self-test && <scan>的两半形。⇒ 本卡前提完整成立,可派。
一条派发前就能看见的、会影响调查结论的事实
:1082那行说明姊妹门check:skill-frame-sync在 CI 里跑的是完整两半(它的 scan 半边不惧 CI)。所以它不属于本卡要找的那个家族 —— 家族的判据是「scan 半边进不了 CI,于是把能进的 self-test 半边一起挡在外面」,而不是「脚本里有&&」。派发令里已把这条写成裁决,避免 dev 把&&当成家族标志去扫。Serial constraints cleared:无在飞 PR 触及⚠️ 但
.github/workflows/lint.yml(#8142 是纯 docs,#7960 是docs/adr/+scripts/check-adr-anchors.mjs)。lint.yml是共享基础设施,动手前须重读目标 step 前后的块 —— 本轮已有多张卡在scripts/与 workflow 面上并行。Generated by Claude Code
hotlong commentedon Aug 12, 2026
{ "issue": 8110, "status": "done", "branch": "claude/issue-8110-skill-frame-selftest-ci", "pr": "https://github.com/objectstack-ai/objectstack/pull/8171", "premise_still_valid": true, "summary": "Added one step to lint.yml's typecheck job, next to check:skill-frame-sync, running only `node scripts/check-skill-frame-freshness.mjs --self-test` (never `pnpm check:skill-frame-freshness`, which would drag the CI-hostile scan half in). 36 added lines, one file, no package.json change: a dedicated script name would be a second hand-kept spelling of the same command with nothing to gain, and the repo already invokes check-dev-prereqs.mjs directly with `node` for the same 'do not take the other half' reason. All four PM hypotheses were measured. A: hermetic — 12/12 green in a `git clone --depth 1` carrying one commit and NO refs/remotes/origin/main, so no fetch-depth or checkout change is needed (and the typecheck job already checks out fetch-depth: 0 anyway). B: cheap — ~1.3s depth-1, ~2.0s full worktree, and it needs no node_modules at all. C: the family is non-empty and has exactly ONE other member, check:dev-prereqs, filed as #8170 and deliberately not fixed here. D: needs a correction and the PR body states it — 'a frame-editing PR does not redden it' is true for wording but too broad as stated. A structural change (axis added/removed, declaring sentence reworded past the shared anchors) DOES redden it; measured, and the same edit reddens check:skill-frame-sync directly above with the same diagnosis, which the self-test's own error names as the thing to fix first. So the step adds no failure class its neighbour lacks — 'nothing can redden it alone' is the accurate claim, not 'nothing can redden it'. Also worth carrying: `check:skill-platform-checklist`-style survey work must strip workflow YAML comments before matching, or a comment explaining why a gate is NOT wired reads as wiring.", "tests": "node scripts/check-skill-frame-freshness.mjs --self-test → '✓ check-skill-frame-freshness self-test: 12 cases pass.' (exit 0, real 0m1.958s in the worktree; real 0m1.258s in a --depth 1 clone with no origin/main ref, `git rev-parse --is-shallow-repository` = true). Adversarial D-tests, all in a scratch clone, never on the PR's file surface: (1) reword an axis body line in .claude/agents/os-dev.md → self-test exit 0, check-skill-frame-sync exit 0 ('4 copies ... structurally isomorphic across 3 files'); (2) reword axis prose in all three frame files at once → both exit 0; (3) reword os-dev.md's declaring sentence → self-test exit 1 with 'the REAL document does not parse ... this is a `pnpm check:skill-frame-sync` failure first', and check-skill-frame-sync.mjs exit 1 with 'the declaring sentence anchor matched 0 time(s), expected exactly 1'. Family survey, mechanical: 62 root check:* scripts cross-referenced against all 24 .github/workflows/*.yml with comment lines stripped → 3 unwired (skill-frame-freshness, dev-prereqs, platform-checklist); widened to all 63 scripts/**/*.mjs mentioning --self-test → no further candidates. check:dev-prereqs measured: --self-test exit 0 in 0.120s on an unbuilt checkout with no node_modules (16 cases), scan exit 1 ('The workspace is not built ... 67 of 67 workspace packages'). check:platform-checklist rejected: its scan is CI-capable, it is out by an explicit maintainer policy decision recorded in lint.yml, not by CI-hostility. Gates re-derived with `node scripts/pm/dispatch-gates.mjs .github/workflows/lint.yml` and run: check:changeset-gate-self-tests (118+153+117 assertions), check:node-version, check:nul-bytes (7422 files, 0 control bytes), check:required-contexts, check:shard-attestation, check:type-check-coverage, check:workflow-status-functions, node scripts/check-changeset-no-major.mjs ('✓ This diff introduces no `major` bump.') — all exit 0. check:type-check-debt NOT run and this is deliberate: its --re-measure half refuses without the full workspace built ('55 workspace dependencies of the ledgered packages have no built type entry point on disk'), and building 77 packages to re-measure TS debt for a diff that touches no TS source is not proportionate; its --self-test half is shared with check:type-check-coverage, which passed. YAML validated by parse (41 steps in typecheck; the new step's run is exactly the self-test command). skip-changeset applied by me and read back after bots settled: labels are ci/cd, size/s, skip-changeset. CI on the PR: in_progress at report time — not waited on, per contract.", "open_questions": [], "out_of_scope_findings": [ "filed as #8170: `check:dev-prereqs --self-test` runs nowhere in CI — the second and only other member of this card's split-gate family (self-test green in 0.12s on an unbuilt tree; scan hard-red there, so the scan correctly stays out and takes the self-test with it). Includes the full enumeration method and the two rejected candidates, so the negative half of the survey is reviewable." ] }Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 12, 2026
ACCEPT — PR #8171,已翻 ready,auto-merge 已挂(squash)。23 条 check 零 failure,其中
TypeScript Type Check(新 step 所在的那个 job)18:53:24Z 绿 —— 这就是新 step 在 CI 里真的跑过且通过的直接读数,不是推断。裁决对账(读 diff)
--self-test半边run: node scripts/check-skill-frame-freshness.mjs --self-testpnpm check:skill-frame-freshnessnode直调,注释里点名理由,并类比仓库既有的check-dev-prereqs.mjs同款处理.github/workflows/lint.yml,package.json未动.claude/skills/**/skills/**四条机制假设,两条修正了我
git clone --depth 1、没有refs/remotes/origin/main的树里 12/12 绿。⇒ CI 不需要fetch-depth或 checkout 调整。这正是我担心的浅克隆敏感性,被直接排除。node_modules。check:dev-prereqs(self-test 在未构建的树上 0.12s 绿,scan 在那里硬红 —— 所以 scan 正确地留在 CI 外,并把 self-test 一起带了出去),已另立check:dev-prereqs --self-testruns nowhere in CI — the second and only other member of #8110's split-gate family #8170,⛔ 未在本 PR 内顺手修。枚举方法可复核:62 个根check:*× 24 份 workflow(剥掉注释行后匹配)→ 3 个未接线;再放宽到 63 个提及--self-test的scripts/**/*.mjs→ 无新候选。两个被排除的候选各写了排除理由(check:platform-checklist的 scan 其实能在 CI 跑,它在外面是维护者的明确政策决定,不是 CI-hostility)。负结论也可审,这是我要的那一半。check:skill-frame-sync以同样的诊断先红,而 self-test 的失败信息本身就写着「this is apnpm check:skill-frame-syncfailure first」。⇒ 准确的说法是「它不会单独把 PR 变红」,不是「它不会变红」。本 PR 不引入邻居没有的失败类别 —— 这个结论比我原来的假设更强,也更真。一条值得带走的普查陷阱
dev 顺带记了一条:做 workflow 接线普查时必须先剥掉 YAML 注释再匹配 —— 否则一段「解释某门为什么没有接线」的注释会被读成「已接线」。本 PR 自己就加了 30 行这样的注释,是这条陷阱的活体样本。已记进座位贴 pitfalls。
一处我看了但不拦的
30 行注释配 1 行 step,比例悬殊。但这里的非显然决定恰恰是「省掉哪一半」 —— 而 #8024 的教训正是「门留在门清单里读起来像覆盖、实际什么都不保护」。把省略写成决定而不是遗漏,值这个篇幅。
Generated by Claude Code
hotlong commentedon Aug 12, 2026
Note for the next surveyor (from #8229, recorded by the triage seat) — 本评论来自分诊座位 Routine.
The survey method described on this card (enumerate root
check:*scripts, grep.github/workflows/**with comment lines stripped, treat unmatched as unwired) has a measured false-positive now that split gates land as directnode scripts/<file> --self-teststeps rather thanpnpm check:<name>invocations: re-running the survey verbatim reportscheck:skill-frame-freshness(wired since PR #8171) andcheck:dev-prereqs(wired in PR #8227) as unwired.Refined criterion: a script counts as wired if EITHER its npm script name OR
scripts/<its file>appears in an uncommented workflow line. Deliberate exclusions as of 2026-08-12:check:platform-checklist(out by policy), and the scan halves of the two split gates above (their workflow steps intentionally invoke only--self-test).Full analysis: #8229 (closed as delivered by this note).
Generated by Claude Code
ci(lint): run the skill-frame freshness gate's self-test half in CI (o…