| 1 | """Tests for Loader's typed system-prompt builder.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from pathlib import Path |
| 6 | |
| 7 | from loader.context.project import ProjectContext |
| 8 | from loader.runtime.prompting import ( |
| 9 | SYSTEM_PROMPT_DYNAMIC_BOUNDARY, |
| 10 | build_system_prompt_result, |
| 11 | ) |
| 12 | |
| 13 | |
| 14 | def _tool_schema(name: str = "read") -> dict[str, object]: |
| 15 | return { |
| 16 | "name": name, |
| 17 | "description": "Inspect repository files.", |
| 18 | "parameters": { |
| 19 | "type": "object", |
| 20 | "properties": { |
| 21 | "file_path": { |
| 22 | "type": "string", |
| 23 | "description": "Path to inspect.", |
| 24 | } |
| 25 | }, |
| 26 | "required": ["file_path"], |
| 27 | }, |
| 28 | } |
| 29 | |
| 30 | |
| 31 | def test_prompt_builder_renders_dynamic_sections_with_metadata(temp_dir: Path) -> None: |
| 32 | context = ProjectContext( |
| 33 | root=temp_dir, |
| 34 | project_type="python", |
| 35 | package_manager="uv", |
| 36 | test_framework="pytest", |
| 37 | structure=["src/", "tests/"], |
| 38 | test_command="uv run pytest -q", |
| 39 | ) |
| 40 | |
| 41 | result = build_system_prompt_result( |
| 42 | tools=[_tool_schema()], |
| 43 | use_react=False, |
| 44 | project_context=context, |
| 45 | workflow_mode="clarify", |
| 46 | permission_mode="prompt", |
| 47 | cwd=temp_dir, |
| 48 | current_task="Clarify the acceptance criteria for Loader.", |
| 49 | ) |
| 50 | |
| 51 | assert result.prompt_format == "native" |
| 52 | assert result.dynamic_section_names == [ |
| 53 | "Runtime Config", |
| 54 | "Workflow Context", |
| 55 | "Mode Guidance", |
| 56 | "Project Context", |
| 57 | "Project Tips", |
| 58 | ] |
| 59 | assert SYSTEM_PROMPT_DYNAMIC_BOUNDARY in result.content |
| 60 | assert "## Clarify Mode" in result.content |
| 61 | assert "Permission mode: `prompt`" in result.content |
| 62 | assert "Current task: Clarify the acceptance criteria for Loader." in result.content |
| 63 | assert "Package manager: uv" in result.content |
| 64 | |
| 65 | |
| 66 | def test_prompt_builder_keeps_sections_stable_across_formats(temp_dir: Path) -> None: |
| 67 | native = build_system_prompt_result( |
| 68 | tools=[_tool_schema("bash")], |
| 69 | use_react=False, |
| 70 | workflow_mode="execute", |
| 71 | permission_mode="workspace-write", |
| 72 | cwd=temp_dir, |
| 73 | ) |
| 74 | react = build_system_prompt_result( |
| 75 | tools=[_tool_schema("bash")], |
| 76 | use_react=True, |
| 77 | workflow_mode="execute", |
| 78 | permission_mode="workspace-write", |
| 79 | cwd=temp_dir, |
| 80 | ) |
| 81 | |
| 82 | assert native.section_names == react.section_names |
| 83 | assert native.dynamic_section_names == react.dynamic_section_names |
| 84 | assert "`native`" in native.content |
| 85 | assert "`react`" in react.content |
| 86 | assert "<tool_call>" in react.content |
| 87 | assert "call tools" in native.content.lower() |
| 88 | |
| 89 | |
| 90 | def test_execute_mode_guidance_prefers_file_tools_for_text_edits(temp_dir: Path) -> None: |
| 91 | result = build_system_prompt_result( |
| 92 | tools=[_tool_schema("edit")], |
| 93 | use_react=False, |
| 94 | workflow_mode="execute", |
| 95 | permission_mode="workspace-write", |
| 96 | cwd=temp_dir, |
| 97 | ) |
| 98 | |
| 99 | assert "Prefer `edit`/`patch`/`write` over shell one-liners" in result.content |
| 100 | |
| 101 | |
| 102 | def test_prompt_builder_adds_html_guide_quality_guidance(temp_dir: Path) -> None: |
| 103 | result = build_system_prompt_result( |
| 104 | tools=[_tool_schema("write")], |
| 105 | use_react=False, |
| 106 | workflow_mode="execute", |
| 107 | permission_mode="workspace-write", |
| 108 | cwd=temp_dir, |
| 109 | current_task=( |
| 110 | "Create an equally thorough HTML guide with index.html and chapters/ " |
| 111 | "matching the cadence of the reference guide." |
| 112 | ), |
| 113 | ) |
| 114 | |
| 115 | assert "Generated HTML Guide Quality" in result.dynamic_section_names |
| 116 | assert "write each generated page or chapter as a real first pass" in result.content |
| 117 | assert "never after `</html>`" in result.content |