These apply to humans and agents alike. Read only the section you need.
- Layering and allowed dependencies are defined in ARCHITECTURE.md. Pure (L0) crates use no tokio, do no IO, and never read the wall clock; inject a
Clockinstead.just arch-checkenforces both from that table; the one exempt line (the system clock) carries// arch-check: allow (<reason>). - One crate, one responsibility, with a minimal public API.
- Every
lib.rsstarts with a//!contract covering purpose, public API summary, invariants, dependencies and how to test. Its first line is the one-line summary thatdocs/MAP.mdpicks up. - Rust is the source of truth for cross-language types. DTOs derive
TS, andjust genwrites them toweb/src/api/gen/. Never hand-edit generated files.coreandmodeltypes derive it behind theirtsfeature (#[cfg_attr(feature = "ts", derive(ts_rs::TS))]); ts-rs's serde warnings are off, so a type with custom serde (try_from, a hand-writtenSerialize, a foreign field type) needsts(type = "…")orts(as = "…")(D-041). - Node-side code lives in
node-agent/(ucode plus POSIX sh). Arguments travel as JSON or argv arrays and are never interpolated into shell strings. The one exception is a checked integer formatted with%dwherepopentakes no argv (logread's line count, D-036).
- Edition 2024 on the stable toolchain pinned in
rust-toolchain.toml.#![forbid(unsafe_code)]unless an exception is justified in the crate contract. - Errors:
thiserrorin libraries (typed, with context fields) andanyhowonly in the binary. Nounwrap()outside tests;expect()only with a stated invariant. - Async: tokio. Never block in async code; SQLite runs on its own thread or actor. Loops must be cancellation-safe.
tracingspans carrynode_idandjob_id. - Lints: clippy with
-D warnings, using workspace-level lint configuration. - Keep files to about 400 lines or fewer and organize modules by concept. No
utils.rsgrab-bags. - Tests live next to the code (
#[cfg(test)]). Use snapshots viainsta(review withcargo insta review) and property tests viaproptestfor parsers, IPAM and the planner. - Performance: share large facts through
Arcinstead of cloning them, use bounded channels, and batch DB writes. - Dependencies: take the newest release, majors included, with no cooldown (D-049). A crate held back is held by another crate's requirement (such as tokio-tungstenite at the version axum pins); note why where it is pinned.
- Strict TypeScript and no
any(enforced by Biome). Named exports. Components go in PascalCase files, one per file infeatures/; primitives go inui/. - Fetch data only through
lib/api(the typed client) with solid-query. Live updates come throughlib/wssubscriptions. - Styling: Tailwind utilities plus tokens. No ad-hoc colors: Tailwind's default palette is removed, so only the tokens in
ui/tokens.cssexist. No inline styles except dynamic geometry set through the CSSOM (style props); the CSP blocks style attributes in markup. - User-facing strings live in
lib/strings.ts, so the UI is ready for translation. Icons arelucidedata rendered byui/Icon; interactive primitives (dialogs, radio groups, menus) come from Kobalte. - Import directions (
just arch-checkenforces them): features never import other features, and only a feature's tests importapp/;ui/imports onlylib/stringsfrom outside itself;lib/imports onlyapi/;viz/imports onlyui/,lib/andapi/. - Solid 2 (D-045) is not Solid 1: before writing components, read
web/node_modules/solid-js/CHEATSHEET.md(about 7k tokens; its last section lists what changed). The usual traps:createEffect(compute, apply)has two arguments, no signal writes inside a component body or a memo, props are values (<X v={count()} />) and never destructured, andclass={[...,{ on: cond }]}replacesclassList. - Tests: Vitest + Testing Library for components that contain logic; Playwright for user flows (against demo mode). jsdom has no layout, so
src/test/setup.tsstubs what Kobalte needs; components that use the router are tested insidecreateAppRouter(memoryHistory(path)).
- Target the ucode in OpenWrt 24.10 and later. Feature-detect modules (
requireinsidetry) and fall back to CLI tools. - stdout carries exactly one JSON object per line and nothing else. Debug output goes to stderr.
- Write persistently only in the transaction and guard paths (
/etc/uniwrt). Everything else goes under/tmp/uniwrt. - Each collector is its own file exporting
collect(ctx), registered incollect/index.uc.
just new-feature <area> [--path /page] [--telemetry] scaffolds steps 2, 5 and 6 (and 4 with --telemetry): a placeholder type, route, DTO, page and their tests, wired in and compiling, with TODO(new-feature) markers where the real work goes. It writes nothing if any target exists already, and lists the steps it leaves. just test-new-feature (CI) runs it in a throwaway worktree, so the templates keep up with the code.
- Spec: add or extend the section in
docs/features/<area>.md. Record any decision in DECISIONS.md. - Model: types in
crates/model/src/<area>.rs(desired state and/or facts), plus validation, plus a migration if the schema changes. - Render: a module in
crates/render/src/<package>/with golden tests (fixtures incrates/render/tests/fixtures/). The scaffold skips it until the crate exists. - Telemetry (if needed): a collector in
node-agent/collect/<set>.uc, listed incrates/node/src/bundle.rs, and its parser incrates/node/src/facts/<set>.rswith a fixture. Making it a collect set takes a variant ofproto::ops::Set, its line innode-agent/collect.uc'sSETS, aSetFactsvariant and theNodeFactsfield it fills. - API: routes in
crates/api/src/<area>(a module per area, such asauth/,nodes.rs,ws/) with DTOs that deriveTS, then runjust gen. Live data goes out as a WS topic (ws/topic.rs,ws/subs.rs). - UI:
web/src/features/<area>/(page plus components) built fromui/primitives, with its lazy-loaded route registered inapp/routes.tsxfor a page inapp/nav.ts, its client inlib/api.tsand its strings inlib/strings.ts. - Docs: tick the WP in ROADMAP and update STATUS.
docs/MAP.mdregenerates automatically.
- Format: Conventional Commits as
type(scope): summary. - Types: feat, fix, perf, refactor, docs, test, build, ci, chore. Mark breaking changes with
!or aBREAKING CHANGE:footer. - Scope: a crate or area, for example
feat(render): …orfix(web/topology): …. - One logical change per commit. Commit once a significant change is complete and
just checkpasses. The type drives the release version (see BUILD.md).