This document provides guidelines for AI coding agents working on the monocle codebase.
# Format code (always run after editing)
cargo fmt
# Build with default features (cli)
cargo build
# Run all tests
cargo test --all-features
# Run clippy with all warnings as errors
cargo clippy --all-features -- -D warnings
# Run a single test by name
cargo test test_name
cargo test lens::time::tests::test_parse_unix
# Run tests for a specific module
cargo test lens::rpki
# Run tests with output visible
cargo test test_name -- --nocaptureThe GitHub Actions workflow runs these checks on PRs:
cargo fmt --check # Check formatting
cargo clippy --all-features -- -D warnings # Lint
cargo test --all-features --verbose # All testsThe crate enforces strict lints in lib.rs:
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]Use ? operator or explicit error handling instead of .unwrap() or .expect().
Group imports in this order, separated by blank lines:
- Standard library (
std::) - External crates
- Internal crate modules (
crate::,super::)
| Item | Convention | Example |
|---|---|---|
| Lens struct | {Name}Lens |
TimeLens, RpkiLens, InspectLens |
| Args struct | {Name}{Op}Args |
TimeParseArgs, RpkiValidationArgs |
| Result struct | {Name}{Op}Result |
As2relSearchResult, InspectResult |
| Module name | snake_case | as2rel, pfx2as, inspect |
| Constants | SCREAMING_SNAKE_CASE | DEFAULT_RPKI_CACHE_TTL |
Use anyhow::Result for lens methods with descriptive error messages:
// Good
Err(anyhow!("Invalid prefix format: {}. Expected CIDR notation (e.g., 1.1.1.0/24)", input))
// Bad
Err(anyhow!("invalid input"))Use conditional compilation for feature-specific code:
#[cfg_attr(feature = "cli", derive(clap::Args))]
pub struct MyArgs { ... }
// Display (tabled) is always available with lib feature
#[derive(tabled::Tabled)]
pub struct MyResult { ... }Feature flags (3 simple options):
lib: Complete library (database + all lenses + display)server: WebSocket server (implies lib)cli: Full CLI binary (implies lib and server)
src/
├── lib.rs # Public API exports
├── config.rs # Configuration management
├── database/ # Persistence layer (SQLite)
│ ├── core/ # Connection and schema
│ ├── session/ # Ephemeral databases (MsgStore)
│ └── monocle/ # Main repositories (asinfo, as2rel, rpki, pfx2as)
├── lens/ # Business logic layer
│ ├── utils.rs # OutputFormat, shared utilities
│ ├── time/ # Time parsing
│ ├── country.rs # Country lookup
│ ├── parse/ # MRT parsing
│ ├── search/ # BGP search
│ ├── rpki/ # RPKI validation
│ └── inspect/ # Unified inspection
├── server/ # WebSocket server (requires `server` feature)
│ └── handlers/ # Method handlers
└── bin/
├── monocle.rs # CLI entry point
└── commands/ # CLI command handlers
Repositories (database/): Data access only - CRUD, queries, no business logic
Lenses (lens/): Business logic, validation, formatting, coordination
CLI (bin/): Argument parsing, output format selection, error display
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operation() {
let lens = MyLens::new();
let args = MyArgs::new("test");
assert!(lens.lookup(&args).is_ok());
}
// Tests requiring network - mark as ignored
#[test]
#[ignore]
fn test_external_api() { ... }
}- Creating new branches is fine, but do NOT commit or push until explicitly asked
- Keep language factual and professional
- Avoid words like "comprehensive", "extensive", "amazing", "powerful", "robust"
- Use objective language: "Added X", "Fixed Y", "Updated Z"
- Update CHANGELOG.md for every commit - Add entries to "Unreleased changes" section for:
- Breaking changes
- New features
- Bug fixes
- Code improvements
- When pushing commits, list all commits first using
git log --oneline origin/[branch]..HEADand ask for confirmation
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "cli", derive(clap::Args))]
pub struct MyLensArgs {
#[cfg_attr(feature = "cli", clap(value_name = "QUERY"))]
pub query: String,
#[cfg_attr(feature = "cli", clap(short, long))]
pub filter: Option<String>,
}
impl MyLensArgs {
pub fn new(query: impl Into<String>) -> Self {
Self { query: query.into(), ..Default::default() }
}
}pub struct MyLens<'a> {
db: &'a MonocleDatabase,
}
impl<'a> MyLens<'a> {
pub fn new(db: &'a MonocleDatabase) -> Self {
Self { db }
}
pub fn lookup(&self, args: &MyLensArgs) -> Result<Vec<MyResult>> {
Ok(vec![])
}
}ARCHITECTURE.md- Overall project structureDEVELOPMENT.md- Adding new lenses and fixing bugssrc/server/README.md- WebSocket API specificationexamples/README.md- Usage examples by feature tier