Rules for service, CLI, shared types and infrastructure code in this repository. Use when implementing anything behind the interface.
Scanned 8/31/2026
Install via CLI
openskills install Inebrio/Routerly---
name: backend-conventions
description: Rules for service, CLI, shared types and infrastructure code in this repository. Use when implementing anything behind the interface.
---
# Backend conventions
Binding detail lives in `.Codex/rules/service.md` and `.Codex/rules/cli.md`.
Read the one covering what you touch. This is the short list you must not
get wrong.
## Everywhere
- TypeScript ESM. Relative imports carry the `.js` extension, Node builtins
carry the `node:` prefix.
- Types come from the shared package. Never redeclare one locally.
- Tests are `*.test.ts` beside the source they cover.
## Service
- Config writes go through `writeConfig()`. Never `fs.writeFile` on config.
- A new management endpoint needs all four: Zod body validation, permission
check, the route itself, and a `fastify.inject()` test proving allowed →
200 and forbidden → 403.
- Bearer tokens are stored as SHA-256. Passwords are bcrypt, 12 rounds.
Never log either, never return either.
- Runtime state is JSON under `ROUTERLY_HOME`. There is no database.
## CLI
- HTTP goes through `api.ts`. Never fetch directly.
- The service URL comes from the active account's `serverUrl` in `store.ts`.
Never hardcode it.
- Refresh the token silently before every call when `expiresAt` has passed.
- Errors to stderr with exit code 1. Success to stdout with exit code 0.
- `--json` where piping makes sense, and its output must always parse.
- Register new commands in `index.ts`, or they do not exist.
## Scripts against live external services
A script that creates a disposable GitHub repo, hits a real API, or runs a
live release tool is expensive to redo: minutes of wall-clock, a fresh
external resource, and a full agent turn budget, every time it fails on
something you could have caught for free.
- **Sanity-check before the first live run.** `bash -n` the script. Re-read
it for shell portability this machine actually has — `grep -P`/`-Pq`
silently misbehaves here because this is BSD grep, not GNU; prefer
`gh api --jq` or POSIX-safe patterns. Check whether an early step
(`npm install`, a build) leaves the tree dirty in a way a later
`git checkout` in the same script will collide with. A live run that fails
on a bug like this bought no evidence and cost a real repo.
- **Wait for it with one blocking call, not a loop of turns.** For a script
that logs many lines over minutes, do not stream it with `Monitor`
line-by-line and then fill turns with idle placeholder commands between
notifications — each notification and each placeholder is a paid turn,
and a chatty script can burn the whole budget before you ever reach the
step that writes your report. Prefer a single foreground `Bash` call with
a timeout that covers the expected wall-clock, or `run_in_background`
plus one wait for the process to actually exit. Read the resulting log
file once it is done, rather than watching it happen.
## Before you report done
- The package builds and its tests pass.
- The command you claim works has been run, and you have its output.
- Anything you could not verify is stated as unverified, not as done.
No comments yet. Be the first to comment!