From 732e20594325f972ed2b9d9f0fb8e83faa39f432 Mon Sep 17 00:00:00 2001 From: David Ibia Date: Fri, 23 Jan 2026 15:23:40 +0100 Subject: [PATCH] Add AGENTS.md with build commands and code style guidelines for AI agents --- AGENTS.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..af72bb8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,170 @@ +# AGENTS.md - agcanvas + +Guidelines for AI agents working in this Rust codebase. + +## Project Overview + +**agcanvas** is a desktop application for agent-human collaboration via SVG visualization. +- GUI framework: egui/eframe +- SVG parsing: usvg, rendering: resvg/tiny-skia +- Async runtime: tokio +- WebSocket: tokio-tungstenite +- Serialization: serde/serde_json +- Error handling: anyhow/thiserror +- Logging: tracing + +## Build/Lint/Test Commands + +```bash +cargo build # Debug build +cargo build --release # Release build (optimized) +cargo run --release # Run app + +cargo clippy # Run clippy lints +cargo fmt # Format all code + +cargo test # Run all tests +cargo test # Run single test by name +cargo test -- --nocapture # Show println! output + +cargo check # Type check only (fast) +cargo doc --open # Generate and open docs +``` + +## Project Structure + +``` +src/ +├── main.rs # Entry point, window setup +├── app.rs # Main app state, UI (eframe::App impl) +├── element_tree.rs # ElementTree, Element, ElementKind types +├── clipboard.rs # System clipboard integration +├── canvas/ +│ ├── state.rs # Pan/zoom transformation state +│ └── interaction.rs # Mouse/keyboard input handling +├── svg/ +│ ├── parser.rs # SVG -> ElementTree conversion +│ └── renderer.rs # SVG -> pixels (resvg/tiny-skia) +└── agent/ + ├── protocol.rs # JSON message types + └── server.rs # WebSocket server (ws://127.0.0.1:9876) +``` + +## Code Style + +### Imports (ordered with blank lines between groups) +```rust +use std::collections::HashMap; // 1. std +use anyhow::Result; // 2. External crates +use crate::element_tree::ElementTree; // 3. crate:: +use super::protocol::AgentRequest; // 4. super/self +``` + +### Naming +| Item | Convention | Example | +|------|------------|---------| +| Types/Structs | PascalCase | `ElementTree`, `CanvasState` | +| Functions/Methods | snake_case | `parse_svg`, `find_by_id` | +| Constants | SCREAMING_SNAKE | `AGENT_PORT` | +| Enum variants | PascalCase | `ElementKind::Rectangle` | + +### Error Handling +```rust +// Use anyhow::Result for fallible functions +pub fn parse_svg(svg_data: &str) -> Result<(ElementTree, Tree)> { + let tree = Tree::from_str(svg_data, &options)?; // ? operator + Ok((element_tree, tree)) +} + +// Return Option for recoverable errors +pub fn find_by_id(&self, id: &str) -> Option<&Element> { ... } + +// Use anyhow::anyhow!() for error messages +.ok_or_else(|| anyhow::anyhow!("Failed to create pixmap"))? +``` + +### Struct/Enum Definitions +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] // Always derive common traits +pub struct ElementTree { pub root: Element, pub metadata: TreeMetadata } + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] // Use tag for enum serialization +pub enum ElementKind { + Group { name: Option }, + Rectangle { rx: Option, ry: Option }, +} + +impl Default for CanvasState { // Implement Default for configurable structs + fn default() -> Self { Self { offset: Vec2::ZERO, zoom: 1.0, ... } } +} +``` + +### Module Organization +Each module folder has a `mod.rs` that re-exports public items: +```rust +// src/svg/mod.rs +mod parser; +mod renderer; +pub use parser::parse_svg; +pub use renderer::SvgRenderer; +``` + +### Async & Logging +```rust +// Spawn async tasks from runtime +runtime.spawn(async move { + if let Err(e) = server.run().await { + tracing::error!("Agent server error: {}", e); + } +}); + +// Use tracing macros +tracing::info!("Agent server listening on ws://{}", addr); +tracing::debug!("Processing request: {:?}", request); +``` + +### Common Patterns +```rust +// Arc + RwLock for shared state +tree_handle: Arc>> +let mut guard = tree_handle.write().await; +*guard = Some(tree_clone); + +// Option chaining +let svg_data = self.clipboard.as_mut().and_then(|c| c.get_svg()); +name.as_ref().map(|n| format!(" '{}'", n)).unwrap_or_default() + +// Turbofish for type inference +serde_json::from_str::(&text) +``` + +## Formatting + +Run `cargo fmt` before committing: +- 4-space indentation +- Max line width: 100 characters +- Trailing commas in multi-line constructs + +## What NOT to Do + +- **No `unwrap()`** in production paths (use `?` or explicit handling) +- **No clippy suppression** without justification +- **No blocking the main thread** (use async/spawn) +- **No `unsafe`** without clear documentation +- **No unnecessary dependencies** (consider binary size) + +## Agent Protocol + +WebSocket server on `ws://127.0.0.1:9876`: +```json +// Request examples +{"type": "GetTree"} +{"type": "GetElementById", "id": "button-1"} +{"type": "Describe"} +{"type": "GenerateCode", "target": "react", "element_id": null} + +// Response +{"type": "Tree", "tree": {...}} +{"type": "Error", "message": "No SVG loaded"} +```