Imported from OyadotAI/oya-browser (
AGENTS.md). Install upstream withnpx skills add OyadotAI/oya-browser. Copyright stays with the author.
Working in this repository
Guidance for AI coding agents and people alike. Read ARCHITECTURE.md first
for the map, then the ARCHITECTURE.md of the part you are changing.
Parts and their commands
| Part | Lint | Types | Tests |
|---|---|---|---|
server/ |
npm run lint · npm run format:check |
npm run typecheck |
npm test (unit + integration) · npm run test:unit · npm run test:coverage |
ui/ |
npm run lint · npm run format:check |
npm run typecheck |
npm test (Vitest) · npm run test:ui (Playwright, needs a running stack) |
browser/ |
npm run lint · npm run format:check |
none (plain JavaScript) | npm test (unit + regressions) · npm run test:shell · npm run test:control · npm run test:identity · npm run test:sync (real Electron) |
packages/* |
npm run lint (root) |
npm run build:sdk |
npm run test:packages (root) |
Run the commands from the part's own folder. The root npm test runs the
server, CLI and package suites.
Rules the linter will hold you to
- Every file starts with a
/** … */header. Every function, class, method, field, type, interface member and route has a doc comment. Say what it is for and why, in plain words, not the name again. - Functions: at most 10 lines of code. React components: at most 50, with their logic in hooks. Classes: at most 200 lines. Split long code into named steps and classes into collaborators.
- No magic numbers. Put each number in the folder's
constants.ts(orconstants.js) under a name. HTTP statuses come from the sharedStatustable. An env-tunable value reads the environment in one place, with a named default.
Design conventions
- Facades: import another module through its
index.tsor entry file, never its internals. Keep a module's exported names stable. When you move code, re-export it from where it was. - Command maps instead of
switch: dispatch on a type throughRecord<type, handler>. Guard the lookup withObject.hasOwn. - Strategies: for interchangeable implementations (transports, providers, launchers), define an interface and pick the implementation in one place.
- Repositories: services never touch
fsor the database directly. - Composition root: services receive their dependencies through their
constructors.
server/src/app/container.tsbuilds them. - Errors: a service throws
HttpError(Status.X, message). Routes stay thin.
Tests
- Placement: unit tests live in
tests/unit/, mirroringsrc/. Name each one after the file it tests:src/a/b.ts→tests/unit/a/b.test.ts. - Runner: Node's built-in
node:testwithnode:assert/strict(server, browser, packages), and Vitest with Testing Library (ui). Add no other test dependencies. - Isolation: tests are hermetic. The server preload
(
tests/support/hermetic.js) stops.envfrom loading and points state at a scratch directory. Fake the seams withtests/unit/support/fakes.ts, and usemock.timersfor anything time-based. Never call the network. - What to test:
- behavior through the public surface, not private helpers
- one test per rule, named after the rule
- the failure paths as well as the happy one
- Coverage: changes must keep coverage at or above the enforced minimum.
Things that must not change casually
server/src/modules/control/egress.tsandbrowser/governance.jsshare a host matcher that must stay byte-identical. A test compares the two.- Scripts injected into web pages (
browser/scripts/analyzer.js,browser/anonymity/*) are visible to the pages they run in, including their source text and shape. Change them only on purpose, and re-run the stealth checks. - Persona fingerprints must be deterministic: the same persona gives the same device, forever.
- Leave commits to the maintainer unless asked.