How to think about names in the Grida repo — not conventions, but what a name commits you to, reveals about the system, and costs to change. The central discipline is that a strict, honest name refuses to grow, and that refusal drives the repo's shape (flat modules, small agnostic packages, suffix siblings). Use when planning a new package, crate, module, directory, route group, or test corpus — the name comes first.
55
62%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/naming/SKILL.mdVanilla conventions (snake_case in Rust, kebab-case in JS/TS,
PascalCase exports, use-* hooks) are table stakes — assume them.
This document is about the observations on top: what a name commits
you to, what it reveals, and what it costs when it's wrong.
Pick the name before the types, before the tests, before the file exists. If the name doesn't come easily, the design isn't ready — don't start coding; sharpen the concept until the name falls out. Maintainability is downstream of naming. How a module grows, how cleanly it retires, how safely it can be deleted — all of it is decided at the moment you choose what to call it.
A name's primary job here is to refuse the wrong content. A
module called painter should feel actively wrong to host a
layout helper; a package called @grida/cmath should feel wrong
to host color logic. The strictness is deliberate — it is the
mechanism that keeps features from leaking into each other.
"Strict" requires "honest." A name that no longer describes what's inside has stopped being a gate: it won't reject foreign additions, and new readers can't trust it. When contents drift past the name, you have two moves — rename to match what the module has become, or extract the drifted pieces out — and you must pick one promptly. Letting a name go stale is how codebases rot quietly.
Three consequences cascade from the gate discipline, and together they produce the current repo structure:
src/ or a package's
src/. When you're tempted to grow a third level, the parent's
name has stopped describing what's inside — flatten or extract,
don't nest.painter.rs +
painter_debug_node.rs + painter_geometry.rs) or whether it
collapses into the existing file. Both preserve the parent's
scope; a new subdirectory quietly widens it.@grida/* packages and small crates precisely
because this extraction was made each time a sibling would have
diluted the parent's name.The gate only works because each module commits to one thing. Two-thing modules can't enforce either scope — the name becomes ambiguous as a filter, and new additions slip in under whichever reading is convenient. When you notice a module doing two things, pick the primary, rename for it, and extract or delete the other. The repo's proliferation of tiny packages is this choice compounded.
A well-named module exhibits two properties under change. Treat them as the measurable signal that naming is doing its work:
A codebase where neither test passes easily has a naming problem
dressed up as an architecture problem. The inverse is the real
payoff: when both tests pass, code grows by adding siblings or
spawning small packages, and it retires by a single git rm.
The cost of a bad name is rename friction × fanout. Fanout is
set by where the name is visible, and the difference is severe:
git mv + import updates. Cheap.@grida/cg, Cargo name) is seen
by every call site in every branch in every downstream repo.
Rename cost: coordinated migration, deprecation window, semver
break.This asymmetry is why the directory name and the published name
can diverge. packages/grida-canvas-cg publishes as
@grida/cg: the long directory pays for browsability (the
canvas family clusters in the file tree) where rename is cheap;
the short scope pays for ergonomics where rename is expensive.
The Rust side, by contrast, aligns — the engine repo's
crates/grida (gridaco/nothing) publishes as grida because
the core crate is also the project's public namespace, and
keeping the two in lockstep removes a name to remember. Two different trade-offs; pick per
surface. Invest heavily in a name before it escapes its
file; once it's a public surface, the name is a commitment.
A name that feels hard to pick is telling you something about the module, not your vocabulary. Common tells:
grida-canvas/canvas-text/ is the symptom;
grida-canvas/text/ is the correction.Naming is the cheapest design review you get. Listen to it when it resists.
Two-letter names (cg, fe, k/, q/) are not
abbreviations — they are assertions that nothing else in this
parent competes for the slot. The assertion is load-bearing;
reviewers rely on it to mean "this is the canvas-graphics
module," not "one of several."
The bar to mint one: would adding any peer to this parent make
the terse name ambiguous? If yes, qualify now. If no, terseness
pays — proportionally to how often the name appears at call sites.
Long breadcrumb names earn their length by narrowing; every
segment in grida-canvas-react-renderer-dom discriminates against
a sibling that differs at that segment. Segments that don't
narrow are decoration, and decoration erodes trust in the ones
that do.
The alphabetic sort in a file tree is the primary lookup index
most readers use. Domain first, role last makes the tree a
usable index — the canvas family clusters, its react variants
cluster under that, the DOM renderer variant under that.
Role-as-prefix inverts this and scatters siblings across the
alphabet by what they do rather than what they're of. That's
why *-hosted, *-wasm, *-react, *-renderer-<backend> are
always suffixes.
@grida/* and the grida-canvas-* package family are not filing
conventions — they are assertions that these packages share
release cadence, review ownership, and compatibility guarantees.
Adding a package to the scope is a governance decision.
react-p-queue lives unscoped because it doesn't derive identity
from Grida.
The same is true of lifecycle signals in names — -legacy,
-experimental-*, x- (cross-cutting / vendor-adjacent),
-hosted. They carry more trust than documentation because they
sit in the name itself, and that trust decays the moment they
stop being accurate. Prune: promote out of experimental/ when
the shape settles; remove legacy/ when the replacement is done;
don't let x- become the label for "anything weird."
A flat directory of kebab-breadcrumb filenames is an index optimized for grep and prefix-completion — the reader's first motion — not browsing. Nest only when a subfolder would be a browseable category a reader would open without knowing the case they want. Almost no real test corpus is that.
(www) / (site) / (workbench) / (workspace) / (tenant)
encode who is on the other side of the screen, not which
feature lives here. Each group is a surface with its own auth,
chrome, analytics, and deployability story. Adding a group is an
architectural commitment; if it's a new feature for an existing
reader, it belongs inside an existing group.
See cases.md for concrete tables and the
grandfathered short-name list.
2e0d276
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.