|
| 1 | +--- |
| 2 | +applyTo: "pyrit/output/**" |
| 3 | +--- |
| 4 | + |
| 5 | +# PyRIT Output Module — Coding & Review Guidelines |
| 6 | + |
| 7 | +For full architecture documentation, usage examples, and extension guides, see [doc/code/output/0_output.py](../../../doc/code/output/0_output.py). |
| 8 | + |
| 9 | +This file covers the rules for **writing and reviewing** code in `pyrit/output/`. |
| 10 | + |
| 11 | +## Critical Rules |
| 12 | + |
| 13 | +### Output goes through the sink — never call `print()` directly |
| 14 | + |
| 15 | +All rendering methods return `str`. The inherited `write_async` calls `render_async` then `_write_async(content)`. No bare `print()` calls anywhere in the output module except inside `StdoutSink`. |
| 16 | + |
| 17 | +When reviewing: reject any `print()` call outside `StdoutSink`. |
| 18 | + |
| 19 | +### Data fetching belongs in leaf classes only |
| 20 | + |
| 21 | +Format classes (`PrettyAttackResultPrinter`, `MarkdownAttackResultPrinter`) must not import or reference `CentralMemory`. Only `*MemoryPrinter` leaf classes do data I/O. |
| 22 | + |
| 23 | +When reviewing: reject any `CentralMemory` import in a non-leaf file (`pretty.py`, `markdown.py`, `json.py`). |
| 24 | + |
| 25 | +### Sinks must use async I/O |
| 26 | + |
| 27 | +Sink implementations must not block the event loop. Use `asyncio.to_thread()` or native async libraries for I/O operations. `FileSink` uses an `asyncio.Lock` to prevent concurrent write races. |
| 28 | + |
| 29 | +When reviewing: reject synchronous `open()`, `write()`, or network calls inside a sink's `write_async`. |
| 30 | + |
| 31 | +## Three-Layer Hierarchy |
| 32 | + |
| 33 | +Every domain follows this structure. Do not mix responsibilities across layers. |
| 34 | + |
| 35 | +| Layer | File | Responsibility | May import CentralMemory? | |
| 36 | +|-------|------|---------------|---------------------------| |
| 37 | +| **Base** | `base.py` | Abstract data-fetching methods + abstract `render_async` | No | |
| 38 | +| **Format** | `pretty.py`, `markdown.py`, `json.py` | Implements `render_async`, returns `str` | No | |
| 39 | +| **Leaf** | Same file as format (e.g., `PrettyAttackResultMemoryPrinter`) | Implements data methods via CentralMemory; forwarding `render_async` | Yes | |
| 40 | + |
| 41 | +### File names = output format |
| 42 | + |
| 43 | +- `pretty.py` — ANSI-colored human-readable |
| 44 | +- `markdown.py` — Markdown |
| 45 | +- `json.py` — structured JSON |
| 46 | + |
| 47 | +### Memory leaf classes must work with zero args |
| 48 | + |
| 49 | +```python |
| 50 | +printer = PrettyAttackResultMemoryPrinter() # defaults: StdoutSink, matching sub-printers |
| 51 | +await printer.write_async(result) |
| 52 | +``` |
| 53 | + |
| 54 | +Pass `sink=` to redirect output. Pass sub-printers only to override defaults. |
| 55 | + |
| 56 | +### Convenience functions live in `helpers.py` |
| 57 | + |
| 58 | +Every new domain printer **must** have a corresponding convenience function added to `helpers.py`. This is the primary entry point most callers use. |
| 59 | + |
| 60 | +```python |
| 61 | +from pyrit.output.helpers import output_attack_async |
| 62 | +await output_attack_async(result, format="pretty") |
| 63 | +``` |
| 64 | + |
| 65 | +`helpers.py` resolves `format` → printer class, `sink` → Sink, and calls `write_async`. |
| 66 | + |
| 67 | +When reviewing: if a new domain printer is added without a helper function, request one. |
0 commit comments