STRUCTURE — how the codebase is organized (agent-first)
Source path: knowledge-base/knowledge/architecture/STRUCTURE.md
# STRUCTURE — how the codebase is organized (agent-first)
> **Read this before adding or moving a file.** It is the mechanical answer to two
> questions an implementer (human or agent) asks constantly: **"where does this
> go?"** and **"what may it import?"** The rules here are enforced by lint
> (`eslint.config.mjs`) and surfaced in the generated map (`knowledge-base/generated/TREE.md`), so they
> are checkable, not just advisory. Architecture narrative: [ARCHITECTURE.md](ARCHITECTURE.md).
> Runtime roles: CLAUDE.md → "One app, four roles".
## 1. The two layered constitutions
The repo has **two parallel layer stacks**, each with a strict one-directional
import rule. Everything below hangs off these.
### 1a. Library layers (authority) — `src/lib/`
Ordered by *who owns truth and where effects happen*:
```
core → (nothing above it) pure domain model, reducer, rules, derivations
engine → core AI providers, prompts, extraction, research
server → core, engine Node-only: record, ops, scheduler, sync, credentials
client → core (+ engine types) browser projection, transports, store, cache
```
### 1b. UI tiers (composition) — `src/ui`, `src/features`, `src/shell`
Ordered by *what composes what*. Composition flows **down, never up**:
```
shell → features → ui the app frame (Stage, rail, topbar, inspector)
features → ui one product capability each (its components/hooks/state)
ui → (nothing above it) leaf primitives: atoms, icons, Modal, effects, StreamingText
```
Both stacks share one meta-rule: **`ui`/`features`/`shell` are client
surfaces — none may import `@/lib/server/*`** (reach server capability through
`@/lib/client` transports).
### 1c. Canonical subfolder taxonomy (parallel structure)
Inside a `ui/`, `features/<capability>/`, or `shell/<area>/` dir, use these
**structural-role** folder names consistently so any dir is predictable:
| Folder | What lives in it |
|---|---|
| `frame/` | the chrome/shell of the area — its container, bars, rails, backdrop (e.g. `stage/frame`, `constellation/frame`, `slides/frame`) |
| `views/` | the main content surfaces (e.g. `stage/views/*`, `constellation/views`) |
| `panels/` | side / detail / inspector panels (always plural) |
| `modals/` | dialogs (always plural) |
| `detail/` | per-entity detail inspectors (`inspector/detail`) |
| `kit/` | that area's shared internal primitives (`inspector/kit`) |
Beyond these, a dir may use **domain-specific** sub-area folders that name a slice
of the capability (e.g. `generation/{branch,thinking,coordination,fields}`,
`game/{scenario,play,setup,table}`, `research/{run,schedule,operations}`,
`access/{auth,onboarding,profile,people}`, `gateway/{landing,console,admin}`). The
`ui/` tier groups by kind instead: `controls/ feedback/ display/ entity/ brand/
icons/ effects/`. Keep small single-capability dirs flat — don't over-folder.
## 2. The import matrix (what the lint enforces)
`✓` = allowed · `type` = type-only allowed (erased at build, no runtime coupling) ·
`✗` = forbidden (lint `warn`). Rows import columns.
| from ↓ / to → | core | engine | server | client | ui | features | shell |
|---|---|---|---|---|---|---|---|
| **lib/core** | ✓ | type | type | type | — | — | — |
| **lib/engine** | ✓ | ✓ | type | type | — | — | — |
| **lib/server** | ✓ | ✓ | ✓ | type | — | — | — |
| **lib/client** | ✓ | ✓ | type | ✓ | — | — | — |
| **ui** | ✓ | — | type | ✓ | ✓ | type | type |
| **features** | ✓ | ✓ | type | ✓ | ✓ | ✓* | type |
| **shell** | ✓ | ✓ | type | ✓ | ✓ | ✓ | ✓ |
`*` cross-feature imports go through the other feature's `index.ts` barrel, and
should be rare — prefer lifting shared logic to `@/lib` or a primitive to `@/ui`.
> **`@/components/*` is a retired tier.** The directory is gone (§6), but the lint
> config still names it in its deny-lists so a reintroduced path fails loudly rather
> than silently. Don't add the column back to this table — treat any `@/components/*`
> import as an error, not a legacy allowance.
Enforcement is `warn`, not `error`: it surfaces the pre-existing violations without
breaking CI, while making every *new* violation visible in the lint output. The current
count, split by layer and listed file-by-file, is the generated **[boundary ledger](../../generated/BOUNDARIES.md)**
(`npm run gen:boundaries`) — read the number there rather than from this page, which
cannot stay current. Fixing a flagged import is: move the dependency to a lower layer,
convert to `import type` if it's only a type, or route through the correct tier.
**The number is a ratchet.** `npm run check:boundaries` (run by the pre-push hook) compares
the live violations to the committed ledger *edge by edge* and fails the push on anything new
— a fix cannot pay for a new violation, so the total only moves down. Conceding one
deliberately is `npm run gen:boundaries` + commit: the raised budget then shows up in the diff
as a reviewable line rather than a silent warning.
## 3. Where does this file go? (decision table)
| I'm writing… | It goes in… |
|---|---|
| A deterministic rule / reducer action / pure derivation | `src/lib/core/<area>/` |
| A prompt, an AI/generation call, extraction, research logic | `src/lib/engine/<area>/` |
| Something needing Node / filesystem / credentials / the record / an op | `src/lib/server/<area>/` |
| A browser transport, the store, IndexedDB cache, a client context | `src/lib/client/<area>/` |
| A shared visual primitive with no domain knowledge (button, chip, chart atom, icon, modal) | `src/ui/` |
| The UI for one product capability (its views + hooks + commands) | `src/features/<capability>/` |
| App chrome that composes features (Stage frame, rail, topbar, layout, routing) | `src/shell/<area>/` |
| A Next.js route / page | `src/app/(instance\|gateway\|shared)/…` — compose only; no domain logic |
| A test | see §5 |
**Rule of thumb for UI:** if it knows about a *capability* (research, generation,
routes, a domain concept) → it's a **feature**. If it only *arranges* things →
**shell**. If it knows *nothing* and is reused everywhere → **ui**.
## 4. Header descriptions for the generated map
The tree generator (`scripts/gen/tree.mjs`) derives each file's description from
its leading comment and writes it to the generated [file map](../../generated/TREE.md).
```ts
// <FileName> — one-line description (already the convention).
```
Keep that first comment block to one scan-friendly sentence: it is the description that appears in
the generated file map. Put detailed rationale, invariants, and implementation notes after a blank line in a
second comment block. That keeps the generated map useful without discarding the explanation where
the implementation needs it.
Regenerate after structural changes: `npm run gen:tree`.
## 5. Tests — `src/__tests__/`
Subdivided by kind: top-level = subsystem/integration tests; `react/` = UI-hook +
provider behaviour; `fixtures/` + `helpers/` = shared test data and utilities.
Pure-unit tests may also be colocated next to the module (`foo.test.ts` beside
`foo.ts`). When adding a test, match the nearest existing pattern for its subsystem.
## 6. Current state
The retired `@/components/*` tier is fully dissolved into the three tiers, each deeply
sub-categorised so a file's path infers its role:
- ✅ `src/lib/{core,engine,server,client}` — the authority split, lint-enforced.
- ✅ `src/app/(instance|gateway|shared)` — route groups split.
- ✅ `src/ui/` — leaf primitives in `controls/ feedback/ display/ entity/ brand/
icons/ effects/`. Self-contained (zero upward imports), lint-enforced. See
[src/ui/README.md](../../../src/ui/README.md).
- ✅ `src/features/` — one capability per dir, each with intent subfolders, e.g.
`generation/{branch,coordination,thinking,panels,fields}`, `game/{scenario,play,
setup,table,panels,hooks}`, `research/{compose,run,query,schedule,operations}`,
`access/{auth,onboarding,profile,people,plans,buttons}`,
`gateway/{landing,console,admin,auth}`, `constellation/{views,frame,modals}`,
`onboarding/{steps,primitives,hooks}`, `timeline/{charts,eval}` (+ `index.ts`
barrel). Small single-capability dirs stay flat.
- ✅ `src/shell/` — the app frame: `stage/{frame,variables,views/{base,content,mind,
program,watch}}` (views grouped by nav section), `topbar/{bar,indicators,modals,
context}`, `inspector/{detail,panels,chat,kit}`,
`sidebar/{rail,panels,media,modals,surveys}`, plus `home/ mobile/ layout/`.
The per-dir truth is generated, not curated here: the [file map](../../generated/TREE.md) shows files and the
[boundary ledger](../../generated/BOUNDARIES.md) shows direction debt. This section states the *shape*; when
they disagree, they are right.
**Direction check:** `shell → features → ui` is the intended flow. `shell→features`
imports are correct (the shell composes features); `features→shell` imports are the
debt to watch — when a `features/` file reaches into a `shell/` view for something
reusable, that something is usually a primitive that should be promoted to `@/ui`
(as `RoomUI`→`@/ui/entity/PerspectivePrimitives` and the position sparklines were).
**The working rule for new/touched code:** put new UI in the right tier and
subfolder; if a `features/` file needs a `shell/` internal, promote the shared
piece to `@/ui` instead of reaching across.
Open on GitHubRaw Markdown source