Add AGENTS.md with build commands and code style guidelines for AI agents
This commit is contained in:
170
AGENTS.md
Normal file
170
AGENTS.md
Normal file
@@ -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 <test_name> # 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<T> 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<String> },
|
||||
Rectangle { rx: Option<f32>, ry: Option<f32> },
|
||||
}
|
||||
|
||||
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<RwLock<Option<ElementTree>>>
|
||||
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::<AgentRequest>(&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"}
|
||||
```
|
||||
Reference in New Issue
Block a user