RTK Rust Design Patterns
rtk-ai/rtk
Describes seven Rust design patterns for the RTK CLI filter modules, with when to use each, RTK examples, and notes on when a pattern is overkill.
Architecture, naming, and testing conventions guidelines for architecting Rust codebases.
$ npx skills add sergigp/yarrtube --skill rust-architect -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install sergigp/yarrtube rust-architect --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/rust-architect .claude/skills/rust-architect && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .claude/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architectType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add sergigp/yarrtube --skill rust-architect -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install sergigp/yarrtube rust-architect --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/rust-architect .agents/skills/rust-architect && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .agents/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add sergigp/yarrtube --skill rust-architect -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install sergigp/yarrtube rust-architect --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/rust-architect .cursor/skills/rust-architect && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .cursor/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/sergigp/yarrtube.git --path .claude/skills/rust-architect--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add sergigp/yarrtube --skill rust-architect -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install sergigp/yarrtube rust-architect --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/rust-architect .gemini/skills/rust-architect && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .gemini/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install sergigp/yarrtube rust-architectInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add sergigp/yarrtube --skill rust-architect -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/rust-architect .github/skills/rust-architect && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .github/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add sergigp/yarrtube --skill rust-architect -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install sergigp/yarrtube rust-architect --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/sergigp/yarrtube.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/rust-architect .opencode/skills/rust-architect && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "rust-architect" agent skill from https://github.com/sergigp/yarrtube/tree/main/.claude/skills/rust-architect into .opencode/skills/rust-architect/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "rust-architect", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
rust-architectArchitecture, naming, and testing conventions guidelines for architecting Rust codebases.
Rust Architect is an agent skill from sergigp/yarrtube. Architecture, naming, and testing conventions guidelines for architecting Rust codebases. Use when writing, reviewing, or refactoring Rust code: creating a new module or file, adding a repository/port/service/entity/value object, adding an HTTP handler or CLI command, deciding where a new .rs file goes, or writing/naming Rust tests.
Its SKILL.md is about 6.1k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Refactoring. It works with Rust, YouTube, SQLite and Docker. The repository describes itself as: Self-hosted YouTube playlist & channel synchronizer for your NAS, built on yt-dlp. No ads, no algorithm. The licence is MIT.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 8263380. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are rust).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Rust Architect loads about 6.1k tokens when it runs. Until then it costs about 87 tokens; SKILL.md has 2,998 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from sergigp/yarrtube at commit 8263380, republished under its MIT licence (© sergigp). 2,998 words, ~6,051 tokens.
.claude/skills/rust-architect/SKILL.md (or your agent's skills folder).We try to follow a Domain Driven Design (DDD) approach with hexagonal architecture (aka ports and adapters) with some opinionated decisions. We try to follow clean architecture and we give a lot of importance to tests.
We structure our code in three layers: application, domain, and infrastructure.
This is the entry point to the application, the most common case will be http controllers but it can also be a CLI application, subscribers (message consuming from a queue system), etc. The main responsibility of this layer is to VALIDATE entry data and TRANSFORM it into Value Objects (VO). In the case of http this layer is the one responsible of parsing HTTP requests, validating the data and returning HTTP errors if not valid, transforming into VO and calling the domain layer, in the case that this the method needs to return a response it will transform the domain response into a response DTO and return it to the caller with the correct HTTP status code.
This is where the core BUSINESS LOGIC of the application lives. It is composed of entities, value objects, domain services, and domain events. The main responsibility of this layer is to implement the business rules and logic of the application and ORCHESTRATE calls to the ports in the infrastructure layer. We need to try to not depend on any external libraries or frameworks but some concessions could be done.
This layer is responsible for implementing ports and COMMUNICATING WITH EXTERNAL SYSTEMS. Most of the time we will be coding repositories to access the database, but external clients to external APIs should be implemented here too. As a convention, even if this is not entirely correct bc this should go in the domain layer, we will place the trait of the repository here, near the implementation bc it's convenient when adding new methods to the repository. repositories/ holds one aggregate's dedicated port implementation (a port injected into a single aggregate's domain service); shared/ holds infra usable across aggregates, regardless of whether it's a port (e.g. the clock, domain events) — see the file structure below.
We must not hide behaviour in this layer, repositories should be as simple as possible and should behave like collections with methods like find, find_by_id, save, delete, etc. The domain layer should be the one that implements the business logic, rules and entity transformations, not the infrastructure layer. The infrastructure layer should be as simple as possible and should not contain any business logic like domain event publishing, it should only contain the operations on entities and optional monitoring. Write operations such as save, insert, update should have the entity as a parameter, not the properties. Delete could operate on the entity identifier instead of the entity itself and read methods such as find(id), find_all, etc should always return entities or collections of entities.
We try to organize our domain code into modules with the aggregate name as module name. This is an example of how we would structure the code:
src/
domain/
<aggregate>/
<value_object>.rs # one file per value object, named after the type
<entity>.rs # named after the type, even if it repeats the folder name
errors.rs # every error type for this aggregate, together
services/ # every aggregate's use-case services, together, flat
<use_case>.rs # one domain service per use case, e.g. widget_creator.rs
application/ # every external entry point (adapters), one subfolder per interface
http/ (or grpc/, etc.)
mod.rs # ApiServices + router wiring only
error.rs # ApiError + From<ValidationError>
blocking.rs # run_blocking
validation.rs # required() + shared missing-field messages
<resource>/
mod.rs # handlers only
dto.rs # request/response wire types + From<Domain> conversions
cli/ # only if the project has a CLI
mod.rs # Cli/Commands definitions (this is the CLI's "routing")
<command>.rs # one file per command's orchestration
subscribers/ # event subscribers, one file per subscriber
<subscriber>.rs
tasks/ # scheduled/background task handlers, one file per task
<task>.rs
infrastructure/
repositories/ # one aggregate's dedicated port implementation
<implementation>_<port>.rs # trait + its implementation, together
client/ # port-shaped adapters nothing in domain/ injects
<port>.rs
shared/ # infra usable across aggregates (ports or not)
<thing>.rsWe prefer not generic names: not entity.rs, not value_objects.rs, not ports.rs. A file is named after the single type/concept it holds (user.rs holds User, user_id.rs holds UserId).
errors.rs is the one deliberately generic name: every error type for an aggregate (use-case error enums) lives together in one file, not scattered across the files that raise them.
Every value object constructor fails with the single shared ValidationError(String) (domain/shared/errors.rs), never a per-VO error type. The application layer maps it once (e.g. impl From<ValidationError> for ApiError → 400), so handlers build VOs with a plain ?.
Every value object gets its own file. Don't bundle multiple value objects into one "value objects" file.
A port is named after the concept it fronts, not the one method it happens to expose (WidgetRepository, not WidgetLookup, even if today it only has an exists method). "Repository" is used loosely for "adapter implementing a domain port," not strictly persistence.
infrastructure/repositories/ files are named <implementation>_<port>.rs (sqlite_playlist_repository.rs implements PlaylistRepository with SQLite, youtube_video_downloader_repository.rs implements VideoDownloaderRepository against YouTube/yt-dlp). The prefix signals which technology backs the port. infrastructure/shared/ and infrastructure/client/ files are named after the port/thing itself, not this convention, since they aren't per-aggregate repositories.
A repository/port method either reads (returns anyhow::Result<Entity> / anyhow::Result<Vec<Entity>> / anyhow::Result<Option<Entity>>, an entity or collection, never a wrapper/outcome enum) or writes (returns anyhow::Result<()>, void on success). No custom infra error types (RepositoryError/LookupError and friends), infra failures are untyped anyhow::Error. A Repository always returns the entities that its name implies (UserRepository returns User).
Business-meaningful outcomes that look like they belong in the repository (e.g. "was this newly created, or did it already exist?") are decided in the domain service, not returned by infra: call find, branch on Some/None, then call insert/save. This trades DB-level atomicity for keeping business logic out of infra, a known, accepted race window, not an oversight.
State transitions live on the entity, never as behavior-named repository methods or as anemic domain models operated from domain service. We prefer immutable state transitions: a transition method takes self by value and returns a new Self (via struct-update syntax, Self { field: new_value, ..self }) rather than mutating &mut self. Name such a method with_<field> when it simply sets one field (e.g. with_thumbnail); reserve a verb-based name (mark_downloaded, start_download, reset_for_redownload) for a transition that carries additional domain meaning beyond "set this field".
Fn ordering: within any impl block, and among free functions in a file, order is: new (if it exists), then every pub method or trait-impl method (trait-impl methods are the type's public surface even without the pub keyword), then private/helper methods. This applies uniformly, no exceptions — including repositories' row_to_* mapping helpers, which go after the trait-impl methods they support, not before.
Domain service layout: every domain service file is laid out in four blocks, top to bottom, so a reader meets the contract before the implementation:
impl with only new.pub trait <Service>Api: Send + Sync declaring every public operation (doc comments describing the contract go here, on the trait method, not on the impl).impl <Service>Api for <Service> with the implementation of those operations.impl <Service> holding every private helper, never pub.Guideline (not a hard rule): every public operation (block 3) should read at a glance — aim for 10–15 lines of body after rustfmt. It reads as a sequence of named steps (let channel = self.find_channel(&id)?; self.delete_channel_videos(&id)?; ...), each step a private helper in block 4 named for what it does. Lookup-or-fail matches, multi-arm outcome handling, event building and repeated map_err chains are the usual candidates to extract. When two arms or two operations share logic (e.g. marking a failed download as errored), extract it once rather than shrinking each copy. Exceptions are fine for genuinely complex flows (e.g. reconciliation) where splitting further would scatter one coherent algorithm across helpers that only make sense together — prefer readability over the line count.
Callers keep depending on the concrete service type (State<PlaylistCreator>, video_downloader: VideoDownloader) and just use the …Api trait to call it; the trait exists to make the contract readable, not to add dynamic dispatch. Supporting types the service returns (e.g. an outcome enum) and constants go above the struct.
pub struct WidgetCreator {
repository: Arc<dyn WidgetRepository>,
}
impl WidgetCreator {
pub fn new(repository: Arc<dyn WidgetRepository>) -> Self {
Self { repository }
}
}
pub trait WidgetCreatorApi: Send + Sync {
fn create(&self, id: WidgetId) -> Result<Widget, CreateWidgetError>;
}
impl WidgetCreatorApi for WidgetCreator {
fn create(&self, id: WidgetId) -> Result<Widget, CreateWidgetError> {
self.ensure_absent(&id)?;
// ...
}
}
impl WidgetCreator {
fn ensure_absent(&self, id: &WidgetId) -> Result<(), CreateWidgetError> {
// ...
}
}Read models (*View, e.g. TaskView, ChannelView) hold the flat fields they expose, not the entity they're derived from.
Domain services are call-agnostic: they know nothing about HTTP, CLI, subscribers, or tasks. Adapting any external trigger into a domain call — parsing/validating input, invoking the domain service, mapping its result back — is the application layer's sole responsibility.
State<PlaylistCreator>), never the whole ApiServices. ApiServices derives FromRef so axum resolves the sub-state.Response: Result<(StatusCode, Json<T>), ApiError> when the status varies, Result<Json<T>, ApiError> for a plain 200, Result<StatusCode, ApiError> for bodiless responses.ApiError (status + message, rendered as {"error": ...}). Input is validated with ? only: VOs via From<ValidationError>, missing fields via required(request.field, MISSING_X)? (http/validation.rs).Err(e @ CreateChannelError::Lookup(_)) => Err(ApiError::new(StatusCode::BAD_GATEWAY, e))), no catch-all arm. A mapping repeated across handlers gets one small function.run_blocking (http/blocking.rs), since services are synchronous (SQLite, blocking HTTP). Don't judge per call whether it's needed.#[serde(flatten)]-ing another response DTO. A genuinely nested JSON object (e.g. a video's source) gets its own DTO.Tests are classified by where they enter the code, not by what they fake. Our goal is to couple our tests as much as possible to behaviour instead of implementation, so we can refactor the code without breaking the tests.
Acceptance and behaviour tests follow the same rules for persistence and test doubles (see below): persistence is real, only external dependencies are faked. The Playwright suite in smoke-tests/ is the true end-to-end layer (real binary, real browser) and lives outside these conventions.
This tests the domain logic and the validations at application level. We will place this tests in application (for example in http controllers or event subscribers) and the test will be the type of "I receive this request and I expect this response and these collateral effects". In the case of event subscribers we will send events and assert the final state of the repositories. Very similar for Tasks, we will create tasks and assert the final state of the repositories.
HTTP acceptance tests call the handler function directly with only the service it uses, not through a Router. A small helper per handler unwraps the Json (create(service, request) -> Result<(StatusCode, ChannelResponse), ApiError>). Route wiring (paths, methods, which handler each route reaches) is covered by the Playwright suite in smoke-tests/, not by Rust tests: every route the UI calls must be exercised by some spec there.
Every test has the same 7 steps, top to bottom and inline: TestDatabase + repositories/fakes → seed them → build the service with Service::new(..) → build the request → call the handler → assert the response → assert side effects (repositories, outbox events, scheduled tasks, fake state).
service() → service_with() builder chains. They hide which dependencies a test uses and make it easy to skip asserting them. The one allowed exception is a constructor helper for a service with many ports no test observes: it takes the asserted repositories as parameters and fills in the rest (channel_video_reconciler(&db, channel_repository, ..)). A test that observes one of the ports the helper fills in builds the service inline with Service::new(..) instead of growing the helper or adding a second one.assert_eq!: Ok((StatusCode::CREATED, some_channel_response())), Err(ApiError::bad_request("<exact message>")), repository.list().unwrap() == vec![..]. Never field by field, never len(), never raw JSON (that only re-tests serde).CreateChannelRequest { quality: None, ..create_request("@x") }).it_should_* test, then helpers.A domain service reached from several adapters (e.g. the reconcilers, called from a task, a subscriber and an HTTP handler) has its logic tested exhaustively through one primary adapter, the one that does the recurring work (the task). Every other adapter tests only what differs for it (e.g. force_reconcile not rescheduling, error mapping) plus one happy path proving the service is wired. When an adapter's call path starts to diverge inside the service, the tests for that difference go in that adapter.
Tests whose request is rejected before reaching the service (validation 4XX) assert the response only and don't need a database: their any_<service>() helper builds repositories on an unmigrated in-memory connection (Connection::open_in_memory()), so a request that wrongly got through fails loudly instead of passing.
Ideally we should not tests domain services at all because the logic there is tested from acceptance tests. There could be exceptions for very complex domain logic that is hard to test from application layer, but this should be the exception and not the rule. When one is justified, it calls the domain service directly and asserts its result plus the final state of the repositories, under the same persistence and fakes rules as acceptance tests: a behaviour test is never a reason to keep a fake of our own persistence alive. Before adding one, check the acceptance tests of the adapters calling that service don't already cover it.
Persistence is real, not faked. Because our database is an embedded SQLite file, which is fast, cheap to create and is the production engine, acceptance and behaviour tests wire the real Sqlite* repositories against a fresh database per test instead of fakes. This catches SQL, row-mapping, ordering, upsert and constraint bugs that a hand-written fake silently re-implements (and drifts from). Concretely:
let db = TestDatabase::new(); (infrastructure/shared/sqlite_connection.rs): a freshly migrated database file in its own temp directory, removed when the test ends, so tests run in parallel without sharing state. No pool of pre-migrated databases: migrating a fresh one costs a few milliseconds.SqliteXRepository::new(db.connection()) (or db.shared_connection() for the ones taking Arc<Mutex<Connection>>), opened with the same sqlite_connection::open as production (WAL, busy timeout), so tests exercise the real multi-connection setup.insert, list, find, list_for_...), never through raw SQL. When a whole-table assertion needs a read the port doesn't have (e.g. listing every video), add a #[cfg(test)] inherent method on the SQLite implementation rather than widening the port for tests.ChannelVideo { id: 1, ..ChannelVideo::create(..) }) rather than masking them.SqliteEventPublisher writes them, and the test reads them back with SqliteEventRepository::list_eligible(), comparing against the expected ScheduledEvent rows (a pending_event(id, DomainEvent) helper builds them). Scheduled tasks likewise through the real SqliteTaskRepository (list_non_completed()).Fakes are for everything else: out-of-process or side-effecting dependencies (external APIs such as YouTube, yt-dlp, outbound HTTP, and, for now, filesystem-backed ports). The fakes will be hand-written and will be as simple as possible, they will not use any mocking library. The fakes will be state-based, for example a fake backed by a Mutex<Vec<T>>, and may offer constructors for failure scenarios (e.g. failing(), unavailable()).
The Clock is treated like a port and faked (FixedClock) so time is deterministic; it is also what makes stored timestamps (e.g. event created_at) assertable.
Each value object's validation rules are tested exhaustively in its own file (every accepted and rejected input, asserting the exact Ok(vo) / Err(ValidationError(message))). Acceptance tests must not repeat those rules: per endpoint, one invalid-value test proves the ValidationError → 4XX mapping, plus tests for the adapter's own checks (e.g. a required field missing from the request). Services take value objects as parameters, so the type system already guarantees a handler validates before calling the domain.
Infrastructure tests use the real dependency, colocated with the code. Some examples:
Keep tests in #[cfg(test)] mod tests in the same file as the code they test by default. Only introduce a top-level tests/ directory (which needs a lib.rs, turning the project into a library + thin binary) when a project independently justifies it — not just to match a template.
it_should_<behaviour>[_if_<condition>], short enough to read at a glance — drop the _if_... part when there's no meaningful precondition beyond "given valid input". This naming focuses on behaviour instead of implementation.it_should_create_a_channel, it_should_list_all_playlists, it_should_download_the_video.fail: it_should_fail_to_create_if_path_missing, it_should_fail_if_invalid_handle_provided.skip / ignore: it_should_skip_if_playlist_is_gone, it_should_ignore_reconcile_of_a_missing_channel.fail_to_delete_… vs fail_to_reconcile_…).Option and Result types instead of nulls and exceptions. We will use iterators instead of for loops when possible. We will use map, filter, fold, etc. instead of imperative loops when possible.Some of these are deliberate deviations from DDD/hexagonal orthodoxy, for convenience or to avoid overengineering:
infrastructure/, not domain/ (dependency direction inverted, traded for editing convenience).anyhow::Error until the domain service gives it meaning.find then insert), not one atomic upsert.domain/services/, not a separate application/ layer. The application/ folder holds only adapters (HTTP, CLI, subscribers, tasks) that translate an external trigger into a domain call — business orchestration itself never lives there.If code looks like it violates textbook architecture in one of these ways, it's probably intentional. Ask before changing it.
© sergigp, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/rust-architect of sergigp/yarrtube.
Open the folder on GitHubat commit 8263380
Rust Architect next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Rust Architect this skillsergigp/yarrtube | 133 | — | ~6.1k | Automated safety check: Pass | MIT | |
| RTK Rust Design Patternsrtk-ai/rtk | 83k | — | ~1.9k | Automated safety check: Pass | Apache-2.0 | |
| Pnpm Engineteambit/bit | 18k | — | ~1.9k | Automated safety check: Pass | Custom licence | |
| Code Refactor Reviewkcsujeet/ilamy-calendar | 351 | 2 repos | ~1.6k | Automated safety check: Pass | MIT | |
| Create Release Checklistsoftware-mansion/smelter | 734 | — | ~1.9k | Automated safety check: Notes | Custom licence | |
| Coding Agentmastra-ai/mastra | 29k | — | ~2.3k | Automated safety check: Pass | Custom licence |
rtk-ai/rtk
Describes seven Rust design patterns for the RTK CLI filter modules, with when to use each, RTK examples, and notes on when a pattern is overkill.
teambit/bit
Work on the pnpm Rust engine (@pnpm/napi, the pacquet crates) that bit install runs through.
kcsujeet/ilamy-calendar
Reviews code changes for reuse, composition, codebase consistency, and slop.
software-mansion/smelter
Generate a GitHub release-checklist issue for a full (non-RC) release of the Smelter server and/or the TypeScript SDK.
mastra-ai/mastra
Authoring playbook for building agents that write, edit, review, or refactor code.
Qovery/console
Qovery Console coding standards, architecture guidelines, naming conventions, testing practices, and development workflows.
Categories
Architecture, naming, and testing conventions guidelines for architecting Rust codebases. Rust Architect is an agent skill from sergigp/yarrtube. Architecture, naming, and testing conventions guidelines for architecting Rust codebases.
Rust Architect fits situations like: refactoring Rust code: creating a new module; adding a repository/port/service/entity/value object; adding an HTTP handler; deciding where a new .rs file goes.
Run `npx skills add sergigp/yarrtube --skill rust-architect -a claude-code`. Or copy the skill folder (.claude/skills/rust-architect in sergigp/yarrtube) into .claude/skills/rust-architect in your project. Claude Code loads it when a task matches its description.
Run `npx skills add sergigp/yarrtube --skill rust-architect -a codex`. Or copy the skill folder (.claude/skills/rust-architect in sergigp/yarrtube) into .agents/skills/rust-architect in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add sergigp/yarrtube --skill rust-architect -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/rust-architect, .gemini/skills/rust-architect, .github/skills/rust-architect and .opencode/skills/rust-architect in your project.
SKILL.md names no scripts, command-line tools or credentials: Rust Architect is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Rust Architect is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.1k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Rust Architect: RTK Rust Design Patterns (rtk-ai/rtk, 83k stars), Pnpm Engine (teambit/bit, 18k stars), Code Refactor Review (kcsujeet/ilamy-calendar, 351 stars) and Create Release Checklist (software-mansion/smelter, 734 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
sergigp (a GitHub user) maintains it in sergigp/yarrtube, which has 133 GitHub stars. The repository was last updated on October 7, 2026.
Source: sergigp/yarrtube on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.