From e44ca48f7bc7787def023f67253126584cd1dcdb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 20 Aug 2026 19:35:30 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix(=E4=BC=9A=E5=AE=A1):=20tighten=20Record?= =?UTF-8?q?=20protocol=20baseline=20and=20seat=20lifecycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the internal dyro-board Record protocol so a board run must declare a production-line baseline, close prior P0/P1, re-check seat claims, wait before marking 逾期未交, and record test commands from raw output. Keep the wrapper as a pointer only. Co-authored-by: Dandre Yang --- CHANGELOG.md | 6 +++ .../integrations/assets/dyro-board/SKILL.md | 35 +++++++++++-- .../assets/dyro-review-board/SKILL.md | 7 ++- tests/test_integrations.py | 50 +++++++++++++++++++ tests/test_seats.py | 14 ++++++ 5 files changed, 106 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b097b..c93dc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- Tighten the internal `dyro-board` Record protocol: declare a + production-line baseline (not `master`/`main`), close prior P0/P1, + re-check seat claims before arbitration, wait ≥ 10 minutes before + marking a seat `逾期未交`, and record the full test command plus raw + output. The `/dyro-review-board` wrapper still follows that file only + and refuses same-turn delivery. Version unchanged. - Hide the internal `dyro-board` protocol from slash / `$` skill pickers. `/dyro-review-board` is the only human-facing 会审 command. The protocol asset still installs as a non-invocable companion of `review-board` (and diff --git a/src/dyro/integrations/assets/dyro-board/SKILL.md b/src/dyro/integrations/assets/dyro-board/SKILL.md index 780f32d..68d4fc5 100644 --- a/src/dyro/integrations/assets/dyro-board/SKILL.md +++ b/src/dyro/integrations/assets/dyro-board/SKILL.md @@ -26,13 +26,40 @@ Do not scan personal host skill directories. ## Record protocol -1. One shared review file. Prefer - `docs/reviews/YYYY-MM-DD--adversarial-board.md`. +0. Before review starts, declare the diff baseline per repo: + ` `. Default the **production line** (usually + `origin/release`). **Must not default to `master`/`main`.** If + `master`/`main` has diverged from production, record how many commits. + If a prior board on the same development line used a different + baseline, say why. +0b. Search for prior boards on the same development line / same topic. If + one exists, the record **must** include a section that closes each + prior P0/P1 as `已闭环` / `未闭环` / `须人工核` with evidence. +1. One shared review file. Prefer the repo's **existing** + review-directory convention. If none, + `docs/reviews/YYYY-MM-DD--adversarial-board.md`. If a prior + board exists on this line, use the same directory. 2. Each reviewer writes only in a signed section. Do not edit, rewrite, or summarize another section. -3. Source code and live contracts outrank plans and prior reviews. +3. Source code and live contracts outrank plans, prior reviews, **and + same-batch seat opinions**. The chair must re-check every seat claim + before it enters final arbitration. Unchecked items must be marked + `未复核·转述`. 4. Unprovable claims are `须人工核`. -5. Final arbitration deduplicates, resolves conflicts, and emits P0/P1/P2, +5. Empty `ListAgents` / `TaskOutput` is **not** enough to declare a seat + dead — those APIs can be empty while the seat is still running. The + chair must set an explicit wait window (≥ 10 minutes) and **must not + publish arbitration** during that window. +6. A seat that misses the window is `逾期未交`, not "lost". Record who + covers that dimension. +7. Seat reports that arrive after arbitration require a **revised** + record that lists what changed. No silent merge into the published + verdict. +8. Any test verdict must record: the full command, the summary line from + raw output, and whether a pipeline/redirect hid the exit code. Exit + code or a task-completed notification alone must not assert pass. + Unrun targets are `未执行` plus why. +9. Final arbitration deduplicates, resolves conflicts, emits P0/P1/P2, Go/No-Go, and the next human Dyro command if one already exists. ## Authority diff --git a/src/dyro/integrations/assets/dyro-review-board/SKILL.md b/src/dyro/integrations/assets/dyro-review-board/SKILL.md index b835312..b175ef7 100644 --- a/src/dyro/integrations/assets/dyro-review-board/SKILL.md +++ b/src/dyro/integrations/assets/dyro-review-board/SKILL.md @@ -17,7 +17,9 @@ metadata: 命令名 `/dyro-review-board`、内部 id `dyro-board`、`dyro integration status board`、记录文件名里的 `adversarial-board` 都可以说。 -内部 id 仍是 `dyro-board`,不要改,也不要另写一套协议。 +内部 id 仍是 `dyro-board`,不要改,也不要另写一套协议。 Follow +`dyro-board` exactly; do not invent a second protocol. The protocol now +requires baseline + seat lifecycle. ## Same-turn delivery @@ -33,7 +35,8 @@ If this turn also asks to 提交、commit、push、发布、publish、`task merg - `dyro-board/SKILL.md` next to this skill's parent directory - the installed `dyro-board` skill already on this host 2. If none of those can be read, stop. Say 会审协议未安装;下一步是 `dyro integration status board`. -3. Follow that file exactly. Do not start 会审 from this wrapper alone. +3. Follow `dyro-board` exactly. Do not invent a second protocol. Do not + start 会审 from this wrapper alone. 4. In chat, call the activity 会审 or 对抗审查. Call the output a 记录. ## Say this to developers diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 629ae2d..71f5b20 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -296,12 +296,62 @@ def test_review_board_is_the_only_invocable_board_slash(self) -> None: self.assertIn("user-invocable: true", review) self.assertIn("disable-model-invocation: true", review) self.assertIn("dyro-board/SKILL.md", review) + self.assertIn("Follow `dyro-board` exactly", review) + self.assertIn("do not invent a second protocol", review.lower()) + self.assertIn("baseline + seat lifecycle", review) self.assertIn("Refuse those delivery actions", review) self.assertNotIn("--yes", review) self.assertNotIn("board", USER_INTEGRATION_CHOICES) self.assertIn("review-board", USER_INTEGRATION_CHOICES) self.assertIn("board", INTEGRATION_CHOICES) + def test_board_record_protocol_requires_baseline_and_seat_lifecycle( + self, + ) -> None: + board = (manager._asset_root("board") / "SKILL.md").read_text( + encoding="utf-8" + ) + review = (manager._asset_root("review-board") / "SKILL.md").read_text( + encoding="utf-8" + ) + self.assertIn("## Record protocol", board) + self.assertIn(" ", board) + self.assertIn("production line", board) + self.assertIn("origin/release", board) + self.assertIn("Must not default to `master`/`main`", board) + self.assertIn("existing", board) + self.assertIn("review-directory convention", board) + self.assertIn("docs/reviews/YYYY-MM-DD--adversarial-board.md", board) + self.assertIn("signed section", board) + self.assertIn("same-batch seat opinions", board) + self.assertIn("re-check every seat claim", board) + self.assertIn("未复核·转述", board) + self.assertIn("须人工核", board) + self.assertIn("ListAgents", board) + self.assertIn("TaskOutput", board) + self.assertIn("wait window", board) + self.assertIn("10 minutes", board) + self.assertIn("must not publish arbitration", board) + self.assertIn("逾期未交", board) + self.assertIn("revised", board) + self.assertIn("the full command", board) + self.assertIn("raw output", board) + self.assertIn("未执行", board) + self.assertIn("P0/P1/P2", board) + self.assertIn("Go/No-Go", board) + self.assertIn("not a Proof", board) + self.assertIn("task review` PASS", board) + self.assertIn("Do not merge, push, signoff", board) + self.assertNotIn("--yes", board) + self.assertIn("user-invocable: false", board) + self.assertNotIn("/dyro-board", board) + self.assertNotIn("$dyro-board", board) + self.assertIn("Follow `dyro-board` exactly", review) + self.assertIn("do not invent a second protocol", review.lower()) + self.assertNotIn("## Record protocol", review) + self.assertNotIn("逾期未交", review) + self.assertNotIn(" ", review) + def test_review_board_install_also_installs_protocol(self) -> None: installed = install_integration("review-board", yes=True) self.assertEqual(installed.status.state, IntegrationState.CURRENT) diff --git a/tests/test_seats.py b/tests/test_seats.py index 5f9f37a..5434470 100644 --- a/tests/test_seats.py +++ b/tests/test_seats.py @@ -98,6 +98,17 @@ def test_packaged_first_batch_skills_auto_trigger_and_stay_bounded(self) -> None self.assertNotIn("user-invocable: true", frontmatter) self.assertNotIn("/dyro-board", content) self.assertIn("/dyro-review-board", content) + self.assertIn("production line", content) + self.assertIn("Must not default to `master`/`main`", content) + self.assertIn("review-directory convention", content) + self.assertIn("re-check every seat claim", content) + self.assertIn("wait window", content) + self.assertIn("逾期未交", content) + self.assertIn("the full command", content) + self.assertIn("raw output", content) + self.assertIn("须人工核", content) + self.assertIn("未复核·转述", content) + self.assertIn("P0/P1/P2", content) self.assertEqual(keys, expected_keys, msg=seat.skill_name) self.assertIn(f"name: {seat.skill_name}", frontmatter) lowered = content.lower() @@ -160,6 +171,9 @@ def test_slash_skills_are_packaged_but_not_auto_load_seats(self) -> None: self.assertIn("user-invocable: false", board) self.assertNotIn("user-invocable: true", board) self.assertNotIn("/dyro-board", board) + self.assertIn("Must not default to `master`/`main`", board) + self.assertIn("逾期未交", board) + self.assertIn("review-directory convention", board) def test_wheel_package_data_lists_every_first_batch_skill(self) -> None: metadata = Path(__file__).resolve().parents[1].joinpath("pyproject.toml") From 9d977b498d271230b196f7b407a9a1bf6de03e79 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 20 Aug 2026 20:01:49 +0000 Subject: [PATCH 2/3] =?UTF-8?q?test(=E4=BC=9A=E5=AE=A1):=20collapse=20whit?= =?UTF-8?q?espace=20when=20pinning=20Record=20protocol=20phrases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wait-window sentence wraps across lines in the protocol file. Assert the required phrase against collapsed whitespace so the pin does not depend on a single physical line. Co-authored-by: Dandre Yang --- tests/test_integrations.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/test_integrations.py b/tests/test_integrations.py index 71f5b20..d403ea4 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -314,34 +314,35 @@ def test_board_record_protocol_requires_baseline_and_seat_lifecycle( review = (manager._asset_root("review-board") / "SKILL.md").read_text( encoding="utf-8" ) + collapsed = re.sub(r"\s+", " ", board) self.assertIn("## Record protocol", board) self.assertIn(" ", board) - self.assertIn("production line", board) + self.assertIn("production line", collapsed) self.assertIn("origin/release", board) - self.assertIn("Must not default to `master`/`main`", board) + self.assertIn("Must not default to `master`/`main`", collapsed) self.assertIn("existing", board) - self.assertIn("review-directory convention", board) + self.assertIn("review-directory convention", collapsed) self.assertIn("docs/reviews/YYYY-MM-DD--adversarial-board.md", board) - self.assertIn("signed section", board) - self.assertIn("same-batch seat opinions", board) - self.assertIn("re-check every seat claim", board) + self.assertIn("signed section", collapsed) + self.assertIn("same-batch seat opinions", collapsed) + self.assertIn("re-check every seat claim", collapsed) self.assertIn("未复核·转述", board) self.assertIn("须人工核", board) self.assertIn("ListAgents", board) self.assertIn("TaskOutput", board) - self.assertIn("wait window", board) - self.assertIn("10 minutes", board) - self.assertIn("must not publish arbitration", board) + self.assertIn("wait window", collapsed) + self.assertIn("10 minutes", collapsed) + self.assertIn("must not publish arbitration", collapsed) self.assertIn("逾期未交", board) self.assertIn("revised", board) - self.assertIn("the full command", board) - self.assertIn("raw output", board) + self.assertIn("the full command", collapsed) + self.assertIn("raw output", collapsed) self.assertIn("未执行", board) self.assertIn("P0/P1/P2", board) self.assertIn("Go/No-Go", board) - self.assertIn("not a Proof", board) + self.assertIn("not a Proof", collapsed) self.assertIn("task review` PASS", board) - self.assertIn("Do not merge, push, signoff", board) + self.assertIn("Do not merge, push, signoff", collapsed) self.assertNotIn("--yes", board) self.assertIn("user-invocable: false", board) self.assertNotIn("/dyro-board", board) From a9fb91fc7f2c331ba6a6ae2d41130b6285accad2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 20 Aug 2026 20:09:20 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix(=E4=BC=9A=E5=AE=A1):=20lock=20Record=20?= =?UTF-8?q?polarity=20pins=20and=20prior-board=20path=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin 0b close-out (已闭环 / 未闭环 / same line and topic), the empty ListAgents/TaskOutput "is not enough" polarity, and test-verdict pipeline/redirect plus "must not assert pass" so delete or invert fails CI. Prior-board directory wins path clashes; negative attestation is 已检索·无先前会审; wait clock starts at first dispatch. Discipline doc now points at dyro-board/SKILL.md. Co-authored-by: Dandre Yang --- CHANGELOG.md | 13 +-- docs/agent-orchestration-discipline.md | 16 +--- .../integrations/assets/dyro-board/SKILL.md | 12 ++- tests/test_integrations.py | 85 ++++++++++++++++++- tests/test_seats.py | 25 +++++- 5 files changed, 122 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c93dc8c..ef2531b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,14 @@ ## Unreleased - Tighten the internal `dyro-board` Record protocol: declare a - production-line baseline (not `master`/`main`), close prior P0/P1, - re-check seat claims before arbitration, wait ≥ 10 minutes before - marking a seat `逾期未交`, and record the full test command plus raw - output. The `/dyro-review-board` wrapper still follows that file only - and refuses same-turn delivery. Version unchanged. + production-line baseline (not `master`/`main`), close prior P0/P1 as + `已闭环` / `未闭环` / `须人工核` (or `已检索·无先前会审` when none), + re-check seat claims, start the wait window at first dispatch, let a + prior-board directory win over a conflicting repo convention, and + record the full test command plus whether a pipeline/redirect hid the + exit code. Pins lock those polarities. The `/dyro-review-board` + wrapper still follows that file only and refuses same-turn delivery. + Version unchanged. - Hide the internal `dyro-board` protocol from slash / `$` skill pickers. `/dyro-review-board` is the only human-facing 会审 command. The protocol asset still installs as a non-invocable companion of `review-board` (and diff --git a/docs/agent-orchestration-discipline.md b/docs/agent-orchestration-discipline.md index 41d7f0d..7d81223 100644 --- a/docs/agent-orchestration-discipline.md +++ b/docs/agent-orchestration-discipline.md @@ -58,21 +58,7 @@ Agent 输出永远是**建议**;进入交付链必须经过 Dyro 契约与独 ## 3. 对抗评审记录协议(摘要) -第一方座位 `dyro-board` 在用户提出会审 / 对抗 / Go/No-Go 时自动戴上协议;人类命令是 `/dyro-review-board`。记录仍不是 Proof。完整模板见 [可选本地 Agent 派发设计 §6](designs/optional-local-agent-dispatch.md#6-对抗评审记录协议)。 - -硬规则: - -1. 单一共享评审文件 -2. 每人仅写自己的签名区 -3. **源码 / 线上契约 > 计划 / 旧评审** -4. 无法证明 → 标 `须人工核` -5. 终裁合并重复、仲裁冲突、输出 P0/P1/P2 + Go/No-Go + 执行交接 - -建议路径: - -```text -docs/reviews/YYYY-MM-DD--adversarial-board.md -``` +第一方座位 `dyro-board` 在用户提出会审 / 对抗 / Go/No-Go 时自动戴上协议;人类命令是 `/dyro-review-board`。记录仍不是 Proof。完整 Record 协议只以 [`dyro-board/SKILL.md`](../src/dyro/integrations/assets/dyro-board/SKILL.md) 为准;不要在此另写一套,也不要把设计稿或旧五条摘要当成现行模板。 ## 4. 写冲突与脏工作区 diff --git a/src/dyro/integrations/assets/dyro-board/SKILL.md b/src/dyro/integrations/assets/dyro-board/SKILL.md index 68d4fc5..33ef911 100644 --- a/src/dyro/integrations/assets/dyro-board/SKILL.md +++ b/src/dyro/integrations/assets/dyro-board/SKILL.md @@ -34,11 +34,14 @@ Do not scan personal host skill directories. baseline, say why. 0b. Search for prior boards on the same development line / same topic. If one exists, the record **must** include a section that closes each - prior P0/P1 as `已闭环` / `未闭环` / `须人工核` with evidence. + prior P0/P1 as `已闭环` / `未闭环` / `须人工核` with evidence. If the + search finds none, the record must say `已检索·无先前会审`. 1. One shared review file. Prefer the repo's **existing** review-directory convention. If none, `docs/reviews/YYYY-MM-DD--adversarial-board.md`. If a prior - board exists on this line, use the same directory. + board exists on this line, use the same directory. If the repo + convention and a prior-board directory differ, the prior-board + directory wins (same line). 2. Each reviewer writes only in a signed section. Do not edit, rewrite, or summarize another section. 3. Source code and live contracts outrank plans, prior reviews, **and @@ -48,8 +51,9 @@ Do not scan personal host skill directories. 4. Unprovable claims are `须人工核`. 5. Empty `ListAgents` / `TaskOutput` is **not** enough to declare a seat dead — those APIs can be empty while the seat is still running. The - chair must set an explicit wait window (≥ 10 minutes) and **must not - publish arbitration** during that window. + chair must set an explicit wait window (≥ 10 minutes). The clock + starts at **first dispatch** of the seats, not the first empty poll, + and **must not publish arbitration** during that window. 6. A seat that misses the window is `逾期未交`, not "lost". Record who covers that dimension. 7. Seat reports that arrive after arbitration require a **revised** diff --git a/tests/test_integrations.py b/tests/test_integrations.py index d403ea4..ab23040 100644 --- a/tests/test_integrations.py +++ b/tests/test_integrations.py @@ -25,6 +25,37 @@ from dyro.integrations import manager +def _record_protocol_section(skill: str) -> str: + start = skill.index("## Record protocol") + rest = skill[start:] + match = re.search(r"\n## ", rest[1:]) + return rest if match is None else rest[: match.start() + 1] + + +def _plain_record(skill: str) -> str: + return re.sub(r"\s+", " ", re.sub(r"[*_]", "", _record_protocol_section(skill))) + + +def _has_prior_closeout(plain: str) -> bool: + return ( + re.search(r"(?:^|\s)0b\.", plain) is not None + and "same development line / same topic" in plain + and "已闭环" in plain + and "未闭环" in plain + ) + + +def _has_empty_api_polarity(plain: str) -> bool: + return "is not enough to declare a seat dead" in plain + + +def _has_test_verdict_polarity(plain: str) -> bool: + return ( + "pipeline/redirect hid the exit code" in plain + and "must not assert pass" in plain + ) + + class IntegrationManagerTests(unittest.TestCase): def setUp(self) -> None: self.tmp = tempfile.TemporaryDirectory(prefix="dyro-integrations-") @@ -315,28 +346,69 @@ def test_board_record_protocol_requires_baseline_and_seat_lifecycle( encoding="utf-8" ) collapsed = re.sub(r"\s+", " ", board) + record = _record_protocol_section(board) + plain = _plain_record(board) self.assertIn("## Record protocol", board) self.assertIn(" ", board) self.assertIn("production line", collapsed) self.assertIn("origin/release", board) self.assertIn("Must not default to `master`/`main`", collapsed) - self.assertIn("existing", board) - self.assertIn("review-directory convention", collapsed) + self.assertIn("existing review-directory convention", plain) self.assertIn("docs/reviews/YYYY-MM-DD--adversarial-board.md", board) + self.assertIn("prior-board directory wins", plain) self.assertIn("signed section", collapsed) self.assertIn("same-batch seat opinions", collapsed) self.assertIn("re-check every seat claim", collapsed) self.assertIn("未复核·转述", board) self.assertIn("须人工核", board) + self.assertTrue(_has_prior_closeout(plain), msg=plain) + self.assertIn("已检索·无先前会审", record) + self.assertFalse( + _has_prior_closeout(plain.replace("0b.", "1b.", 1)), + msg="dropping 0b must fail the close-out pin", + ) + self.assertFalse( + _has_prior_closeout( + plain.replace("已闭环", "", 1).replace("未闭环", "", 1) + ), + msg="deleting 已闭环 / 未闭环 must fail the close-out pin", + ) self.assertIn("ListAgents", board) self.assertIn("TaskOutput", board) + self.assertTrue(_has_empty_api_polarity(plain), msg=plain) + self.assertNotIn("is enough to declare a seat dead", plain) + self.assertFalse( + _has_empty_api_polarity( + plain.replace( + "is not enough to declare a seat dead", + "is enough to declare a seat dead", + 1, + ) + ), + msg="flipping empty-API polarity to 'is enough' must fail", + ) self.assertIn("wait window", collapsed) self.assertIn("10 minutes", collapsed) + self.assertIn("first dispatch", plain) self.assertIn("must not publish arbitration", collapsed) self.assertIn("逾期未交", board) - self.assertIn("revised", board) + self.assertIn("revised record", plain) self.assertIn("the full command", collapsed) self.assertIn("raw output", collapsed) + self.assertTrue(_has_test_verdict_polarity(plain), msg=plain) + self.assertNotIn("must assert pass", plain) + self.assertFalse( + _has_test_verdict_polarity( + plain.replace("must not assert pass", "must assert pass", 1) + ), + msg="inverting 'must not assert pass' must fail", + ) + self.assertFalse( + _has_test_verdict_polarity( + plain.replace("pipeline/redirect hid the exit code", "", 1) + ), + msg="deleting pipeline/redirect hid-exit-code must fail", + ) self.assertIn("未执行", board) self.assertIn("P0/P1/P2", board) self.assertIn("Go/No-Go", board) @@ -352,6 +424,13 @@ def test_board_record_protocol_requires_baseline_and_seat_lifecycle( self.assertNotIn("## Record protocol", review) self.assertNotIn("逾期未交", review) self.assertNotIn(" ", review) + self.assertNotIn("已检索·无先前会审", review) + discipline = Path(__file__).resolve().parents[1].joinpath( + "docs", "agent-orchestration-discipline.md" + ).read_text(encoding="utf-8") + self.assertIn("dyro-board/SKILL.md", discipline) + self.assertNotIn("完整模板见", discipline) + self.assertNotIn("1. 单一共享评审文件", discipline) def test_review_board_install_also_installs_protocol(self) -> None: installed = install_integration("review-board", yes=True) diff --git a/tests/test_seats.py b/tests/test_seats.py index 5434470..3759812 100644 --- a/tests/test_seats.py +++ b/tests/test_seats.py @@ -100,12 +100,26 @@ def test_packaged_first_batch_skills_auto_trigger_and_stay_bounded(self) -> None self.assertIn("/dyro-review-board", content) self.assertIn("production line", content) self.assertIn("Must not default to `master`/`main`", content) - self.assertIn("review-directory convention", content) + collapsed = re.sub(r"\s+", " ", re.sub(r"[*_]", "", content)) + self.assertIn("existing review-directory convention", collapsed) + self.assertIn("0b.", content) + self.assertIn("same development line / same topic", content) + self.assertIn("已闭环", content) + self.assertIn("未闭环", content) + self.assertIn("已检索·无先前会审", content) + self.assertIn("is not enough to declare a seat dead", collapsed) + self.assertNotIn("is enough to declare a seat dead", collapsed) + self.assertIn("first dispatch", collapsed) + self.assertIn("prior-board directory wins", collapsed) self.assertIn("re-check every seat claim", content) self.assertIn("wait window", content) self.assertIn("逾期未交", content) + self.assertIn("revised record", collapsed) self.assertIn("the full command", content) self.assertIn("raw output", content) + self.assertIn("pipeline/redirect hid the exit code", content) + self.assertIn("must not assert pass", content) + self.assertNotIn("must assert pass", collapsed) self.assertIn("须人工核", content) self.assertIn("未复核·转述", content) self.assertIn("P0/P1/P2", content) @@ -173,7 +187,14 @@ def test_slash_skills_are_packaged_but_not_auto_load_seats(self) -> None: self.assertNotIn("/dyro-board", board) self.assertIn("Must not default to `master`/`main`", board) self.assertIn("逾期未交", board) - self.assertIn("review-directory convention", board) + collapsed = re.sub(r"\s+", " ", re.sub(r"[*_]", "", board)) + self.assertIn("existing review-directory convention", collapsed) + self.assertIn("0b.", board) + self.assertIn("已闭环", board) + self.assertIn("未闭环", board) + self.assertIn("is not enough to declare a seat dead", collapsed) + self.assertIn("must not assert pass", board) + self.assertIn("pipeline/redirect hid the exit code", board) def test_wheel_package_data_lists_every_first_batch_skill(self) -> None: metadata = Path(__file__).resolve().parents[1].joinpath("pyproject.toml")