# AGENTS.md Guidelines for agentic coding agents working in this repository. ## Project Overview `rp` is a Python CLI tool for rich-printing files (markdown, JSON, code, etc.) in the terminal. It uses `rich` for rendering and `typer` for the CLI interface. - **Language**: Python 3.12+ - **Package Manager**: uv - **Entry Point**: `rp` → `rp.cli:app` - **Source Location**: `src/rp/` ## Build/Lint/Test Commands ### Setup ```bash uv sync # Install dependencies ``` ### Running the CLI ```bash uv run rp # Rich-print a file uv run rp --help # Show help uv run rp --version # Show version echo '{"a":1}' | uv run rp # Read from stdin cat patch.txt | uv run rp --guess-content # Enable heuristic content guessing ``` ### Testing Run tests with pytest: ```bash # Run all tests uv run pytest # Run a single test file uv run pytest tests/test_detect.py # Run a single test class uv run pytest tests/test_detect.py::TestExplicitType # Run a single test function uv run pytest tests/test_detect.py::TestExplicitType::test_explicit_overrides_everything # Run with verbose output uv run pytest -v ``` ### Linting & Type Checking (recommended) ```bash uv run ruff check src/ # Lint uv run ruff format src/ # Format uv run mypy src/ # Type check ``` ## Code Style Guidelines ### Imports Order imports using standard library → third-party → local grouping: ```python from __future__ import annotations import sys from pathlib import Path from typing import Annotated import typer from rich.console import Console from rp import __version__ from rp.detect import detect_type ``` - Always use `from __future__ import annotations` at the top for modern type hint syntax - Use explicit imports (no `from module import *`) - Import `Path` from `pathlib`, not `os.path` ### Formatting - **Line length**: 88 characters (ruff default) - **Quotes**: Prefer double quotes for strings, single quotes inside f-strings if needed - **Trailing commas**: Use in multi-line collections and function arguments ### Type Hints - Use modern union syntax: `str | None` instead of `Optional[str]` - Use `Annotated[Type, typer.Option(...)]` for CLI parameters - Always annotate function parameters and return types - Use `dataclass` for simple data containers ```python def detect_type( path: str | None = None, content: str | None = None, explicit_type: str | None = None, guess_content: bool = False, ) -> str: ... ``` ### Naming Conventions - **Modules**: lowercase with underscores (`detect.py`, `render.py`) - **Classes**: PascalCase (`RenderOptions`) - **Functions/Variables**: snake_case (`detect_type`, `file_path`) - **Constants**: UPPER_SNAKE_CASE (`EXTENSION_MAP`, `_FILENAME_MAP`) - **Private module-level**: prefix with underscore (`_MARKDOWN_TYPES`) - **CLI parameter names**: Avoid Python builtins (use `file_type` not `type`) ### Error Handling - Use `rich.Console` for user-facing error messages with `[red]Error:[/red]` prefix - Exit with `typer.Exit(code=1)` for errors, `typer.Exit()` for success - Catch specific exceptions, not bare `except:` ```python try: content = file.read_text(encoding="utf-8") except UnicodeDecodeError: console.print(f"[red]Error:[/red] Cannot read binary file: {file}") raise typer.Exit(code=1) except (PermissionError, OSError) as e: console.print(f"[red]Error:[/red] {e}") raise typer.Exit(code=1) ``` - For fallback behavior (e.g., unknown lexer), catch broadly but document why: ```python try: renderable = Syntax(content, file_type, ...) except Exception: # Unknown lexer — fall back to plain text renderable = Syntax(content, "text", ...) ``` ### CLI Patterns - Use `typer.Option()` with `--flag/--no-flag` for boolean toggles - Use `Annotated[Type, typer.Option(...)]` for all options - Set `no_args_is_help=False` when stdin input is valid - Use callbacks for `--version` with `is_eager=True` ### Module Structure Each module should have: 1. Module docstring (one line) 2. `from __future__ import annotations` 3. Standard library imports 4. Third-party imports 5. Local imports 6. Constants (module-level) 7. Classes 8. Functions ### File Type Detection When adding new file types: 1. Add extension to `EXTENSION_MAP` in `detect.py` 2. Use Pygments lexer name as the value 3. For special filenames (e.g., `Dockerfile`), add to `_FILENAME_MAP` Detection stays conservative by default: explicit type, filename map, extension map, shebang, and JSON detection all run before falling back to `text`. Heuristic Pygments content guessing is opt-in via `--guess-content` and should remain a late fallback. ### Rendering - `markdown` and `md` → use `rich.markdown.Markdown` - `json` → use `rich.json.JSON` with fallback to `Syntax` - All other types → use `rich.syntax.Syntax` ### Pager Behavior The pager prefers `less -R --mouse` when available (version 543+) for mouse scrolling. Falls back to Rich's `SystemPager` otherwise. ### Version Sync Version metadata must stay in sync between `pyproject.toml` and `src/rp/__init__.py`. ## Project Structure ``` rich-print/ ├── pyproject.toml # Project config, dependencies, entry point ├── uv.lock # Lockfile (commit this) ├── .gitignore ├── README.md ├── AGENTS.md # This file ├── src/ │ └── rp/ │ ├── __init__.py # Version metadata │ ├── cli.py # Typer CLI entry point │ ├── detect.py # File type detection │ ├── render.py # Rendering logic │ ├── pager.py # Pager integration │ └── py.typed # PEP 561 marker └── tests/ ├── conftest.py ├── test_cli.py ├── test_detect.py ├── test_render.py └── test_pager.py ``` ## Dependencies - `rich>=13.0` — Terminal rendering (Markdown, JSON, Syntax) - `pygments>=2.13.0` — Lexer detection and syntax lexers - `typer>=0.12` — CLI framework Runtime dependencies. Dev dependencies: `ruff>=0.9`, `mypy>=1.14`, `pytest>=8.0`, `pytest-cov>=5.0`, `types-pygments>=2.19`. ## Git Workflow ### Branches - **Long-lived branches** - `main` — stable release branch; only updated via merges from `dev` - `dev` — integration branch for day-to-day development - **Short-lived branches** - `feature/` — new features - `fix/` — bug fixes - `docs/` — documentation changes - `chore/` — maintenance, tooling, CI - `refactor/` — refactoring without behavior change - `test/` — adding or improving tests ### Branching Rules - Start normal work from the latest `dev` - Keep branches short-lived and focused on one logical change - Do not commit directly to `main` or `dev` unless explicitly requested - Delete the short-lived branch after it is merged ### Pull Requests - Open normal PRs into `dev`, not `main` - Use **squash merge** for PRs into `dev` - Ensure CI passes before merging - Keep PRs focused; split unrelated changes into separate PRs ### Release Flow - Merge `dev` into `main` when changes are ready to release - Tag releases from `main` with version numbers (e.g., `v1.2.3`) - If a hotfix is made directly against `main`, merge it back to `dev` ### Agent Expectations - Unless the user says otherwise, assume new work should target `dev` - If a branch must be created, follow the naming rules above - Do not create or push branches automatically unless the user asks - Do not commit changes unless the user explicitly asks ## Notes for Agents - This is a small, focused CLI tool — prefer simplicity over abstraction - The `src/` layout is intentional for proper packaging - The `py.typed` marker indicates type hints are available to consumers - When in doubt, follow patterns from existing code in the same file