From 65807eea994cb2c1f9486eb8b2be592850bb38d3 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Thu, 15 Jan 2026 07:13:56 +0530 Subject: [PATCH] Add carmack.rust.md agent-specific language prompt Rust-specific readability guidance for the carmack agent including: - let...else example for shallow control flow - Async: don't block the runtime (tokio::fs, spawn_blocking, Send) - Visibility: prefer pub(crate), private fields with accessors - Generics: impl Trait over explicit params, avoid complex where clauses - Improved iterator guidance: if you need a comment, use a loop - UTF-8 string slicing warnings - Ownership/lifetime pragmatism - Anti-patterns: no macros/typestate/proc-macros unless already in repo Also adds Rust detection to LANGUAGE_PROMPTS (empty base prompt, agent-specific prompts handle the guidance). --- crates/g3-cli/src/language_prompts.rs | 6 +++ prompts/langs/carmack.rust.md | 68 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 prompts/langs/carmack.rust.md diff --git a/crates/g3-cli/src/language_prompts.rs b/crates/g3-cli/src/language_prompts.rs index 388e966..4a15b1f 100644 --- a/crates/g3-cli/src/language_prompts.rs +++ b/crates/g3-cli/src/language_prompts.rs @@ -11,6 +11,11 @@ use std::path::Path; /// The key should match common file extensions or language identifiers. static LANGUAGE_PROMPTS: &[(&str, &[&str], &str)] = &[ // (language_name, file_extensions, prompt_content) + ( + "rust", + &[".rs"], + "", // No base Rust prompt; agent-specific prompts handle this + ), ( "racket", &[".rkt", ".rktl", ".rktd", ".scrbl"], @@ -23,6 +28,7 @@ static LANGUAGE_PROMPTS: &[(&str, &[&str], &str)] = &[ static AGENT_LANGUAGE_PROMPTS: &[(&str, &str, &str)] = &[ // (agent_name, language_name, prompt_content) ("carmack", "racket", include_str!("../../../prompts/langs/carmack.racket.md")), + ("carmack", "rust", include_str!("../../../prompts/langs/carmack.rust.md")), ]; /// Detect languages present in the workspace by scanning for file extensions. diff --git a/prompts/langs/carmack.rust.md b/prompts/langs/carmack.rust.md new file mode 100644 index 0000000..851cbc0 --- /dev/null +++ b/prompts/langs/carmack.rust.md @@ -0,0 +1,68 @@ +Prefer **obvious, readable Rust** over cleverness. + +## Keep control flow shallow +```rust +let Some(user) = get_user(id) else { + return Err(anyhow!("user not found")); +}; +``` +- Prefer early returns (`return`, `?`) over deep nesting. +- Use `let ... else { ... }` to reduce indentation. +- Prefer `match` when it clarifies exhaustiveness; prefer `if let` for single-arm cases. + +## Name things for humans +- Use concrete, intention-revealing names: + - `bytes`, `chars`, `graphemes` (don't call everything `data`) + - `request`, `response`, `state`, `config`, `opts` + - `*_path`, `*_dir`, `*_id`, `*_idx` (be explicit about units) +- If something is a char index, `*_char_idx`, etc. + +## Make invariants explicit in types when cheap +- Prefer newtypes for confusing primitives (`UserId`, `SessionId`) when it reduces mistakes. +- Prefer enums over magic strings/ints for state. + +## Prefer small helper functions over mega-blocks +- Extract helpers when a block: + - has >1 responsibility + - has nested matches/loops + - repeats subtle conditions +- Helpers should have crisp names and minimal parameter lists. + +## Error handling: clarity > cleverness +- Avoid `unwrap()` / `expect()` in production paths. +- Use `anyhow`/`thiserror` patterns already present in the repo; don't introduce new error stacks casually. +- Add context where it matters (`.context("...")`) but don't spray context everywhere. + +## Iterators vs loops +- Prefer a `for` loop when it's clearer than an iterator chain. +- If you need a comment to explain an iterator chain, use a loop instead. +- Use `collect::, _>>()?` when it's idiomatic *AND* readable; otherwise a loop with `push` is fine. + +## Strings are UTF-8: do not do byte slicing +- Never slice `String`/`&str` using byte indices (`s[a..b]`) unless you can prove ASCII. +- Prefer: + - `char_indices()` for char-aware operations + - `unicode-segmentation` graphemes when user-perceived characters matter + - helper functions in-repo (if present) for safe truncation/slicing + +## Ownership/lifetimes: avoid "lifetime gymnastics" +- Prefer owned values at module boundaries if lifetimes complicate readability. +- Avoid returning references tied to complex internal state unless there's a strong perf reason. + +## Async: don't block the runtime +- Never call blocking I/O (`std::fs`, `std::net`) in async functions without `spawn_blocking`. +- Prefer `tokio::fs` over `std::fs` in async contexts. +- Keep futures `Send` unless you have a specific reason not to. + +## Visibility: minimize pub surface +- Prefer `pub(crate)` over `pub` for internal APIs. +- Keep struct fields private with accessor methods when invariants matter. + +## Generics: prefer simplicity +- Prefer `fn process(items: impl Iterator)` over `fn process>(items: I)` when there's only one generic parameter. +- Avoid trait bounds that require reading three lines of `where` clauses. + +## What *NOT* to do +- Do not introduce macros or advanced patterns (typestate, proc-macros, async trait hacks) unless the repo already uses them and it clearly improves readability. +- Do not add `dbg!()` calls in committed code. +- Do not create "god files"—split by responsibility when a module grows unwieldy.