This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
a3s/ ← MONOREPO ROOT (NOT a Rust workspace)
├── apps/ # Applications (desktop, web, CLI)
│ ├── os/ # A3S platform (NestJS + React)
│ └── safeclaw/ # SafeClaw desktop app (Tauri + React)
├── crates/ # Rust crates (submodules)
│ ├── box/ code/ event/ gateway/ lane/ power/ search/ updater/
│ └── common/ # Shared types
├── docs/ # Documentation
└── homebrew-tap/ # Homebrew tap
- No
Cargo.toml,src/,justfilein root — root is NOT a Rust workspace - No
cargo initorcargo newin root - Git remote MUST point to
git@github.com:A3S-Lab/a3s.git
git stash pop CAN destroy your changes after failed merge/rebase (autostash issue).
- Before
git pull --rebase: commit or stash first - After
git stash pop: verify changes withgit diff --stat HEAD - If stash pop fails: use
git stash branch recoveryinstead - Always use
git stash push -m "message"with descriptive messages
- Create repo on GitHub (PascalCase, under A3S-Lab org)
- Init in
/tmp, push, thengit submodule addintocrates/<name> - Commit
.gitmoduleschange in a3s root - Update README.md (Modules table, Roadmap, Repository Structure)
- Never
cargo initdirectly in a3s root
| Item | Convention |
|---|---|
| GitHub repo | PascalCase (MyNewCrate) |
| Submodule path | kebab-case (crates/my-new-crate) |
| Rust crate name | a3s-my-new-crate |
Rust: Follow Microsoft Rust Guidelines. cargo fmt before every commit, cargo clippy for linting (enforced in CI).
Key rules:
- Async-first: all I/O uses Tokio, no blocking in async context
- Error handling:
BoxErrorincore/src/error.rs, always include context in errors - Event keys: dot-separated lowercase (
<domain>.<subject>.<action>) - Public types must be
Send + Sync, no panics in production
Python SDK: Async/await, async with for cleanup, type hints.
Config: Prefer HCL over TOML. Use .hcl extension by default.
Module organization: Each business module in its own folder
DDD layering:
domain/: Entities, value objects, aggregate roots, domain events (pure TypeScript)application/: Application services, command handlers, query handlersinfrastructure/: Database, Redis, external service implementationspresentation/: DTOs, controllers, guards
Dependency injection: Use constructor injection, avoid new in classes
Configuration: Use @nestjs/config with validated environment variables
Every module MUST follow the 4-layer DDD structure. This is mandatory - violations are considered critical issues.
modules/{module-name}/
├── domain/ # Pure TypeScript - no framework imports
│ ├── entities/ # Aggregate roots, entities
│ │ └── *.entity.ts
│ ├── value-objects/ # Value objects
│ │ └── *.vo.ts
│ ├── repositories/ # Repository interfaces ONLY
│ │ └── *.repository.interface.ts
│ ├── services/ # Service interfaces ONLY (no implementations!)
│ │ └── *.service.interface.ts
│ └── events/ # Domain events
│ └── *.event.ts
├── application/ # CQRS handlers
│ ├── commands/ # Command handlers
│ │ └── {feature}/
│ │ ├── {feature}.command.ts
│ │ ├── {feature}.handler.ts
│ │ └── index.ts
│ └── queries/ # Query handlers
│ └── {feature}/
│ ├── {feature}.query.ts
│ ├── {feature}.handler.ts
│ └── index.ts
├── infrastructure/ # Implementations
│ └── persistence/ # Repository implementations
│ └── *.repository.ts
├── presentation/ # HTTP layer
│ ├── controllers/
│ │ └── *.controller.ts
│ └── dto/
│ ├── request/ # Request DTOs only
│ │ └── *.request.dto.ts
│ └── response/ # Response DTOs only
│ └── *.response.dto.ts
└── {module}.module.ts
1. Domain Layer is PURE TypeScript
- NO NestJS imports (
@nestjs/common,@nestjs/cqrs, etc.) - NO framework dependencies
- Only interfaces, types, and domain logic
2. Domain Services Directory
- ONLY contain
.interface.tsfiles with interface definitions - NEVER create re-export files like
*.service.tsthat re-export interfaces - Implementation goes to
infrastructure/persistence/
3. Repository Interfaces
- Define in
domain/repositories/withI{Entity}Repositorynaming - Symbol token:
{ENTITY}_REPOSITORY - Implementation in
infrastructure/persistence/
4. Service Interfaces
- Define in
domain/services/withI{Service}Servicenaming - Symbol token:
{SERVICE}_SERVICE - Implementation in
infrastructure/
5. CQRS Handlers
- Use subdirectory pattern:
commands/{feature}/handler.ts - NOT flat structure like
commands/handler.tsin root - Register handlers in module:
CommandHandlers = [...],QueryHandlers = [...]
6. DTO Organization
- Request DTOs:
presentation/dto/request/ - Response DTOs:
presentation/dto/response/ - Application layer should NOT have DTOs (they belong to presentation)
7. Controller Rules
- Thin controllers only - delegate to CommandBus/QueryBus
- No business logic in controllers
- Use proper typing - no
any
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
Re-export files in domain/services/ |
Violates "interfaces only" rule | Delete .service.ts re-exports, keep only .interface.ts |
Handlers in flat commands/ |
Inconsistent organization | Use subdirectory: commands/{feature}/ |
| Business logic in controller | Violates separation of concerns | Move to command/query handlers |
Missing infrastructure/ layer |
Repository implementations have no home | Create infrastructure/persistence/ |
| DTOs in application layer | DTOs are HTTP concerns | Move to presentation/dto/ |
any type in handlers |
Defeats type safety | Use proper entity types |
All API responses use a consistent wrapper format. The response format is enforced by ApiResponseInterceptor.
{
"code": 200,
"message": "Success",
"data": { ... },
"requestId": "uuid",
"timestamp": "2026-04-17T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
code |
number |
HTTP status code (e.g., 200, 201, 204) |
message |
string |
Human-readable response message |
data |
object | array |
Response payload |
requestId |
string |
Request tracing ID (optional) |
timestamp |
string |
ISO 8601 timestamp |
{
"code": 200,
"message": "Success",
"data": {
"items": [...],
"total": 100,
"page": 1,
"limit": 10,
"totalPages": 10,
"hasNext": true,
"hasPrevious": false
},
"requestId": "uuid",
"timestamp": "2026-04-17T00:00:00.000Z"
}{
"code": 404,
"statusCode": "NOT_FOUND",
"message": "Resource not found",
"details": { ... },
"requestId": "uuid",
"timestamp": "2026-04-17T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
code |
number |
HTTP status code |
statusCode |
ErrorCode |
Business error code (string enum) |
message |
string |
Human-readable error message |
details |
object |
Additional error context (optional) |
requestId |
string |
Request tracing ID (optional) |
timestamp |
string |
ISO 8601 timestamp |
The ErrorCode enum defines all business error codes:
enum ErrorCode {
// 4xx Client Errors
BAD_REQUEST = 'BAD_REQUEST',
UNAUTHORIZED = 'UNAUTHORIZED',
FORBIDDEN = 'FORBIDDEN',
NOT_FOUND = 'NOT_FOUND',
CONFLICT = 'CONFLICT',
// 5xx Server Errors
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
// Business Errors (10xxx)
VALIDATION_ERROR = '10001',
DUPLICATE_ENTRY = '10002',
RESOURCE_NOT_FOUND = '10003',
// Auth Errors (20xxx)
TOKEN_EXPIRED = '20001',
TOKEN_INVALID = '20002',
PERMISSION_DENIED = '20004',
// Domain Errors (30xxx)
ENTITY_NOT_FOUND = '30001',
ENTITY_ALREADY_EXISTS = '30002',
// External Service Errors (40xxx)
EXTERNAL_SERVICE_ERROR = '40001',
EXTERNAL_SERVICE_TIMEOUT = '40002',
}codeis the HTTP status code - Set by the HTTP layerstatusCodeis the business error code - Set by application/domain layer- Always use
ApiResponseInterceptor- Ensures consistent wrapper format - Use OpenAPI decorators - Document all error codes in
@ApiResponseschema
- Use Kysely for type-safe SQL queries
- Always use parameterized queries to prevent SQL injection
- Migration files go in
migrations/directory
- Knowledge bases use pgvector for semantic search
- Vector fields use
vector(1536)or appropriate dimensions
| Object | Convention | Example |
|---|---|---|
| Database table | snake_case | user_accounts |
| Column | snake_case | created_at |
| Class name | PascalCase | UserAccount |
| Method/variable | camelCase | findById |
| Constant | UPPER_SNAKE_CASE | MAX_RETRY_COUNT |
All code and documentation MUST be in English. This includes:
- Rust doc comments (
//!,///,//) - Python docstrings
- README.md, docs/*.md, CLAUDE.md
Update docs EVERY time a feature is completed. Code is not done until docs reflect it.
For every feature:
- Update README.md Features section
- Update README.md Roadmap (mark ✅, add implementation notes)
- Update related
docs/*.mdfiles - Remove obsolete content (outdated docs, deprecated examples, completed TODOs)
- Verify code examples still work
Challenge assumptions. Before implementing: "Does this already exist?", "Does this layer need to know this?". After writing: "What breaks if I delete this?".
Every new feature request requires first-principles review BEFORE code:
- What is the project's core mission?
- Does this feature directly serve that mission? If not, refuse.
- Does it strengthen or weaken the architecture?
- Is the problem real or hypothetical?
- Is there a simpler alternative?
If it fails this review, refuse clearly with architectural justification. Do not soften with "we could do it later".
Every module system:
- Core (5-10 components): Minimal, stable, non-replaceable
- Extensions: Everything else, trait-based with default implementations
Before adding code, ask: "Is this core or extension?" If it can be replaced without breaking core, it's an extension.
- Single Responsibility — one function, one job
- Boring Code — obvious > clever
- Search Before Implement — grep before writing
- Only What's Used — no future-proofing
- DRY — single source of truth
- Explicit Errors — self-documenting error messages
- Periodic Pruning — after every feature, audit for dead wrappers/orphaned exports
Keep files focused, directories logically organized. Fight the "god object" and "dump everything in one folder" tendencies.
File splitting guidelines:
- File size: If a file exceeds ~500 lines, consider splitting it. If it exceeds ~1000 lines, it MUST be split.
- Single responsibility per file: One struct/trait + its closely related helpers. Don't mix unrelated concerns.
- Protocol handlers: Each protocol (HTTP, WebSocket, gRPC, TCP, UDP) in its own file, not one massive
entrypoint.rs. - Middleware: Each middleware type in its own file, not one
middleware/mod.rswith 15 match arms. - Managers: Extract lifecycle managers (discovery, autoscaler, ACME, providers) into separate files, not all in
gateway.rs.
Directory structure guidelines:
- Group by concern, not by type:
proxy/http.rs,proxy/websocket.rsnothttp_proxy.rs,ws_proxy.rsscattered - No "catch-all" directories:
utils/,helpers/with unrelated code are smells - Nested depth: Maximum 3 levels deep for source code (
src/a/b/c.rs), 2 for tests
Warning signs (refactor immediately):
mod.rsfiles that re-export 20+ itemssrc/gateway.rswith 1000+ linessrc/entrypoint.rshandling HTTP, TCP, UDP, TLS, ACME all in one file- Any file you need to scroll past 3 screens to understand
When in doubt: err on the side of more files with clearer names over fewer files with clever organization.
Extension/backend choices in SDK options MUST use typed objects, not raw primitives:
// ❌ BAD: leaks backend name, can't be swapped
agent.session('.', { memoryDir: './memory', defaultSecurity: true });
// ✅ GOOD: typed, explicit, swappable
agent.session('.', { memoryStore: new FileMemoryStore('./memory') });
agent.session('.', { securityProvider: new DefaultSecurityProvider() });This applies to: memory stores, session stores, security providers, context providers.
This does NOT apply to: feature flags (builtinSkills: boolean), numeric/string scalars (model: string, toolTimeoutMs: number).
- Searched for existing functionality before implementing
- Read all affected files before modifying
- Applied Rule 0-2 to own design (not just user's request)
- Single responsibility, boring code, no future-proofing
- Pruning audit: no dead wrappers, orphaned exports, redundant modules
- Typed extension options use objects (SDK only)
-
cargo fmt --allandcargo clippypass
- Domain layer has NO framework imports (pure TypeScript)
-
domain/services/contains only.interface.tsfiles (no re-exports) - Repository implementations are in
infrastructure/persistence/ - Commands/queries use subdirectory pattern (not flat structure)
- DTOs are in
presentation/dto/(notapplication/) - Controller delegates to CommandBus/QueryBus (no business logic)
- No
anytypes - use proper entity interfaces
- Write tests FIRST → 2. Run (should fail) → 3. Implement → 4. Run (should pass) → 5. Feature complete
Rules:
- Tests define completion — feature is done only when tests pass
- Modified code must have updated tests
- Deleted features = deleted tests (no orphaned
#[ignore]tests) - Integration tests required for CLI/network/cross-module workflows
- Tests must NOT leave temp files/sockets behind
cargo build --all-featuresmust succeed
Run tests: just test (all) or cargo test -p <crate> (specific)