Source code for ralph.display.prompt_reader

"""Safe PROMPT.md reader with size cap, encoding safety, and markup escape."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Mapping
    from pathlib import Path

from rich.markup import escape

from ralph.pro_support.prompt import resolve_effective_prompt_path

MAX_PROMPT_BYTES = 8192
PREVIEW_LINES = 10


[docs] def find_prompt_path( workspace_root: Path, env: Mapping[str, str] | None = None, ) -> Path | None: """Return the path to the workspace prompt file, or ``None`` if absent. The path is resolved through :func:`ralph.pro_support.prompt.resolve_effective_prompt_path` so the ``PROMPT_PATH`` env var is honoured in Pro mode. The engine-owned ``.agent/CURRENT_PROMPT.md`` is still checked as a fallback because it is the materialised prompt the agent actually reads. The caller supplies the ``env`` mapping. Reading ``os.environ`` directly inside the display module would violate the DI invariant enforced by ``tests/display/test_di_invariants.py``. Callers that do not have a pre-resolved env should pass ``context.make_display_context().env`` or fall back to ``os.environ`` at their own call site. """ path = resolve_effective_prompt_path(workspace_root, env) if path.exists(): return path current_prompt = workspace_root / ".agent" / "CURRENT_PROMPT.md" return current_prompt if current_prompt.exists() else None
[docs] def read_prompt_preview(prompt_path: Path) -> tuple[str, ...]: """Read at most MAX_PROMPT_BYTES, decode with errors='replace', return PREVIEW_LINES escaped.""" if not prompt_path.exists(): return ("[dim]PROMPT.md not found[/dim]",) if not prompt_path.is_file(): return ("[dim]PROMPT.md not a file[/dim]",) try: with prompt_path.open("rb") as f: data = f.read(MAX_PROMPT_BYTES) except PermissionError: return ("[dim]PROMPT.md unreadable[/dim]",) text = data.decode("utf-8", errors="replace") lines = text.split("\n")[:PREVIEW_LINES] return tuple(escape(line) for line in lines)