"""Regression tests for KIN-DOCS-002 — Standardise all agent prompts. Acceptance criteria: 1. pytest green (checked by running this suite) 2. No file in agents/prompts/ contains the old '## Output format' section header 3. Every prompt file contains '## Return Format' 4. Every prompt file contains the full standard structure: ## Working Mode, ## Focus On, ## Quality Checks, ## Return Format, ## Constraints """ from pathlib import Path import pytest PROMPTS_DIR = Path(__file__).parent.parent / "agents" / "prompts" # Single source of truth for required sections (decision #920) REQUIRED_SECTIONS = [ "## Working Mode", "## Focus On", "## Quality Checks", "## Return Format", "## Constraints", ] # Files excluded from standard-structure checks (decision #917/#918) # All 9 previously excluded files now contain all 5 required sections — list is empty. # Guard-тест TestExclusionListIsEmpty (decision #929) не даст тихо добавить файлы обратно. EXCLUDED_FROM_STRUCTURE_CHECK = [] def _prompt_files(): return sorted(PROMPTS_DIR.glob("*.md")) def _active_prompt_files(): """Prompt files not in the exclusion list (decision #918).""" return [f for f in _prompt_files() if f.name not in EXCLUDED_FROM_STRUCTURE_CHECK] _ACTIVE_PROMPT_NAMES = [f.name for f in _active_prompt_files()] # --------------------------------------------------------------------------- # AC-2: No legacy '## Output format' section # --------------------------------------------------------------------------- class TestNoLegacyOutputFormatSection: """Проверяет отсутствие устаревшей секции '## Output format' во всех промптах.""" def test_no_prompt_contains_old_output_format_header(self): """Ни один файл agents/prompts/*.md не содержит '## Output format'.""" files_with_old_header = [ f.name for f in _prompt_files() if "## Output format" in f.read_text(encoding="utf-8") ] assert files_with_old_header == [], ( f"Файлы с устаревшей секцией '## Output format': {files_with_old_header}" ) def test_grep_output_format_is_empty(self): """Эквивалент: grep -rl '## Output format' agents/prompts/ — пустой вывод.""" matches = [ str(f) for f in _prompt_files() if "## Output format" in f.read_text(encoding="utf-8") ] assert matches == [], ( "grep -rl '## Output format' agents/prompts/ должен давать пустой вывод, " f"но нашёл: {matches}" ) # --------------------------------------------------------------------------- # AC-3: Every prompt contains '## Return Format' # --------------------------------------------------------------------------- class TestAllPromptsContainReturnFormat: """Проверяет наличие секции '## Return Format' во всех промптах.""" def test_return_format_count_equals_prompt_count(self): """Число промптов с '## Return Format' равно общему числу промптов.""" all_files = _prompt_files() files_with_rf = [f for f in all_files if "## Return Format" in f.read_text(encoding="utf-8")] assert len(files_with_rf) == len(all_files), ( f"Промптов всего: {len(all_files)}, " f"с '## Return Format': {len(files_with_rf)}. " f"Без секции: {[f.name for f in all_files if f not in files_with_rf]}" ) @pytest.mark.parametrize("prompt_file", [f.name for f in sorted(PROMPTS_DIR.glob("*.md"))]) def test_each_prompt_has_return_format(self, prompt_file): """Каждый промпт-файл содержит секцию '## Return Format'.""" content = (PROMPTS_DIR / prompt_file).read_text(encoding="utf-8") assert "## Return Format" in content, ( f"{prompt_file} не содержит секцию '## Return Format'" ) # --------------------------------------------------------------------------- # AC-4: Full standard structure in every active prompt (decision #917-#920) # --------------------------------------------------------------------------- class TestAllPromptsContainStandardStructure: """Проверяет наличие полного набора обязательных секций во всех активных промптах. Requirements: 1. Каждый активный промпт содержит все 5 секций из REQUIRED_SECTIONS в правильном порядке """ @pytest.mark.parametrize("section", REQUIRED_SECTIONS) @pytest.mark.parametrize("prompt_file", _ACTIVE_PROMPT_NAMES) def test_prompt_has_required_section(self, section, prompt_file): """Каждый активный промпт содержит каждую из 5 обязательных секций.""" content = (PROMPTS_DIR / prompt_file).read_text(encoding="utf-8") assert section in content, ( f"{prompt_file!r} не содержит обязательную секцию {section!r}" ) # --------------------------------------------------------------------------- # Sanity: prompt count stays at 25 # --------------------------------------------------------------------------- class TestPromptCount: """Проверяет, что число промптов не изменилось неожиданно.""" def test_prompt_count_is_25(self): """В agents/prompts/ ровно 25 файлов .md.""" count = len(_prompt_files()) assert count == 25, ( f"Ожидалось 25 промптов, найдено {count}. " "Если добавлен новый промпт — обнови этот тест." ) # --------------------------------------------------------------------------- # Guard: exclusion list must stay empty (decision #929) # --------------------------------------------------------------------------- class TestExclusionListIsEmpty: """Регрессионный guard против молчаливого роста EXCLUDED_FROM_STRUCTURE_CHECK. Если нужно добавить файл обратно в exclusion — этот тест заставит явно обосновать причину и обновить его (decision #929). """ def test_exclusion_list_is_empty(self): """EXCLUDED_FROM_STRUCTURE_CHECK должен оставаться пустым. Все 9 ранее excluded файлов содержат все 5 стандартных секций. Добавление файла в exclusion лишает его регрессионной защиты (decision #921). Чтобы добавить файл — сначала обоснуй причину и обнови этот тест. """ assert EXCLUDED_FROM_STRUCTURE_CHECK == [], ( f"EXCLUDED_FROM_STRUCTURE_CHECK должен быть пустым, " f"но содержит: {EXCLUDED_FROM_STRUCTURE_CHECK}. " "Добавление файла в exclusion лишает его регрессионной защиты (decision #921)." )