|
| 1 | +# Basic Memory Engineering Style |
| 2 | + |
| 3 | +Style is how we make code easier to verify. Prefer explicit, typed, local-first code that |
| 4 | +preserves the file system as the source of truth while keeping the database, API, and MCP |
| 5 | +surfaces in sync. |
| 6 | + |
| 7 | +## Design Center |
| 8 | + |
| 9 | +- Basic Memory is local-first. Markdown files are the durable source; SQLite/Postgres indexes |
| 10 | + are derived state that should be rebuilt or reconciled from files when needed. |
| 11 | +- Keep the existing boundary order: CLI/MCP/API entrypoints compose dependencies, services own |
| 12 | + business behavior, repositories own database access, and file services own filesystem writes. |
| 13 | +- MCP tools should remain atomic and composable. They should call API routers through typed MCP |
| 14 | + clients, not reach around into services. |
| 15 | +- Prefer small, explicit abstractions that match a real domain boundary. Avoid object |
| 16 | + hierarchies when a function, dataclass, type alias, or protocol describes the concept better. |
| 17 | + |
| 18 | +## Types And Data |
| 19 | + |
| 20 | +- Use full type annotations and Python 3.12 syntax. Introduce `type` aliases for repeated |
| 21 | + structured shapes, callback signatures, or domain concepts that would otherwise become |
| 22 | + anonymous `dict[str, Any]` values. |
| 23 | +- Use dataclasses for internal values, operation inputs, and service results. Prefer |
| 24 | + `frozen=True` when the value should not change and `slots=True` when identity/dynamic |
| 25 | + attributes are not needed. |
| 26 | +- Use Pydantic v2 at boundaries that validate, serialize, or deserialize data: API payloads, |
| 27 | + CLI/MCP schemas, configuration, and persistence-adjacent schemas. |
| 28 | +- Use narrow `Protocol`s when a caller needs a capability rather than a concrete repository or |
| 29 | + service. Keep protocols small enough that fake implementations in tests are obvious. |
| 30 | +- Avoid speculative `getattr`, broad casts, or `Any` as a way to paper over uncertainty. Read |
| 31 | + the model or schema definition and make the type relationship explicit. |
| 32 | + |
| 33 | +## Control Flow And Resources |
| 34 | + |
| 35 | +- Fail fast when an invariant is broken. Do not swallow exceptions, add warning-only error |
| 36 | + handling, or introduce fallback behavior unless the user explicitly agrees to that behavior. |
| 37 | +- Keep control flow simple and close to the domain decision. Push `if` statements up into the |
| 38 | + function that owns orchestration; keep leaf helpers focused on computation or one side effect. |
| 39 | +- Make async/resource boundaries visible with context managers and explicit lifecycles. Do not |
| 40 | + start background work without a clear owner, cancellation story, and verification path. |
| 41 | +- Keep file mutations centralized through the existing file utilities/services so checksum, |
| 42 | + atomic write, and index synchronization behavior stays coherent. |
| 43 | + |
| 44 | +## Testing And Verification |
| 45 | + |
| 46 | +- Use evidence-first testing, not mechanical TDD. For bugs and risky behavior, add or update a |
| 47 | + regression test that would catch the failure. For small documentation-only edits, use the |
| 48 | + relevant doc/repo hygiene checks. |
| 49 | +- Prefer tests that exercise real code paths. Use mocks, doubles, or `monkeypatch` only when |
| 50 | + the external boundary would be slow, nondeterministic, or impossible to trigger directly. |
| 51 | +- Keep coverage at 100% for new code. Use `# pragma: no cover` only for code that would require |
| 52 | + disproportionate mocking and is covered through an integration or runtime path. |
| 53 | +- Start with targeted commands, then widen as risk grows: focused pytest, `just fast-check`, |
| 54 | + `just doctor`, package checks for agent packaging changes, and full SQLite/Postgres gates |
| 55 | + when behavior crosses shared boundaries. |
| 56 | + |
| 57 | +## Comments And Names |
| 58 | + |
| 59 | +- Name values after the domain concept they carry: project, entity, permalink, tenant, route, |
| 60 | + checksum, observation, relation, batch, or index state. |
| 61 | +- Comments should say why a branch, invariant, retry, lifecycle, or compatibility constraint |
| 62 | + exists. Section headers are useful when a function or file has clear phases. |
| 63 | +- Avoid comments that restate the code. If a comment cannot explain a decision, simplify the |
| 64 | + code or improve the name instead. |
0 commit comments