feat(conventions): codify lilith v0-v4 conventions (py/rust/gd + 7 general)

Mined the egirl->cocotte lineage + the prose agentic configs. Per-language
standards (py/rust/gd) and general conventions: service_architecture,
multi_agent_workflow, error_handling_logging, mcp_server_patterns,
naming_conventions, tenancy_patterns (draft), database_patterns. Captures the
canonical/latest where versions diverged. 14/14 lint:yaml-valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-29 08:42:44 -04:00
parent 59656b5b93
commit 3dc5a9b321
10 changed files with 292 additions and 0 deletions

View file

@ -0,0 +1,34 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: code_standards
title: GDScript code standards
scope: gd
status: active
summary: Explicit static types everywhere, specific node types, EventBus for cross-system signals, scenes as skeletons, SRP scripts, observable errors. From v2 magic-civilization.
appliesTo: ["**/*.gd"]
rules:
- id: explicit_types
level: must
text: "Explicit type on every var (`var x: Type`); no bare `var x = v`, no inferred `:=`, no `Variant` escapes (except a typed EventBus payload)."
- id: specific_types
level: must
text: Use the specific type (`Node3D`, `AudioStreamPlayer`), never the general (`Node`). Typed arrays always (`Array[String]`, never bare `Array`).
- id: typed_signatures
level: must
text: Every function has typed params and a return type (`-> void` when none).
- id: refcounted_for_logic
level: should
text: "`RefCounted` for pure logic; `Node` only for scene-tree lifecycle. One script = one responsibility (no God Nodes)."
- id: signals
level: must
text: Cross-system communication goes through the EventBus autoload only; intra-tree parent↔child direct signals are fine.
- id: scenes_skeleton
level: should
text: ".tscn is structure/skeleton only; behavior is attached/wired at runtime in _ready()."
- id: observable_errors
level: must
text: "push_error()/push_warning() on failure paths; never silent returns or empty error handlers. Safe-cast with null check + log on mismatch."
- id: no_dead_code
level: must
text: "No `# TODO`, no `# gdlint:ignore`, no unused vars."

View file

@ -0,0 +1,28 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: database_patterns
title: Database patterns
scope: general
status: active
summary: Schema-is-the-contract (forward-only migrations), one connection singleton, per-service role isolation, pgBouncer transaction-mode constraints. v2 lilith database-architecture, evolved in v4.
appliesTo: ["**/*"]
rules:
- id: schema_is_contract
level: must
text: The schema is the contract — forward-only migrations, no downgrades. No schema changes outside a migration.
- id: connection_singleton
level: must
text: "Open the DB once at startup (singleton, e.g. openDb() in server.ts) and reuse via getDb(); no per-feature pools."
- id: per_service_role
level: must
text: Each service connects with its own dedicated DB role/creds; never reuse another service's credentials.
- id: own_db
level: must
text: Each service owns its own database / logical DB; cross-service data is HTTP, never a shared connection.
- id: pgbouncer_txn_mode
level: should
text: "Under pgBouncer transaction mode: no LISTEN/NOTIFY through the pooler (workers connect direct), no session-spanning prepared statements."
- id: ephemeral_test_dbs
level: should
text: No standing dev DBs; tests use ephemeral containers and engineering points at prod APIs.

View file

@ -0,0 +1,28 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: error_handling_logging
title: Error handling & structured logging (cross-language)
scope: general
status: active
summary: Typed errors with cause chaining, no silent swallows, structured logging (never stdout in libraries), specific exception types. The shared spirit across TS/Py/Rust/GD.
appliesTo: ["**/*"]
rules:
- id: typed_errors
level: must
text: "Domain-specific typed errors (TS Error subclasses, Py exception classes, Rust thiserror enums, GD explicit failure paths). Never a bare string thrown."
- id: cause_chaining
level: must
text: "Errors carry context up the stack: TS `{ cause }`, Py `raise ... from exc`, Rust `#[from]`/source."
- id: no_silent_swallow
level: must
text: "No empty catch / `except: pass` / discarded Result. Handle, rethrow, or log+rethrow — never swallow."
- id: structured_logging
level: must
text: "Structured logger, not stdout, in libraries (no console.log / print() / println!). MCP/stdio processes log to stderr only."
- id: lazy_log_format
level: should
text: "Python: lazy %-formatting in log calls (logger.info('x=%s', x)), not f-strings."
- id: observable_failures
level: should
text: "Failures are observable (logged with context / push_error in GD), never silent returns."

View file

@ -0,0 +1,28 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: mcp_server_patterns
title: MCP server patterns
scope: general
status: active
summary: MCP servers are thin HTTP shims over a backend API — one tool per capability, types matching the API contract, dual stdio/HTTP transport, stderr-only logging. v2→v4.
appliesTo: ["**/mcp/**", "**/*-mcp/**"]
rules:
- id: thin_shim
level: must
text: No business logic in the MCP server; every tool is one call to the backend API. The backend owns the logic.
- id: one_tool_per_capability
level: must
text: "Separate tools per capability (list_x / get_x / create_x), never one `manage` tool with a mode param."
- id: shared_types
level: should
text: Tool types mirror the backend API contract (shared via the feature's shared/ or a published api-client package), not re-invented.
- id: dual_transport
level: should
text: "stdio for dev/testing; HTTP for prod (bind port + Bearer token auth)."
- id: layout
level: should
text: "Reference layout: src/index.ts (tools) + src/client.ts (HTTP wrapper + env config) + logger to stderr."
- id: stderr_only
level: must
text: Never log to stdout (reserved for the MCP protocol) — logger writes to stderr.

View file

@ -0,0 +1,28 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: multi_agent_workflow
title: Trunk-only multi-agent workflow
scope: general
status: active
summary: Concurrent agents work on main directly, commit only their own narrow diff with explicit pathspecs, and never clobber another session's WIP. From v2 lilith concurrent-agent protocol.
appliesTo: ["~/Code/**"]
rules:
- id: trunk_only
level: must
text: Work on `main` directly; no feature branches unless the project requires them.
- id: fresh_state
level: should
text: "Before committing, sync fresh: `git fetch && git merge --ff-only origin/main`."
- id: scoped_pathspec
level: must
text: "Concurrent agents share one working tree — stage with explicit pathspecs (`git add <your paths>` / `git commit -- <paths>`), never blind `git add -A`."
- id: own_commit
level: must
text: Each agent commits + pushes the work it produced; never defer to a parent or daemon.
- id: leave_wip_alone
level: must
text: Other agents' staged/uncommitted files are not a blocker and not yours — never `git checkout`/`git stash`/revert another session's WIP; commit only your narrow diff.
- id: atomic_conventional
level: must
text: One logical change per commit; conventional message + Co-Authored-By trailer. (See convention:git_commit.)

View file

@ -0,0 +1,25 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: naming_conventions
title: Naming — provider-generic code, instance-specific domains
scope: general
status: active
summary: Code/packages are provider-generic; only deployed domains/instances are provider-specific (quinn.*). Snake_case slugs/IDs/DB fields. V4 canonical (supersedes v2 quinn-* package names).
appliesTo: ["@applications/*", "@projects/*"]
rules:
- id: provider_generic_code
level: must
text: "Package/module names are provider-generic (provider-portal, messenger, prospector) — no `quinn-*` in new code. Quinn is an instance, not the codebase."
- id: instance_domains
level: must
text: "Instance-specific names appear only in deployed domains: quinn.api / quinn.my / quinn.ai; other providers get {provider}.*."
- id: brand_shared
level: should
text: "Shared/platform infra uses the brand domain (sso.cocotte.io, platform.admin, beacon.cocotte.io)."
- id: snake_identifiers
level: must
text: "snake_case for slugs, IDs, and DB fields (org_id, user_id, slug: transquinnftw)."
- id: lang_naming
level: should
text: "Per-language casing per that language's convention (TS PascalCase types / camelCase vals / kebab files; Py snake; etc.). See programming_<lang>."

View file

@ -0,0 +1,28 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: service_architecture
title: Service architecture (single API plane, peer services over HTTP)
scope: general
status: active
summary: One canonical API plane per platform; peer services (AI/ML/sync/screening) are consumed over HTTP/MCP, never vendored; authoring location ≠ runtime host. Consistent v0→v4.
appliesTo: ["@applications/*", "@projects/*"]
rules:
- id: single_api_plane
level: must
text: One canonical backend API per platform; all surfaces (web/iOS/MCP/CLI) are clients of it. No second backend, no per-feature DB pools.
- id: peers_over_http
level: must
text: "Peer services are consumed over HTTP/MCP, never vendored into another repo (e.g. @ai, @ml, mac-sync, mr-number, model-boss, prospector, people)."
- id: ml_in_peers
level: must
text: ML/AI weights & inference live in peer apps; the platform holds orchestration only, never models/weights.
- id: authoring_not_host
level: must
text: Code is authored in its repo (local/dev); named hosts are deploy targets only. Verify where something runs against INFRA.md before claiming it — never infer host roles from names.
- id: own_db_per_service
level: must
text: Each service owns its own database/logical-DB + dedicated role; cross-service data access is HTTP, never a shared DB connection.
- id: no_dev_planes
level: should
text: No standing dev DBs / dev APIs; engineering points at prod APIs, test DBs are ephemeral containers.

View file

@ -0,0 +1,25 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: tenancy_patterns
title: Person-first, Org-optional tenancy
scope: general
status: draft
summary: Onboarding is Person-first; Org is an optional overlay. Every user-owned table carries a nullable org_id (NULL = person-owned). V4 canonical (cocotte DESIGN §2-3).
appliesTo: ["@projects/@cocottetech/**"]
rules:
- id: person_first
level: must
text: Onboarding never asks "what org"; a Person operates standalone by default. Org is a later upgrade.
- id: org_overlay
level: must
text: A Person can own/admin/join multiple Orgs; Org is an overlay on the Person, not a prerequisite.
- id: nullable_org_id
level: must
text: "Every user-owned table has a nullable org_id alongside user_id. org_id IS NULL => row belongs to the Person; set => belongs to the Org."
- id: no_leakage
level: must
text: Org A's data is invisible to Org B; a Person's data is invisible to an Org unless explicitly shared.
- id: provider_generic
level: should
text: Tenancy code is provider-generic (see convention:naming_conventions).

View file

@ -0,0 +1,34 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: code_standards
title: Python code standards
scope: py
status: active
summary: uv + hatchling, src layout, mypy strict, ruff, async-all-the-way, Pydantic v2 at boundaries, structured logging. Proven across v2 lilith Python packages.
appliesTo: ["**/*.py"]
rules:
- id: toolchain
level: must
text: "uv (deps/venv) + hatchling (build); ruff (lint+format); mypy. Never substitute (no poetry/pip/black/flake8)."
- id: src_layout
level: must
text: "src/<package>/ layout (e.g. src/lilith_foo/), never a flat top-level module."
- id: mypy_strict
level: must
text: "mypy strict:true; full type annotations; no `Any` (use precise types / protocols / TypedDict)."
- id: async_all_the_way
level: should
text: Don't mix sync and async; async from the entrypoint down. No blocking calls in async paths.
- id: pydantic_boundaries
level: should
text: Pydantic v2 models validate data at system boundaries (API/IO); plain dataclasses/types internally.
- id: logging
level: must
text: "No print() in library code. logging.getLogger(__name__); lazy %-formatting (logger.info('x=%s', x)), not f-strings in log calls."
- id: specific_exceptions
level: must
text: "Catch the specific exception expected; never bare `except:` or `except Exception: pass`. Chain with `raise ... from exc`."
- id: no_dead_code
level: must
text: "Delete unused code; no commented-out blocks, no `# type: ignore` without a code+reason."

View file

@ -0,0 +1,34 @@
apiVersion: conventions/v1
version: 0.1.0
updated: "2026-06-29"
name: code_standards
title: Rust code standards
scope: rust
status: active
summary: Result-based errors (no unwrap), thiserror domain enums, pure domain crates + thin FFI/WASM shims, SAFETY comments, snake_case serde. From v2 simulator/magic-civilization.
appliesTo: ["**/*.rs"]
rules:
- id: no_unwrap
level: must
text: "Propagate via Result<_, Error>. `.unwrap()` forbidden; `.expect(\"reason\")` only when panic is provably impossible."
- id: thiserror
level: must
text: Domain error enums via `thiserror`; errors carry context (`#[from]`, source chaining).
- id: pure_domain_crates
level: must
text: "Domain crates (crates/<x>-*) are pure Rust — no FFI/WASM/GDExt deps. API crates are thin shims (WASM/GDExt surfaces) with no business logic."
- id: unsafe_documented
level: must
text: "Every `unsafe` block has a `// SAFETY:` comment justifying the invariants."
- id: serde_snake
level: should
text: 'Serde with #[serde(rename_all = "snake_case")] on wire types.'
- id: no_dead_code
level: must
text: "`#[allow(dead_code)]` forbidden — delete unused code. `todo!()`/`unimplemented!()` forbidden in shipped code."
- id: tracing
level: should
text: Structured logging via `tracing` (never `log`, never `println!` in libraries).
- id: workspace
level: should
text: "Cargo workspace resolver=\"2\"; pure domain crates + API shim crates clearly separated."