Files
.pouch/tests/test_ack_skill.py
T

225 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
class AckSkillContentTests(unittest.TestCase):
def test_ack_skill_owns_resources_without_modifying_agent_instructions(self) -> None:
content = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(encoding="utf-8")
for expected in (
"pouch init ack --project <project-root>",
".pouch/ack/project.md",
".pouch/ack/tasks.yaml",
".pouch/ack/knowledge.yaml",
".pouch/ack/delivery.yaml",
".pouch/ack/regression.yaml",
"tasks: []",
"validate_tasks.py",
"validate_knowledge.py",
"validate_delivery.py",
"validate_regression.py",
"select_tasks.py",
"select_knowledge.py",
"select_regression.py",
"references/kickoff.md",
"agents.yaml",
"validate_agents.py",
"model: null",
"不要修改项目的 `AGENTS.md`",
"当前会话担任 Coordinator",
"intents.testEnvironment",
"运行测试环境",
"运行版本发布",
"运行回归",
"via: deployer",
"## ack 初始化:完成 | 部分完成 | 阻塞",
"加载 deployer skill 的「初始化」",
):
self.assertIn(expected, content)
def test_ack_skill_is_explicit_only(self) -> None:
metadata = (REPO_ROOT / "skills" / "ack" / "agents" / "openai.yaml").read_text(
encoding="utf-8"
)
self.assertIn('display_name: "ACK"', metadata)
self.assertIn("allow_implicit_invocation: false", metadata)
def test_model_routing_is_fingerprint_bound_and_does_not_trust_receipt_reuse(self) -> None:
content = (
REPO_ROOT / "skills" / "ack" / "references" / "model-routing.md"
).read_text(encoding="utf-8")
self.assertIn("--expected-launch-fingerprint", content)
self.assertIn("allowlist-v1", content)
self.assertIn("禁止根据持久化 receipt 自动复用", content)
self.assertIn("launcher 身份证明", content)
def test_worker_reuse_requires_idle_state_and_verified_history_reset(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
adapter = (
REPO_ROOT / "skills" / "ack" / "references" / "orca-adapter.md"
).read_text(encoding="utf-8")
self.assertIn("空闲", skill)
self.assertIn("清理历史消息", skill)
self.assertIn("无法确认清理成功时创建 fresh worker", skill)
self.assertIn("角色、profile、worktree", adapter)
self.assertIn("不得复用仍在工作", adapter)
self.assertIn("或运行状态不明的 worker", adapter)
def test_coordinator_reclaims_only_verified_task_terminals_at_run_end(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
kickoff = (
REPO_ROOT / "skills" / "ack" / "references" / "kickoff.md"
).read_text(encoding="utf-8")
self.assertIn("整轮任务完成", skill)
self.assertIn("回收所有只属于 `verified` 任务的 worker", skill)
self.assertIn("终端,并核对关闭回执", skill)
self.assertIn("不设置 TTL", skill)
self.assertIn("blocked", kickoff)
self.assertIn("failed_retest", kickoff)
self.assertIn("leftover", kickoff)
def test_coordinator_reclaims_role_terminal_after_success(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
adapter = (
REPO_ROOT / "skills" / "ack" / "references" / "orca-adapter.md"
).read_text(encoding="utf-8")
self.assertIn("--completed-task-id", skill)
self.assertIn("--completed-role", skill)
self.assertIn("--completed-role developer", adapter)
self.assertIn("--completed-role test", adapter)
self.assertIn("orca terminal close --tab", adapter)
def test_environment_failures_are_reported_without_consuming_retest_rounds(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
optimization = (
REPO_ROOT / "skills" / "ack" / "references" / "optimization-method.md"
).read_text(encoding="utf-8")
schema = json.loads(
(REPO_ROOT / "skills" / "ack" / "templates" / "tasks.schema.json")
.read_text(encoding="utf-8")
)
self.assertIn("环境失败不占复验轮次", skill)
self.assertIn("userAction", optimization)
self.assertIn("environmentIncidents", schema["definitions"]["task"]["properties"]["dispatch"]["properties"])
def test_validation_ready_hands_off_a_deployed_test_environment(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
delivery = (
REPO_ROOT / "skills" / "ack" / "references" / "delivery.md"
).read_text(encoding="utf-8")
self.assertIn("不能停在", skill)
self.assertIn("`verified` 却声称整轮 ACK 已结束", skill)
self.assertIn("validation_ready", delivery)
self.assertIn("访问地址和用户下一步", delivery)
def test_coordinator_keeps_looping_and_chains_developer_to_test(self) -> None:
skill = (REPO_ROOT / "skills" / "ack" / "SKILL.md").read_text(
encoding="utf-8"
)
required = (
"这不是 fire-and-forget",
"不得在 `launch` / `dispatch` 后只回复“已派发”并结束当前会话",
"`check --wait`",
"`worker_probe.py`",
"默认 60 秒窗口",
"60–120 秒节奏",
"Developer 回报 `worker_done` 或 `escalation`",
"写回 `fixed_by_dev`",
"主动派发 Test",
"继续等待 `retest_result`",
)
for expected in required:
self.assertIn(expected, skill)
dispatch_pos = skill.index("主动派发 Test")
dev_done_pos = skill.index("Developer 回报 `worker_done` 或 `escalation`")
test_done_pos = skill.index("继续等待 `retest_result`")
self.assertLess(dev_done_pos, dispatch_pos)
self.assertLess(dispatch_pos, test_done_pos)
def test_ack_knowledge_resources_and_version_are_present(self) -> None:
ack_dir = REPO_ROOT / "skills" / "ack"
for relative_path in (
"templates/knowledge.template.yaml",
"templates/knowledge.schema.json",
"examples/knowledge.example.yaml",
"scripts/validate_knowledge.py",
"scripts/select_tasks.py",
"scripts/select_knowledge.py",
"scripts/select_regression.py",
"scripts/validate_regression.py",
"templates/regression.template.yaml",
"templates/regression.schema.json",
"examples/regression.example.yaml",
"references/regression.md",
"scripts/run_verification.py",
"scripts/worker_profiles.py",
"scripts/launch_worker.py",
"scripts/validate_agents.py",
"templates/agents.schema.json",
"templates/agents.template.yaml",
"examples/agents.example.yaml",
"templates/delivery.template.yaml",
"templates/delivery.schema.json",
"examples/delivery.example.yaml",
"references/delivery.md",
):
self.assertTrue((ack_dir / relative_path).is_file(), relative_path)
version = (ack_dir / "VERSION").read_text(encoding="utf-8").strip()
self.assertEqual(version, "0.20.0")
self.assertIn(
f'ackVersion: "{version}"',
(ack_dir / "examples" / "tasks.example.yaml").read_text(encoding="utf-8"),
)
self.assertIn(
f"ack v{version}",
(ack_dir / "examples" / "project.example.md").read_text(encoding="utf-8"),
)
self.assertIn(
'ackVersion: "<接入时的 ack skill 版本>"',
(ack_dir / "templates" / "tasks.template.yaml").read_text(
encoding="utf-8"
),
)
self.assertTrue((ack_dir / "references" / "feishu-bug-intake.md").is_file())
def test_task_template_derives_project_files_without_persisted_root_paths(self) -> None:
template = (
REPO_ROOT / "skills" / "ack" / "templates" / "tasks.template.yaml"
).read_text(encoding="utf-8")
# v0.19 起 allowedWorktrees 白名单废弃:模板只保留废弃说明注释,不再生成该字段
self.assertNotIn("allowedWorktrees:", template)
self.assertIn("allowedWorktrees", template)
self.assertIn('knowledgeFile: ".pouch/ack/knowledge.yaml"', template)
self.assertIn('regressionFile: ".pouch/ack/regression.yaml"', template)
if __name__ == "__main__":
unittest.main()