kin: auto-commit after pipeline
This commit is contained in:
parent
ea6a0a399a
commit
2053a9d26c
2 changed files with 211 additions and 4 deletions
207
tests/test_kin_docs_007_regression.py
Normal file
207
tests/test_kin_docs_007_regression.py
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
"""Regression tests for KIN-DOCS-007 — cto_advisor: strategic technical reviewer.
|
||||
|
||||
Acceptance criteria:
|
||||
1. cto_advisor зарегистрирован в specialists.yaml с model=opus
|
||||
2. cto_advisor имеет permissions=read_only
|
||||
3. output_schema содержит поля: status, scalability_assessment, strategic_risks,
|
||||
strategic_verdict, recommendation, notes
|
||||
4. agents/prompts/cto_advisor.md существует и содержит все 5 стандартных секций
|
||||
5. Промпт содержит поля выходной схемы: scalability_assessment, strategic_verdict
|
||||
6. Промпт содержит Blocked Protocol (blocked_at)
|
||||
7. cto_advisor НЕ входит ни в один department workers (опциональный специалист)
|
||||
"""
|
||||
|
||||
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"
|
||||
CTO_ADVISOR_PROMPT = PROMPTS_DIR / "cto_advisor.md"
|
||||
|
||||
REQUIRED_SECTIONS = [
|
||||
"## Working Mode",
|
||||
"## Focus On",
|
||||
"## Quality Checks",
|
||||
"## Return Format",
|
||||
"## Constraints",
|
||||
]
|
||||
|
||||
CTO_ADVISOR_REQUIRED_SCHEMA_FIELDS = {
|
||||
"status",
|
||||
"scalability_assessment",
|
||||
"strategic_risks",
|
||||
"strategic_verdict",
|
||||
"recommendation",
|
||||
"notes",
|
||||
}
|
||||
|
||||
|
||||
def _load_yaml():
|
||||
return yaml.safe_load(SPECIALISTS_YAML.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 1. cto_advisor — регистрация в specialists.yaml
|
||||
# ===========================================================================
|
||||
|
||||
class TestCtoAdvisorSpecialistsEntry:
|
||||
"""cto_advisor зарегистрирован в specialists.yaml с корректной базовой структурой."""
|
||||
|
||||
def test_cto_advisor_exists_in_specialists(self):
|
||||
"""cto_advisor присутствует в секции specialists."""
|
||||
data = _load_yaml()
|
||||
assert "cto_advisor" in data.get("specialists", {}), (
|
||||
"cto_advisor отсутствует в specialists.yaml"
|
||||
)
|
||||
|
||||
def test_cto_advisor_model_is_opus(self):
|
||||
"""cto_advisor использует модель opus (стратегический ревьюер требует Opus)."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["cto_advisor"]
|
||||
assert role.get("model") == "opus", (
|
||||
f"Ожидался model=opus, получили: {role.get('model')}"
|
||||
)
|
||||
|
||||
def test_cto_advisor_permissions_is_read_only(self):
|
||||
"""cto_advisor имеет permissions=read_only (анализ без изменений)."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["cto_advisor"]
|
||||
assert role.get("permissions") == "read_only", (
|
||||
f"Ожидался permissions=read_only, получили: {role.get('permissions')}"
|
||||
)
|
||||
|
||||
def test_cto_advisor_tools_include_read_grep_glob(self):
|
||||
"""cto_advisor имеет инструменты Read, Grep, Glob."""
|
||||
data = _load_yaml()
|
||||
tools = data["specialists"]["cto_advisor"].get("tools", [])
|
||||
for tool in ("Read", "Grep", "Glob"):
|
||||
assert tool in tools, f"cto_advisor должен иметь инструмент {tool!r}"
|
||||
|
||||
def test_cto_advisor_has_output_schema(self):
|
||||
"""cto_advisor имеет поле output_schema."""
|
||||
data = _load_yaml()
|
||||
role = data["specialists"]["cto_advisor"]
|
||||
assert "output_schema" in role, "cto_advisor должен иметь output_schema"
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 2. cto_advisor — output_schema поля
|
||||
# ===========================================================================
|
||||
|
||||
class TestCtoAdvisorOutputSchemaFields:
|
||||
"""output_schema cto_advisor содержит все обязательные поля."""
|
||||
|
||||
@pytest.mark.parametrize("required_field", sorted(CTO_ADVISOR_REQUIRED_SCHEMA_FIELDS))
|
||||
def test_output_schema_contains_required_field(self, required_field):
|
||||
"""output_schema cto_advisor содержит обязательное поле."""
|
||||
data = _load_yaml()
|
||||
schema = data["specialists"]["cto_advisor"]["output_schema"]
|
||||
assert required_field in schema, (
|
||||
f"output_schema cto_advisor обязана содержать поле {required_field!r}"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 3. cto_advisor — промпт существует и имеет стандартную структуру
|
||||
# ===========================================================================
|
||||
|
||||
class TestCtoAdvisorPromptStructure:
|
||||
"""agents/prompts/cto_advisor.md существует и содержит все 5 стандартных секций."""
|
||||
|
||||
def test_prompt_file_exists(self):
|
||||
"""Файл agents/prompts/cto_advisor.md существует."""
|
||||
assert CTO_ADVISOR_PROMPT.exists(), (
|
||||
f"Промпт cto_advisor не найден: {CTO_ADVISOR_PROMPT}"
|
||||
)
|
||||
|
||||
def test_prompt_file_is_not_empty(self):
|
||||
"""Файл cto_advisor.md не пустой."""
|
||||
content = CTO_ADVISOR_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):
|
||||
"""Промпт cto_advisor.md содержит каждую из 5 стандартных секций."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert section in content, (
|
||||
f"cto_advisor.md не содержит обязательную секцию {section!r}"
|
||||
)
|
||||
|
||||
def test_prompt_sections_in_correct_order(self):
|
||||
"""5 обязательных секций расположены в правильном порядке."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
positions = [content.find(sec) for sec in REQUIRED_SECTIONS]
|
||||
assert all(p != -1 for p in positions), "Не все 5 секций найдены в cto_advisor.md"
|
||||
assert positions == sorted(positions), (
|
||||
f"Секции в cto_advisor.md расположены не по порядку. "
|
||||
f"Позиции: {dict(zip(REQUIRED_SECTIONS, positions))}"
|
||||
)
|
||||
|
||||
def test_prompt_has_input_section(self):
|
||||
"""Промпт cto_advisor.md содержит секцию ## Input."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "## Input" in content, "cto_advisor.md не содержит секцию '## Input'"
|
||||
|
||||
def test_prompt_contains_blocked_protocol(self):
|
||||
"""Промпт cto_advisor.md содержит Blocked Protocol."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "blocked_at" in content, (
|
||||
"cto_advisor.md не содержит 'blocked_at' — Blocked Protocol обязателен"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 4. cto_advisor — специфические поля выходной схемы в промпте
|
||||
# ===========================================================================
|
||||
|
||||
class TestCtoAdvisorPromptOutputFields:
|
||||
"""Промпт cto_advisor.md определяет ключевые поля стратегической оценки."""
|
||||
|
||||
def test_prompt_defines_scalability_assessment(self):
|
||||
"""Промпт определяет поле 'scalability_assessment'."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "scalability_assessment" in content, (
|
||||
"cto_advisor.md должен определять поле 'scalability_assessment'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_strategic_verdict(self):
|
||||
"""Промпт определяет поле 'strategic_verdict'."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "strategic_verdict" in content, (
|
||||
"cto_advisor.md должен определять поле 'strategic_verdict'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_strategic_risks(self):
|
||||
"""Промпт определяет поле 'strategic_risks'."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "strategic_risks" in content, (
|
||||
"cto_advisor.md должен определять поле 'strategic_risks'"
|
||||
)
|
||||
|
||||
def test_prompt_defines_platform_vs_product(self):
|
||||
"""Промпт явно упоминает platform_vs_product."""
|
||||
content = CTO_ADVISOR_PROMPT.read_text(encoding="utf-8")
|
||||
assert "platform_vs_product" in content, (
|
||||
"cto_advisor.md должен упоминать поле 'platform_vs_product'"
|
||||
)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# 5. cto_advisor НЕ входит в department workers
|
||||
# ===========================================================================
|
||||
|
||||
class TestCtoAdvisorNotInDepartments:
|
||||
"""cto_advisor является опциональным специалистом — не входит в departments."""
|
||||
|
||||
def test_cto_advisor_not_in_any_department_workers(self):
|
||||
"""cto_advisor не является обязательным членом ни одного департамента."""
|
||||
data = _load_yaml()
|
||||
for dept_name, dept in data.get("departments", {}).items():
|
||||
workers = dept.get("workers", [])
|
||||
assert "cto_advisor" not in workers, (
|
||||
f"cto_advisor не должен быть в workers департамента '{dept_name}'. "
|
||||
"cto_advisor — опциональный стратегический ревьюер, не постоянный участник."
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue