Skip to content

docs(agents): owe the unit vitest tier locally on packages/cli cards, declare integration to CI - #15551

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-14539-cli-definition-of-done-tiers
Sep 5, 2026
Merged

docs(agents): owe the unit vitest tier locally on packages/cli cards, declare integration to CI#15551
os-zhuang merged 3 commits into
mainfrom
claude/issue-14539-cli-definition-of-done-tiers

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #14539
Fixes #15146
Fixes #15323

What changed

One file, .claude/agents/os-dev.md, four spots, rules-only wording in the file's own Chinese. Two spots carry the packages/cli tier clause (first card); the third carries the never-end-a-turn-to-wait rule (second card, a patch round on this PR); the fourth, in step ② of 本地验证范围, says where the local suite list comes from (third card, a second patch round). The tier split itself is the maintainer ruling recorded on #13504 (2026-09-01); the sentence is the skills seat's, and the proposal on the card was used as input, not copied.

Spot 改前 改后
本地验证范围, step ② (line 148) ② 受影响包自己的 pnpm test / pnpm typecheck,用 --filter 圈定;③ 派发词点名的… ② 受影响包自己的 pnpm test / pnpm typecheck,用 --filter 圈定 —— packages/cli 只欠 unit 层(见「Definition of done」的测试条);③ 派发词点名的…
Definition of done, 测试 bullet (line 311) 测试:新增/更新覆盖;跑受影响包的 pnpm test / pnpm typecheck,为报告留真实输出(范围按「本地验证范围」圈定)。 same sentence, then: **packages/cli 的卡本地只欠 unit 层**:pnpm --filter @objectstack/cli exec vitest run --project unit;integration 层声明给 CI(pnpm test 在那里跑两层)。只有 diff 碰到 integration 层文件、spawn 入口(bin/、test/helpers/serve-process.ts)或 driver/kernel 启动路径时才本地跑 --project integration,并在报告里说明;否则报告写「integration 层已声明给 CI」。层是测出来的,不是列出来的:新增会 spawn CLI 或启动 driver 的测试,按 packages/cli/vitest-tiers.ts 的判据自动落 integration 层;分区 pin test/vitest-tiers-partition.test.ts 在某个测试文件两层皆无或皆有时变红。
资源纪律, rule 7 排队不是停摆 (line 116, the rule's last line) …沉默是唯一错误答案。 same, then: **等锁、门禁批或任何慢步骤,一律在本轮内前台阻塞**:锁脚本自己会等,Monitor 只配 until 循环的阻塞用法;⛔ 永不为了等而结束回合。回合只有两种合法收尾:终报 JSON,或 blocked / needs_decision 报告;「我会在 X 完成后继续」不是收尾,是停摆。判据,写与读报告同一条:最后一句是意图而不是结果 ⇒ 该 dev 已停摆,不是已完成。
本地验证范围, step ② (lines 151–153), the affected-package clause ② 受影响包自己的 pnpm test / pnpm typecheck,用 --filter 圈定 —— packages/cli 只欠 unit 层(见「Definition of done」的测试条); ② 受影响包自己的 pnpm test / pnpm typecheck,用 --filter 圈定,受影响包 = CI 会测的包,清单读 TURBO_SCM_BASE="$BASE" pnpm exec turbo ls --affected,⛔ 不按「我改了哪些包」猜:普通 import 被改模块的包也在清单里,欠它们的测试;packages/cli 只欠 unit 层(见「Definition of done」测试条);

The Definition-of-done bullet carries the full rule; the earlier step carries a one-line qualifier that points at it, so the two cannot disagree. No dates, quotations, issue numbers or incident text went into the file.

The third edit extends rule 7 of 资源纪律 — the paragraph a dev reads before waiting on os-verify-lock.sh — with the three facts of the second card: waiting on the lock, a gate batch or any slow step is a foreground block inside the turn (the lock script waits by itself; Monitor only in its blocking until-loop form), never a reason to end the turn; a turn has exactly two legal endings, the final report JSON or a blocked / needs_decision report, and "I will continue once X finishes" is a stall, not an ending; the criterion is one line shared by the report's writer and reader — a last sentence that is an intention rather than a result means the dev stalled, not completed. Three new lines, every one under 120 bytes.

The fourth edit gives step ② its derivation source: the affected-package list is the population CI will test, read from TURBO_SCM_BASE="$BASE" pnpm exec turbo ls --affected ($BASE is the merge base the file already anchors every "where did I start" question to), never guessed as "the packages I changed"; a package that consumes the changed module by an ordinary import is in that list and owes its run. This is CI's own reading: its step computes SCM_BASE=$(git merge-base refs/remotes/origin/$BASE_REF HEAD) and runs TURBO_SCM_BASE="$SCM_BASE" pnpm exec turbo ls --affected. Verified on this tree (turbo 2.10.10): against the merge base this PR lists 0 no packages (pnpm9) — it touches no package; a probe appending a blank line to packages/lint/package.json lists 53 packages (pnpm9) with @objectstack/lint among them (restored; git diff HEAD empty afterwards); turbo's default base, the local main ref, was stale in this shared checkout and listed 79 packages — the wrong list, which is why the rule pins the merge base. Paid inside the ceiling by tightening filler in the same paragraph (15 lines before and after, 1692 → 1715 bytes): 全枚举出来本地跑 55+ 个 —— 无论如何 CI 都会把农场恰好跑一遍全枚举本地跑 —— CI 会把农场跑满; 哪怕当天仔细取的清单也会漏族,点名的跑绿之后当天取的也会漏族,点名的跑绿后; 会算到你头上(实测一次三个),而它算到你头上,而它; 于是全绿、无人察觉,只有报告里那份「我跑了哪些门禁」悄悄变成假的全绿无人察觉,报告里那份「我跑了哪些门禁」却是假的; 脚本会**响亮拒绝**而不是给错清单 —— 照它说的加深即可脚本**响亮拒绝**而非给错清单,照它说的加深; 你的 diff 确实触及的,并在报告里点名新增项你的 diff 触及的,报告里点名新增项; 安全的另一半归 PM,在你报告之后读真实门禁 job 结论另一半归 PM,在你报告后读真实门禁结论; 它们是你仍然欠的便宜一半它们仍是你欠的便宜一半; (见「Definition of done」的测试条)(见「Definition of done」测试条). Every ⛔ (2 → 3) and every command in the paragraph is kept; check:cross-package-test-inputs is not touched — it covers filesystem escapes and is right to.

One deliberate deviation from the dispatch's literal: the predicate holder is named as packages/cli/vitest-tiers.ts, not vitest.config.ts. On main the config imports integrationTestFiles() from that module and its header states there is no list to keep in step — the population is derived at config load, so the rule says the tier is measured, not listed.

Mechanism assumptions, verified on origin/main at 85a245988

  • packages/cli/vitest.config.ts still defines test.projects = unit and integration, both extends: true; INTEGRATION_FILES = integrationTestFiles(__dirname) is derived from vitest-tiers.ts.
  • packages/cli/test/vitest-tiers-partition.test.ts exists; its header holds coverage (a file in no project is never run; a file in both runs twice), that the derivation reaches vitest, and the predicate against fixture sources.
  • The local command in the rule runs. Executed once through the shared verify lock (OS_VERIFY_LOCK_SLOT=issue-14539, acquired after 0s, --maxWorkers=2, after building the 58-package dependency closure in the same hold — a shared-box reading, not an idle-machine promise):
    pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 → exit 0; wall-clock 194s (3m14s) by shell timestamps, vitest Duration 188.70s (transform 24.77s, import 249.51s, tests 100.86s); Test Files 172 passed (172); Tests 2283 passed | 6 expected fail (2289). Lock verdict: VERDICT command-exit 0 · held the lock 571s (9m31s) · waited 0s (build closure 377s + unit tier 194s in one hold). Against the card's readings on the same class of box: 4m28s for the fast tier, 17–24 min for the whole suite.

Ratchet

.claude/agents/os-dev.md: 459 → 466 lines after the first commit, 466 → 469 after the patch round, 469 → 469 after the second patch round (paid in place), against ceiling 469 (headroom 10 → 3 → 0 → 0). No ceiling change. Every added line is at most 119 bytes; the two lines over 120 bytes in the file predate this PR and are untouched.

Gate lines: ✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 466 lines (ceiling 469; headroom 3). on ee9f742a1; ✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 469 lines (ceiling 469; headroom 0). on 3a0459166 and again on 5e1e84ac9.

Gates, run on ee9f742a1 after the final commit

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths; change set from the merge base) derived 16 commands; all 16 ran with exit 0, plus check:pm-governed-prose and check:corpus-claim-drift named by the dispatch. Reconciliation: ✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.

  • ✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model
  • ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.
  • ✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files
  • ✓ check-skill-id-lint: 24 file(s) clean (pattern /#[0-9]{3,}/g).
  • OK check-refd-timer-probe: 5930 source file(s) swept; …
  • ✓ check-watch-hint-literal: 53 declaration(s) across 4 rostered name(s) …
  • check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.
  • check-nul-bytes: OK (scanned 7524 text file(s) -- 7524 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
  • node scripts/pm/check-governed-merges.mjs --test .claude/agents/os-dev.md → exit 3, ⛔ GOVERNED — a human merge is the review record for this PR. This PR stays a draft; landing is the maintainer's, by hand.

Patch round, run on 3a0459166 after the second commit (same single-file change set, so the derived union is unchanged): check-skill-line-ratchet (above), ✓ check-skill-id-lint: 24 file(s) clean (pattern /#[0-9]{3,}/g)., ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others., ✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files, check-nul-bytes: OK (scanned 7524 text file(s) … no raw ASCII control bytes)., ✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model — all exit 0.

Second patch round, run on 5e1e84ac9 after the third commit (same single-file change set): check-skill-line-ratchet (above), ✓ check-skill-id-lint: 24 file(s) clean (pattern /#[0-9]{3,}/g)., ✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others., ✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files, check-nul-bytes: OK (scanned 7524 text file(s) … no raw ASCII control bytes)., ✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model — all exit 0.

Executable criterion from the card, on HEAD: git grep -n -e '--project unit' HEAD -- .claude/agents/os-dev.md.claude/agents/os-dev.md:313, inside the Definition-of-done 测试 bullet.

Not here

No edit to AGENTS.md (a separate open PR holds that file), packages/cli/**, skills/** or .claude/skills/**. skip-changeset: .claude/** publishes nothing from any package.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox


Generated by Claude Code

…rds, declare `integration` to CI

The dev-agent Definition of done told every card to run the affected
package's `pnpm test`; for `packages/cli` that is the whole two-tier
suite. The 测试 bullet now carries the `packages/cli` clause: the local
step is `--project unit`; `--project integration` runs locally only when
the diff touches that tier, a spawn entry or a driver/kernel boot path,
and is otherwise declared to CI, where `pnpm test` runs both tiers; a new
spawning or booting test lands in the integration tier by the config's
own predicate, and the partition pin reds when a file is in neither or
both. The earlier same-meaning step in 本地验证范围 carries a one-line
qualifier pointing at that bullet, so the two never disagree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
…he lock, a gate batch or any slow step

Rule 7 of 资源纪律 (排队不是停摆) gains its missing half: waiting on the
verification lock, a gate batch or any slow step is a foreground block
inside the turn (the lock script waits by itself; Monitor only in its
blocking until-loop form), never a reason to end the turn. A turn has
two legal endings, the final report JSON or a blocked/needs_decision
report; "I will continue once X finishes" is a stall, not an ending.
The criterion is shared by the report's writer and its reader: a last
sentence that is an intention rather than a result means the dev has
stalled, not completed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
…against the merge base, not from "the packages I changed"

Step ② of 本地验证范围 now says what the affected-package list IS: the
population CI will test, read from `TURBO_SCM_BASE="$BASE" pnpm exec
turbo ls --affected` (the same merge-base reading CI takes), never
guessed as "the packages I changed" — a package that consumes the
changed module by an ordinary import is in that list and owes its run.
Paid inside the file's ceiling by tightening filler in the same
paragraph (an incident aside, a stale count, connective words); every
prohibition and every command in the paragraph is kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@os-zhuang
os-zhuang marked this pull request as ready for review September 5, 2026 01:28
@os-zhuang
os-zhuang enabled auto-merge September 5, 2026 01:28
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 7087f99 Sep 5, 2026
30 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14539-cli-definition-of-done-tiers branch September 5, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment