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