kin: auto-commit after pipeline
This commit is contained in:
parent
ce01aeac03
commit
3a4d6ef79d
1 changed files with 341 additions and 0 deletions
341
tests/test_kin_docs_008_regression.py
Normal file
341
tests/test_kin_docs_008_regression.py
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
"""Regression tests for KIN-DOCS-008 — Добавить паттерн error_coordinator для крупных баг-расследований.
|
||||
|
||||
Acceptance criteria:
|
||||
1. agents/prompts/error_coordinator.md существует и содержит все 5 стандартных секций (decision #940)
|
||||
2. output_schema в specialists.yaml содержит обязательные поля:
|
||||
fault_groups, primary_faults, streams (и полный набор из 5 полей) (decision #952, #957)
|
||||
3. Параметризованный тест — каждое из обязательных полей output_schema присутствует (decision #957)
|
||||
4. error_coordinator зарегистрирован в specialists.yaml с корректными атрибутами (decision #954)
|
||||
5. Route template 'multi_bug_debug' существует и содержит шаги [error_coordinator, debugger, tester]
|
||||
6. pm.md содержит правило активации error_coordinator при ≥2 взаимосвязанных ошибках
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
|
||||
SPECIALISTS_YAML = Path(__file__).parent.parent / "agents" / "specialists.yaml"
|
||||
PROMPTS_DIR = Path(__file__).parent.parent / "agents" / "prompts"
|
||||
ERROR_COORDINATOR_PROMPT = PROMPTS_DIR / "error_coordinator.md"
|
||||
PM_PROMPT = PROMPTS_DIR / "pm.md"
|
||||
|
||||
REQUIRED_SECTIONS = [
|
||||
"## Working Mode",
|
||||
"## Focus On",
|
||||
"## Quality Checks",
|
||||
"## Return Format",
|
||||
"## Constraints",
|
||||
]
|
||||
|
||||
# Обязательные поля output_schema (decision #952, #957)
|
||||
ERROR_COORDINATOR_REQUIRED_SCHEMA_FIELDS = {
|
||||
"status",
|
||||
"fault_groups",
|
||||
"primary_faults",
|
||||
"cascading_symptoms",
|
||||
"streams",
|
||||
"reintegration_checklist",
|
||||
}
|
||||
|
||||
|
||||
def _load_yaml():
|
||||
return yaml.safe_load(SPECIALISTS_YAML.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 1. Структура промпта — 5 стандартных секций (AC-1, decision #940)
|
||||
# ===========================================================================
|
||||
|
||||
class TestErrorCoordinatorPromptStructure:
|
||||
"""agents/prompts/error_coordinator.md существует и содержит все 5 стандартных секций."""
|
||||
|
||||
def test_prompt_file_exists(self):
|
||||
"""Файл agents/prompts/error_coordinator.md существует."""
|
||||
assert ERROR_COORDINATOR_PROMPT.exists(), (
|
||||
f"Промпт error_coordinator не найден: {ERROR_COORDINATOR_PROMPT}"
|
||||
)
|
||||
|
||||
def test_prompt_file_is_not_empty(self):
|
||||
"""Файл error_coordinator.md не пустой (более 100 символов)."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert len(content.strip()) > 100
|
||||
|
||||
@pytest.mark.parametrize("section", REQUIRED_SECTIONS)
|
||||
def test_prompt_has_required_section(self, section):
|
||||
"""Промпт error_coordinator.md содержит каждую из 5 стандартных секций."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert section in content, (
|
||||
f"error_coordinator.md не содержит обязательную секцию {section!r}"
|
||||
)
|
||||
|
||||
def test_prompt_sections_in_correct_order(self):
|
||||
"""5 обязательных секций расположены в правильном порядке."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
positions = [content.find(sec) for sec in REQUIRED_SECTIONS]
|
||||
assert all(p != -1 for p in positions), "Не все 5 секций найдены в error_coordinator.md"
|
||||
assert positions == sorted(positions), (
|
||||
f"Секции в error_coordinator.md расположены не по порядку. "
|
||||
f"Позиции: {dict(zip(REQUIRED_SECTIONS, positions))}"
|
||||
)
|
||||
|
||||
def test_prompt_has_input_section(self):
|
||||
"""Промпт error_coordinator.md содержит секцию ## Input."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "## Input" in content, "error_coordinator.md не содержит секцию '## Input'"
|
||||
|
||||
def test_prompt_contains_blocked_protocol(self):
|
||||
"""Промпт error_coordinator.md содержит Blocked Protocol с полем blocked_reason."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "blocked_reason" in content, (
|
||||
"error_coordinator.md не содержит 'blocked_reason' — Blocked Protocol обязателен"
|
||||
)
|
||||
|
||||
def test_prompt_contains_blocked_at(self):
|
||||
"""Промпт error_coordinator.md содержит поле blocked_at в Blocked Protocol."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "blocked_at" in content, (
|
||||
"error_coordinator.md не содержит 'blocked_at' в Blocked Protocol"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 2. output_schema — обязательные поля (AC-2, decision #952)
|
||||
# ===========================================================================
|
||||
|
||||
class TestErrorCoordinatorOutputSchemaFields:
|
||||
"""output_schema error_coordinator содержит все обязательные поля (decision #952)."""
|
||||
|
||||
def test_specialist_has_output_schema(self):
|
||||
"""error_coordinator имеет поле output_schema в specialists.yaml."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["error_coordinator"]
|
||||
assert "output_schema" in role, "error_coordinator должен иметь output_schema"
|
||||
|
||||
def test_output_schema_has_fault_groups(self):
|
||||
"""output_schema содержит ключевое поле fault_groups."""
|
||||
data = _load_yaml()
|
||||
schema = data["specialists"]["error_coordinator"]["output_schema"]
|
||||
assert "fault_groups" in schema, (
|
||||
"output_schema error_coordinator обязана содержать поле 'fault_groups'"
|
||||
)
|
||||
|
||||
def test_output_schema_has_primary_faults(self):
|
||||
"""output_schema содержит ключевое поле primary_faults."""
|
||||
data = _load_yaml()
|
||||
schema = data["specialists"]["error_coordinator"]["output_schema"]
|
||||
assert "primary_faults" in schema, (
|
||||
"output_schema error_coordinator обязана содержать поле 'primary_faults'"
|
||||
)
|
||||
|
||||
def test_output_schema_has_streams(self):
|
||||
"""output_schema содержит ключевое поле streams."""
|
||||
data = _load_yaml()
|
||||
schema = data["specialists"]["error_coordinator"]["output_schema"]
|
||||
assert "streams" in schema, (
|
||||
"output_schema error_coordinator обязана содержать поле 'streams'"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 3. Параметризованный тест отсутствующих полей output_schema (AC-3, decision #957)
|
||||
# ===========================================================================
|
||||
|
||||
class TestErrorCoordinatorOutputSchemaParametrized:
|
||||
"""Параметризованный тест: каждое из обязательных полей output_schema присутствует (decision #957)."""
|
||||
|
||||
@pytest.mark.parametrize("required_field", sorted(ERROR_COORDINATOR_REQUIRED_SCHEMA_FIELDS))
|
||||
def test_output_schema_contains_required_field(self, required_field):
|
||||
"""output_schema error_coordinator содержит обязательное поле."""
|
||||
data = _load_yaml()
|
||||
schema = data["specialists"]["error_coordinator"]["output_schema"]
|
||||
assert required_field in schema, (
|
||||
f"output_schema error_coordinator обязана содержать поле {required_field!r}"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 4. Регистрация специалиста в specialists.yaml (AC-4, decision #954)
|
||||
# ===========================================================================
|
||||
|
||||
class TestErrorCoordinatorSpecialistsEntry:
|
||||
"""error_coordinator зарегистрирован в specialists.yaml с корректными атрибутами (decision #954)."""
|
||||
|
||||
def test_error_coordinator_exists_in_specialists(self):
|
||||
"""error_coordinator присутствует в секции specialists."""
|
||||
data = _load_yaml()
|
||||
assert "error_coordinator" in data.get("specialists", {}), (
|
||||
"error_coordinator отсутствует в specialists.yaml"
|
||||
)
|
||||
|
||||
def test_error_coordinator_model_is_sonnet(self):
|
||||
"""error_coordinator использует модель sonnet."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["error_coordinator"]
|
||||
assert role.get("model") == "sonnet", (
|
||||
f"Ожидался model=sonnet, получили: {role.get('model')}"
|
||||
)
|
||||
|
||||
def test_error_coordinator_permissions_is_read_only(self):
|
||||
"""error_coordinator имеет permissions=read_only (анализ без изменений кода)."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["error_coordinator"]
|
||||
assert role.get("permissions") == "read_only", (
|
||||
f"Ожидался permissions=read_only, получили: {role.get('permissions')}"
|
||||
)
|
||||
|
||||
def test_error_coordinator_tools_include_read_grep_glob(self):
|
||||
"""error_coordinator имеет инструменты Read, Grep, Glob."""
|
||||
data = _load_yaml()
|
||||
tools = data["specialists"]["error_coordinator"].get("tools", [])
|
||||
for tool in ("Read", "Grep", "Glob"):
|
||||
assert tool in tools, f"error_coordinator должен иметь инструмент {tool!r}"
|
||||
|
||||
def test_error_coordinator_not_in_any_department_workers(self):
|
||||
"""error_coordinator не входит ни в один department workers — вставляется через PM routing."""
|
||||
data = _load_yaml()
|
||||
for dept_name, dept in data.get("departments", {}).items():
|
||||
workers = dept.get("workers", [])
|
||||
assert "error_coordinator" not in workers, (
|
||||
f"error_coordinator не должен быть в workers департамента '{dept_name}'. "
|
||||
"Специалист вставляется через PM routing rule, не через department."
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 5. Route template 'multi_bug_debug' (AC-5)
|
||||
# ===========================================================================
|
||||
|
||||
class TestMultiBugDebugRoute:
|
||||
"""Route template 'multi_bug_debug' существует и содержит правильные шаги."""
|
||||
|
||||
def test_multi_bug_debug_route_exists(self):
|
||||
"""Route template 'multi_bug_debug' присутствует в specialists.yaml."""
|
||||
data = _load_yaml()
|
||||
routes = data.get("routes", {})
|
||||
assert "multi_bug_debug" in routes, (
|
||||
"Route template 'multi_bug_debug' отсутствует в specialists.yaml"
|
||||
)
|
||||
|
||||
def test_multi_bug_debug_first_step_is_error_coordinator(self):
|
||||
"""Route 'multi_bug_debug': первый шаг — error_coordinator."""
|
||||
data = _load_yaml()
|
||||
steps = data["routes"]["multi_bug_debug"]["steps"]
|
||||
assert steps[0] == "error_coordinator", (
|
||||
f"Первый шаг 'multi_bug_debug' должен быть 'error_coordinator', получили: {steps[0]!r}"
|
||||
)
|
||||
|
||||
def test_multi_bug_debug_contains_debugger(self):
|
||||
"""Route 'multi_bug_debug' содержит шаг 'debugger'."""
|
||||
data = _load_yaml()
|
||||
steps = data["routes"]["multi_bug_debug"]["steps"]
|
||||
assert "debugger" in steps, (
|
||||
f"Route 'multi_bug_debug' должен содержать 'debugger'. Шаги: {steps}"
|
||||
)
|
||||
|
||||
def test_multi_bug_debug_contains_tester(self):
|
||||
"""Route 'multi_bug_debug' содержит шаг 'tester'."""
|
||||
data = _load_yaml()
|
||||
steps = data["routes"]["multi_bug_debug"]["steps"]
|
||||
assert "tester" in steps, (
|
||||
f"Route 'multi_bug_debug' должен содержать 'tester'. Шаги: {steps}"
|
||||
)
|
||||
|
||||
def test_multi_bug_debug_steps_exact(self):
|
||||
"""Route 'multi_bug_debug' содержит ровно шаги [error_coordinator, debugger, tester]."""
|
||||
data = _load_yaml()
|
||||
steps = data["routes"]["multi_bug_debug"]["steps"]
|
||||
assert steps == ["error_coordinator", "debugger", "tester"], (
|
||||
f"Ожидались шаги ['error_coordinator', 'debugger', 'tester'], получили: {steps}"
|
||||
)
|
||||
|
||||
def test_multi_bug_debug_has_description(self):
|
||||
"""Route 'multi_bug_debug' имеет поле description."""
|
||||
data = _load_yaml()
|
||||
route = data["routes"]["multi_bug_debug"]
|
||||
assert "description" in route and route["description"], (
|
||||
"Route 'multi_bug_debug' должен иметь непустое поле 'description'"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 6. pm.md — правило активации error_coordinator (AC-6)
|
||||
# ===========================================================================
|
||||
|
||||
class TestPmMultiBugRoutingRule:
|
||||
"""pm.md содержит правило активации error_coordinator при ≥2 взаимосвязанных ошибках."""
|
||||
|
||||
def test_pm_mentions_error_coordinator(self):
|
||||
"""pm.md упоминает 'error_coordinator' как специалиста для multi-bug сценария."""
|
||||
content = PM_PROMPT.read_text(encoding="utf-8")
|
||||
assert "error_coordinator" in content, (
|
||||
"pm.md должен упоминать 'error_coordinator' в правилах маршрутизации"
|
||||
)
|
||||
|
||||
def test_pm_mentions_multi_bug_debug_route(self):
|
||||
"""pm.md упоминает route template 'multi_bug_debug'."""
|
||||
content = PM_PROMPT.read_text(encoding="utf-8")
|
||||
assert "multi_bug_debug" in content, (
|
||||
"pm.md должен упоминать route template 'multi_bug_debug'"
|
||||
)
|
||||
|
||||
def test_pm_has_activation_threshold_two_bugs(self):
|
||||
"""pm.md содержит правило активации при ≥2 взаимосвязанных ошибках."""
|
||||
content = PM_PROMPT.read_text(encoding="utf-8")
|
||||
# Проверяем, что присутствует хотя бы одно упоминание порогового правила ≥2
|
||||
has_threshold = "≥2" in content or ">= 2" in content or "2 взаимосвяз" in content
|
||||
assert has_threshold, (
|
||||
"pm.md должен содержать правило активации error_coordinator при ≥2 связанных ошибках"
|
||||
)
|
||||
|
||||
def test_pm_quality_check_mentions_error_coordinator_first(self):
|
||||
"""pm.md содержит Quality Check: при ≥2 багах pipeline[0].role == error_coordinator."""
|
||||
content = PM_PROMPT.read_text(encoding="utf-8")
|
||||
assert "pipeline[0].role == error_coordinator" in content, (
|
||||
"pm.md Quality Checks должен проверять pipeline[0].role == error_coordinator "
|
||||
"при задаче с ≥2 взаимосвязанными багами"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 7. Промпт определяет ключевые поля output_schema в тексте
|
||||
# ===========================================================================
|
||||
|
||||
class TestErrorCoordinatorPromptOutputFields:
|
||||
"""Промпт error_coordinator.md определяет ключевые поля выходной схемы в ## Return Format."""
|
||||
|
||||
def test_prompt_defines_fault_groups(self):
|
||||
"""Промпт определяет поле 'fault_groups' в Return Format."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "fault_groups" in content, (
|
||||
"error_coordinator.md должен определять поле 'fault_groups'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_primary_faults(self):
|
||||
"""Промпт определяет поле 'primary_faults' в Return Format."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "primary_faults" in content, (
|
||||
"error_coordinator.md должен определять поле 'primary_faults'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_streams(self):
|
||||
"""Промпт определяет поле 'streams' в Return Format."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "streams" in content, (
|
||||
"error_coordinator.md должен определять поле 'streams'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_reintegration_checklist(self):
|
||||
"""Промпт определяет поле 'reintegration_checklist' в Return Format."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "reintegration_checklist" in content, (
|
||||
"error_coordinator.md должен определять поле 'reintegration_checklist'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_cascading_symptoms(self):
|
||||
"""Промпт определяет поле 'cascading_symptoms' в Return Format."""
|
||||
content = ERROR_COORDINATOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "cascading_symptoms" in content, (
|
||||
"error_coordinator.md должен определять поле 'cascading_symptoms'"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue