Appearance
Code style
Two rules about where code goes, one formatter, and one habit about comments. Everything else is whatever the file around you already does.
bash
bun run format # biome, writing
bun run lint # biome, checking - what CI runs
bun run typecheck # tsc --noEmitThe formatter decides layout, not you
Biome, configured in biome.json: two spaces, a line width of 100, and the recommended lint preset. There is nothing to discuss in a review about where a brace goes.
bun run format writes; bun run lint is the same check without writing and is what the pipeline runs. If those disagree with something you wrote, the formatter is right.
One rule is switched off in fourteen files
noTemplateCurlyInString, for the files that generate other files. A compose stub or an nginx conf legitimately contains ${VITE_PORT} as literal text, and the rule cannot tell that from a template literal somebody forgot to interpolate. The list is in biome.json and it is a list rather than a global setting on purpose.
src/core/ is pure
No filesystem, no processes, no sockets, no adapter imports. Same input, same answer, always.
text
src/
core/ pure logic - config, plans, checks, generators
adapters/ the outside world
commands/ the CLI surface, thin
ports/ the interfaces adapters implementtests/core/core-purity.test.ts enforces it and fails with the offending file and the reason. It looks for node:fs, node:child_process, Bun.spawn and imports from ../adapters.
This is not taste. It is what makes the second rule affordable:
src/core/** must be 100% covered, checked by scripts/coverage-gate.ts rather than a project-wide percentage, because a percentage lets a well-tested core hide an untested adapter. Core has no I/O, so full coverage costs almost nothing to write.
A line that is hard to cover is usually in the wrong file
If a core function needs a mock, it is reaching for the world and belongs in an adapter, with the decision it was making left behind in core.
Everything that touches the world goes through a port
src/ports/ declares the interfaces; src/adapters/ implements them. Core takes ports as arguments and is tested against in-memory fakes (tests/helpers/fake-fs.ts, tests/helpers/fake-proc.ts).
That is also why a fifth backend would be an adapter rather than a change to the commands: dqx up on Docker Compose and on DDEV are the same code path with a different implementation underneath.
Three guards worth knowing before you write
- The
www/guard. No file outside a small allow-list may assume a project's code sits underwww/. A single-repo project has no such level, and the same wrong assumption once appeared in four places, none of which failed loudly. AskcheckoutDir/checkoutPathinsrc/core/plan/layout.tsinstead. - Site-scoped commands. Anything taking
--sitedefaults it to the site whose checkout you are standing in, matched on the directory rather than the domain.tests/cli/site-scoped.test.tsenforces it; a command whose--siteonly narrows a project-wide job is listed there with its reason. - The dashboard's colours.
ui/src/styles.csswrites a colour in exactly two places -:root[data-theme="dark"]and:root[data-theme="light"]- and both define the same token names; a component names no colour at all (Mark.vueexcepted, because the brand gradient is the same in both themes).tests/cli/ui-theme.test.tsenforces that, checks the two token sets match, and computes WCAG AA contrast from the values, so a nudged colour fails a test rather than a reader. Adding a colour means adding a token to both themes.
Comments explain why, not what
A comment that restates the line above it is noise. A comment naming the failure that made the line necessary is the reason this codebase is navigable at all:
ts
// `.Label "…"`, not `index .Labels "…"`: in `docker ps` the labels are one string, and the index
// form fails with "cannot index slice/array with type string" - measured, and it silently made
// every project look stopped.Most of the sharp ones exist because something failed in a way that was hard to see. Leave them where they are, and when you change the code around one, update it or delete it - a comment that no longer matches is worse than none.
What not to write: a docblock that repeats the signature, a banner separating sections, commented-out code, a changelog in a comment, or a ticket key as an explanation.
Prose that reaches a person
Messages, prompts and documentation share one rule that a test enforces on the site: no em-dashes. Write the sentence without one, or use a plain hyphen. tests/site/plain-punctuation.test.ts fails the build for a page, generated pages included.
The other half is advice you can follow. A check that says "run this to fix it" and names a command that does not fix it costs more than silence, because the developer now believes the tool tried. Several remedies here were rewritten once somebody actually followed them.
Before you push
bash
bun run test:gate # build, tests, coverage gate - what CI runs
bun run lint
bun run typecheckbun test being green is not enough on its own: an embedded text import needs its declaration in src/text-imports.d.ts, and only typecheck says so.