from app.config.ai_prompt_registry import ( PROMPT_REGISTRY_MAP, PROMPT_FAMILY_ALLOWED, get_prompt_registry, get_prompt_registry_entry, iter_prompt_registry_keys, ) from app.config.ai_prompts import ( PROMPT_DECOMPOSE_TASKS, PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION, PROMPT_FEISHU_REQUIREMENT_ANALYSIS, PROMPT_SAVED_IDEA_ENRICHMENT, PROMPT_TASK_BREAKDOWN, ) from app.config.ai_prompts_catalog import __all__ as PROMPT_CATALOG_EXPORTS def test_prompt_registry_covers_all_catalog_exports() -> None: catalog_keys = set(PROMPT_CATALOG_EXPORTS) registry_keys = set(iter_prompt_registry_keys()) assert catalog_keys.issubset(registry_keys) assert set(PROMPT_REGISTRY_MAP) == registry_keys for key in catalog_keys: entry = get_prompt_registry_entry(key) assert entry is not None assert entry["prompt_key"] == key assert entry["prompt_family"] in PROMPT_FAMILY_ALLOWED assert entry["responsibility_scope"] assert entry["priority"] in {"high", "medium", "low"} assert entry["audience"] assert entry["editable_policy"] assert entry["runtime_source"] assert entry["risk_level"] in {"critical", "high", "medium", "low"} def test_prompt_registry_returns_detached_copies() -> None: registry = get_prompt_registry() original_key = registry[0]["prompt_key"] registry[0]["prompt_key"] = "BROKEN_KEY" assert PROMPT_REGISTRY_MAP[original_key]["prompt_key"] == original_key def test_high_frequency_registry_entries_have_complete_metadata() -> None: expected_families = { "PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION": "classification", "PROMPT_FEISHU_LEVEL_CLASSIFICATION": "classification", "PROMPT_FEISHU_SIGNAL_EXTRACTION": "extraction", "PROMPT_FEISHU_TASK_ORCHESTRATION": "planning", "PROMPT_SIGNAL_PLANNER": "planning", "PROMPT_GENERATE_CHARTER": "planning", "PROMPT_DECOMPOSE_TASKS": "planning", "PROMPT_PROJECT_STATUS_QA": "response", "PROMPT_MEMBER_WORKLOAD_QA": "response", "PROMPT_AI_PMO_QA": "response", "PROMPT_EXTERNAL_TREND_QA": "response", "PROMPT_INDUSTRY_TREND_QA": "response", } for prompt_key, family in expected_families.items(): entry = get_prompt_registry_entry(prompt_key) assert entry is not None assert entry["module"] assert entry["business_scenario"] assert entry["input_contract"] assert entry["output_contract"] assert isinstance(entry["call_sites"], tuple) assert entry["call_sites"], prompt_key assert entry["prompt_family"] == family assert entry["responsibility_scope"] signal_planner_entry = get_prompt_registry_entry("PROMPT_SIGNAL_PLANNER") assert signal_planner_entry is not None assert "charter_json" in signal_planner_entry["input_contract"] assert any("mixin_prompt.py" in site for site in signal_planner_entry["call_sites"]) assert any("mixin_analyze.py" in site for site in signal_planner_entry["call_sites"]) assert "规划" in signal_planner_entry["responsibility_scope"] def test_deprecated_prompt_aliases_point_to_canonical_prompts() -> None: registry_keys = set(iter_prompt_registry_keys()) assert "PROMPT_FEISHU_REQUIREMENT_ANALYSIS" not in registry_keys assert "PROMPT_TASK_BREAKDOWN" not in registry_keys assert PROMPT_FEISHU_REQUIREMENT_ANALYSIS == PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION assert PROMPT_TASK_BREAKDOWN == PROMPT_DECOMPOSE_TASKS def test_prompt_registry_includes_runtime_guardrails_and_inline_inventory() -> None: expected = { "PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT": "guarded_release", "PROMPT_RAG_QUERY_SEMANTICS": "guarded_release", "PROMPT_INTERNAL_TARGET_RESOLUTION": "guarded_release", "PROMPT_EXTERNAL_QUERY_PLAN": "guarded_release", "PROMPT_SEMANTIC_FACT_EXTRACT_PROJECT": "guarded_release", "PROMPT_SIGNAL_WEBPAGE_VALUE_ANALYSIS": "needs_migration", "PROMPT_AUDIT_FUNCTIONALITY_REVIEW": "needs_migration", "PROMPT_LLM_STRUCTURED_JSON_REPAIR": "needs_migration", } for prompt_key, editable_policy in expected.items(): entry = get_prompt_registry_entry(prompt_key) assert entry is not None assert entry["editable_policy"] == editable_policy assert entry["call_sites"] def test_pmflow_ai_prompt_registry_uses_current_user_side_scene_descriptions() -> None: expected = { "PMFLOW_SEMANTIC_CONTEXT_DECISION_PROMPT": "上下文与主链边界", "PROMPT_PMFLOW_AI_ROUTE_PLANNER": "旧路由规划", "PROMPT_PMFLOW_AI_TURN_GOAL_STATE_ASSESSOR": "TurnState 目标评估", "PROMPT_PMFLOW_AI_TURN_STATE_ANSWER_SYNTHESIZER": "TurnState 受控答案合成", "PROMPT_CUSTOMER_ANSWER_COMPOSER": "客户答案表达", "PROMPT_CUSTOMER_ANSWER_VERIFIER": "客户答案质检", "PROMPT_RAG_FOLLOWUP_ANCHOR_SEMANTICS": "续问锚点", "PROMPT_RAG_PENDING_CLARIFICATION_SELECTION_SEMANTICS": "待澄清项", } for prompt_key, expected_text in expected.items(): entry = get_prompt_registry_entry(prompt_key) assert entry is not None assert expected_text in entry["business_scenario"] assert any(term in entry["responsibility_scope"] for term in ("不执行", "不生成", "不做", "只输出")) def test_saved_idea_enrichment_prompt_covers_non_idea_greeting_input() -> None: entry = get_prompt_registry_entry("PROMPT_SAVED_IDEA_ENRICHMENT") assert entry is not None assert any("space_saved_idea_service.py" in site for site in entry["call_sites"]) assert "问候" in entry["input_contract"] assert "问候" in PROMPT_SAVED_IDEA_ENRICHMENT assert "readiness_score 输出 0.0" in PROMPT_SAVED_IDEA_ENRICHMENT assert "这个想法具体想解决什么问题?" in PROMPT_SAVED_IDEA_ENRICHMENT