Skip to content

Commit 8d05dad

Browse files
committed
Minor CI and env changes; add AGENTS.md
1 parent 24284d5 commit 8d05dad

File tree

3 files changed

+212
-2
lines changed

3 files changed

+212
-2
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
prek:
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/checkout@v5
9+
- uses: actions/checkout@v6.0.2
1010
- name: Set up Python
1111
uses: actions/setup-python@v5
1212
with:
@@ -25,14 +25,15 @@ jobs:
2525
matrix:
2626
python-version: ["3.13", "3.14"]
2727
steps:
28-
- uses: actions/checkout@v5
28+
- uses: actions/checkout@v6.0.2
2929
- name: Set up Python ${{ matrix.python-version }}
3030
uses: actions/setup-python@v5
3131
with:
3232
python-version: ${{ matrix.python-version }}
3333
- name: Install dependencies
3434
run: |
3535
python -m pip install --upgrade pip
36+
pip install -e "."
3637
pip install -e ".[dev]"
3738
- name: Test with pytest
3839
run: |

AGENTS.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# AGENTS.md — infrared-protocols
2+
3+
Guidelines for agentic coding agents working in this repository.
4+
5+
---
6+
7+
## Project Overview
8+
9+
`infrared-protocols` is a pure-Python library (Python ≥ 3.13) that encodes infrared
10+
remote-control protocols (e.g. NEC) into raw pulse/space timing sequences. The public
11+
API surface is small and intentionally minimal.
12+
13+
---
14+
15+
## Environment Setup
16+
17+
```bash
18+
# Bootstrap dev environment (uses uv)
19+
./script/setup.sh
20+
21+
# Equivalent manual step
22+
uv pip install -e ".[dev]"
23+
```
24+
25+
Dev extras install: `pytest`, `prek` (lint/format/type-check runner).
26+
27+
---
28+
29+
## Build / Lint / Test Commands
30+
31+
### Run all lint, format, and type-check hooks (changed files only)
32+
```bash
33+
prek
34+
```
35+
36+
### Run all hooks on every file
37+
```bash
38+
prek --all-files
39+
```
40+
41+
### Run linter only (with auto-fix)
42+
```bash
43+
ruff check --fix infrared_protocols/
44+
```
45+
46+
### Run formatter only
47+
```bash
48+
ruff format infrared_protocols/
49+
```
50+
51+
### Run type checker only
52+
```bash
53+
basedpyright infrared_protocols/
54+
```
55+
56+
### Run all tests
57+
```bash
58+
pytest --log-cli-level=debug
59+
```
60+
61+
### Run a single test file
62+
```bash
63+
pytest tests/test_commands.py
64+
```
65+
66+
### Run a single test by name
67+
```bash
68+
pytest tests/test_commands.py::test_nec_command_get_raw_timings_standard
69+
```
70+
71+
> CI tests against Python 3.13 and 3.14 via GitHub Actions.
72+
73+
---
74+
75+
## Repository Structure
76+
77+
```
78+
infrared_protocols/ # Library source (only this directory is linted/type-checked)
79+
__init__.py # Public API: defines __all__ and re-exports
80+
commands.py # All domain logic: Command ABC, NECCommand, Timing
81+
tests/
82+
test_commands.py # pytest suite (all tests in one file)
83+
script/
84+
setup.sh # Dev environment bootstrap
85+
.pre-commit-config.yaml # ruff, ruff-format, basedpyright hooks
86+
pyproject.toml # Build config, ruff rules, pyright settings
87+
```
88+
89+
---
90+
91+
## Code Style
92+
93+
### Formatting
94+
- Enforced by `ruff-format` (Black-compatible).
95+
- **Indentation:** 4 spaces.
96+
- **Quotes:** Double quotes (`"..."`).
97+
- **Line length:** 88 characters (Black default).
98+
- **Trailing commas:** Required in multi-line collections and argument lists.
99+
- **Semicolons:** Never.
100+
- Two blank lines between top-level definitions; one blank line between methods.
101+
102+
### Imports
103+
- **Within the package:** use relative imports (`from .commands import ...`).
104+
- **In tests:** use absolute imports from the installed package (`from infrared_protocols import ...`).
105+
- Named imports only; no wildcard imports (`from x import *`).
106+
- Import order is enforced by ruff's `I` (isort) rules: stdlib → third-party → local.
107+
- `__all__` must be defined in `__init__.py` to explicitly declare the public API.
108+
109+
### Naming
110+
| Element | Convention | Example |
111+
|---|---|---|
112+
| Files / modules | `snake_case` | `commands.py` |
113+
| Packages | `snake_case` | `infrared_protocols` |
114+
| Classes | `PascalCase` | `NECCommand`, `Timing` |
115+
| Functions / methods | `snake_case` | `get_raw_timings` |
116+
| Variables / attributes | `snake_case` | `high_us`, `repeat_count` |
117+
| Local numeric constants | `snake_case` (not `UPPER_CASE`) | `leader_high = 9000` |
118+
| Test functions | `test_<subject>_<description>` | `test_nec_command_get_raw_timings_standard` |
119+
| Hex literals | Uppercase hex digits | `0xFF`, `0x04FB` |
120+
121+
> Note: Local numeric constants intentionally use `snake_case` rather than
122+
> `SCREAMING_SNAKE_CASE`. Follow this convention throughout.
123+
124+
### Types
125+
- Type checker: `basedpyright` with `typeCheckingMode = "standard"`.
126+
- **All** function parameters and return types must be annotated.
127+
- `-> None` must be explicit on `__init__` and void methods.
128+
- Use PEP 585 lowercase generics: `list[Timing]`, not `List[Timing]`.
129+
- Use PEP 604 union syntax: `T | None`, not `Optional[T]`.
130+
- Use `@override` (from `typing`, Python 3.12+) on every overridden method.
131+
- No `Any`; avoid `cast`; prefer real type narrowing.
132+
- Inline variable annotations where needed: `timings: list[Timing] = []`.
133+
134+
### Classes
135+
- Abstract base classes use `abc.ABC` and `@abc.abstractmethod`.
136+
- Immutable value objects use `@dataclass(frozen=True, slots=True)`.
137+
- Constructor arguments should be **keyword-only** (use `*` separator) to prevent
138+
positional-argument confusion.
139+
140+
### Docstrings
141+
- All public classes and methods must have a docstring.
142+
- First line: concise one-line summary.
143+
- Multi-line: blank line after the summary, then prose description. No Google/NumPy
144+
parameter sections unless complexity demands it.
145+
146+
```python
147+
def get_raw_timings(self) -> list[Timing]:
148+
"""Get raw timings for the NEC command.
149+
150+
NEC protocol timing (in microseconds):
151+
- Leader pulse: 9000µs high, 4500µs low
152+
...
153+
"""
154+
```
155+
156+
---
157+
158+
## Architecture
159+
160+
### Adding a New Protocol
161+
1. Subclass `Command` (ABC) in `infrared_protocols/commands.py`.
162+
2. Implement `get_raw_timings(self) -> list[Timing]`.
163+
3. Decorate the override with `@override`.
164+
4. Define timing constants as local `snake_case` variables inside the method.
165+
5. Re-export the new class from `infrared_protocols/__init__.py` and add it to
166+
`__all__`.
167+
168+
### Key Abstractions
169+
- **`Timing(high_us, low_us)`** — frozen dataclass representing one pulse+space pair
170+
(microseconds). Immutable, comparable by value.
171+
- **`Command` (ABC)** — base class for all IR protocol encoders. Holds `modulation`
172+
and `repeat_count`.
173+
- **`NECCommand(Command)`** — encodes the NEC protocol; reference implementation.
174+
175+
### Patterns to Follow
176+
- Build timing lists by starting with a base list, appending in a loop, then using
177+
`extend()` for repeat frames.
178+
- Repeat-code frame gaps replace the last timing's `low_us` (see `NECCommand`).
179+
- Bit manipulation uses masks like `data & 1`, `data >>= 1`, `(~x) & 0xFF`.
180+
181+
---
182+
183+
## Testing
184+
185+
- No mocking. Tests use pure value comparison against manually constructed
186+
`list[Timing]` fixtures.
187+
- One assertion per logical case; reuse expected values with list unpacking
188+
(`[*expected[:-1], ...]`) rather than duplicating fixtures.
189+
- Tests live in `tests/test_commands.py`; keep them in one file unless the suite
190+
grows substantially.
191+
- Pre-commit hooks (`prek`) do **not** run on `tests/`; the type checker and linter
192+
only target `infrared_protocols/`. Tests are still expected to be clean Python.
193+
194+
---
195+
196+
## Error Handling
197+
198+
- The library currently has no custom exceptions. Incorrect inputs surface as natural
199+
Python runtime errors (`TypeError`, `ValueError`).
200+
- Correctness is enforced primarily through the type checker and immutable value
201+
objects rather than defensive runtime checks.
202+
- If you add validation, raise standard built-in exceptions with descriptive messages
203+
rather than introducing custom exception classes unless there is a clear consumer
204+
need.

script/setup.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
#!/usr/bin/env bash
2+
3+
# Stop on errors
4+
set -e
5+
6+
uv pip install -e "."
27
uv pip install -e ".[dev]"

0 commit comments

Comments
 (0)