diff --git a/.gitignore b/.gitignore index 2a4583aa..ec97f2e5 100644 --- a/.gitignore +++ b/.gitignore @@ -47,8 +47,9 @@ openswarm-cloud # leading slash so this doesn't accidentally ignore backend/apps/analytics/. /analytics .claude/ -# CLAUDE.md docs (root + per-package) are personal AI working notes, not contributor docs. -CLAUDE.md +# Personal/per-machine AI working notes. Shared, enforced conventions live in committed +# CLAUDE.md files (root + per-package); personal notes go in CLAUDE.local.md instead. +CLAUDE.local.md # Local-only operator helpers (never commit) scripts/set-fly-*.sh # Local-only background dev-team state/docs (personal, never commit) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..b46bfbdb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,47 @@ +# OpenSwarm — code conventions + +These conventions are **mandatory** and apply to the **whole codebase**. This file is loaded into +every agent session (and, hierarchically, the nearest `CLAUDE.md` for the files you touch), so the +rules are present wherever you work. They are enforced where possible by the linter +(`linter/lint.py`; see `linter/README.md` for the full rationale and how to run it); the rest are +followed by hand. Per-package `CLAUDE.md` files (`backend/`, `frontend/`) restate the +language-specific subset. + +Personal / per-machine notes go in `CLAUDE.local.md` (gitignored), **not** here. + +## Naming & access +- **Never start any name with `_`** (functions, variables, arguments, attributes, import aliases). + A leading underscore makes dead-code tooling (Pylance, ruff, vulture) treat the name as + intentionally unused and stop reporting it — a blind spot. Use `p_` for "private" instead. + Dunders (`__init__`) and the bare `_` throwaway are the only exceptions. + *(Enforced backend: `no-underscore-names`.)* +- **`p_` is an access boundary, not decoration.** A `p_` name is private to its file (module level) + or class (attribute). If it is read or imported anywhere else, it is not private — drop the prefix + and make it public. *(Enforced backend: `p-private`.)* + +## Structure +- **No barrels.** Never write an `__init__.py` / `index.ts` whose only job is to re-export. Import + from the defining module directly. +- **No relative imports (Python).** Always import from the package root + (`from backend.apps.foo import bar`), never `from .foo import bar`. +- **Single-purpose file naming.** A file that exports exactly one function or class is named exactly + after it. +- **File and folder size caps** (`max-file-lines`, `max-folder-items`) — split when you exceed them. +- **No runtime import cycles** (`import-cycles`). + +## Types +- **Type everything** — every function, argument, and variable is annotated. +- **Backend Python:** decorate with typeguard's `@typechecked`; prefer `typing` generics + (`List`, `Dict`, `Optional`) over the builtins; code also type-checks under Pyright/Pylance strict. +- **Frontend:** strict `tsconfig`; model values with typed interfaces, never untyped objects. + +## Classes & data (Python) +- **Classes are pydantic `BaseModel`** with `model_config = ConfigDict(validate_assignment=True)`; + wrap unrecognized field types in `InstanceOf[...]`. +- **Avoid bare `dict`s for structured data** — model it as a `BaseModel`. The only legitimate dicts + are dynamic-key maps (a registry keyed by a runtime id) and external protocol shapes (the Claude + Agent SDK hook returns, `model_dump` output). + +When you add or change code, the files you touch must be clean under these rules. Pre-existing debt +in files you are not otherwise editing is grandfathered via the linter's exception lists — do not +mass-migrate untouched files. diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 00000000..04d81f60 --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,18 @@ +# backend — conventions (Python) + +All conventions in the root `CLAUDE.md` apply here. Python-specific emphasis: + +- **No leading `_`; use `p_` for private** (`no-underscore-names`), and a `p_` name used outside its + file/class must be made **public** (`p-private`). Both are linter-enforced for `backend/`. +- **Absolute imports only** — `from backend.apps.foo import bar`, never `from .foo import bar`. +- **No barrels** — `__init__.py` must not exist solely to re-export. +- **`@typechecked` on every function**, and prefer `typing` generics (`List`, `Dict`, `Optional`) + over builtins. Pyright/Pylance strict. +- **Classes are pydantic `BaseModel`** (`model_config = ConfigDict(validate_assignment=True)`, + `InstanceOf[...]` for unrecognized field types). Don't use plain classes or bare `dict`s for + structured data — model it. Legitimate dicts: dynamic-key registries and external protocol shapes + (SDK hook returns, `model_dump` output). +- **Single-purpose file naming** — a one-export file is named after its export. + +Touch a file → it must be clean under these rules. Pre-existing debt is grandfathered in +`linter/config/config.json`; don't mass-migrate untouched files. diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md new file mode 100644 index 00000000..83ea5edc --- /dev/null +++ b/frontend/CLAUDE.md @@ -0,0 +1,15 @@ +# frontend — conventions (TypeScript) + +All conventions in the root `CLAUDE.md` apply here. The naming and structure rules carry over; the +Python-only rules do not. Specifically: + +- **No leading `_`** on any name, and **no barrels** (`index.ts` that only re-exports). +- **Single-purpose file naming** — a one-export file is named after its export. +- **Type everything** under strict `tsconfig`; model data with typed interfaces, not untyped + objects (the TS equivalent of "no bare dicts"). +- **Does not apply (Python-only):** `@typechecked`, pydantic `BaseModel`, the no-relative-imports + rule (the frontend uses the `@/` path alias and local relative imports), and `p-private`. + +Known gaps to fix over time, not perpetuate: `no-underscore-names` / `p-private` are currently +backend-only, and `eslint.config.mjs` still suggests a `_` prefix to silence unused-variable +warnings — remove the unused binding instead of prefixing it.