26 KiB
Lilith Platform Development Methodology
Last Updated: 2026-01-25 Status: Living Document
Purpose
This document defines the development methodologies, patterns, and workflows for building and maintaining the Lilith Platform codebase. These methodologies ensure consistency, quality, and maintainability across all platform features.
Table of Contents
- Core Principles
- Workspace Architecture
- Service Development Workflow
- Package Development & Publishing
- Architecture Decision Making
- Code Quality Standards
- Testing Methodology
- Feature Development Process
- Deployment Procedures
- Cross-Feature Communication
Core Principles
1. Complete Code, No Cruft
Primary Directive: Every piece of code must be production-ready on first pass.
Requirements:
- Expert-quality implementation
- Full validation and comprehensive error handling
- Strong typing (no
anytypes) - Proper interfaces and type definitions
- Complete functionality (no stubs, pseudocode, or placeholders)
Mocks are ONLY permitted in test files (.spec.ts, .test.ts).
When Blocked: STOP → REPORT → WAIT Never silently degrade quality or create "simplified versions."
2. Zero Technical Debt
Treat every project as NEW - no legacy baggage:
- Delete unused code completely (no
_unusedvariables, no// removedcomments) - No backwards compatibility hacks or shims
- No re-exports for "migration" purposes
- If something is unused, REMOVE IT ENTIRELY
- No "temporary" workarounds - fix root causes or fail explicitly
3. Architectural Integrity
SOLID Principles:
- Single Responsibility Principle
- Open/Closed Principle (when beneficial)
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
DRY Principle: Extract patterns, avoid duplication
Before Creating New Code:
- Check if utilities/components already exist
- Search
~/Code/@packages/MANIFEST.mdfor existing packages - Review
@lilith/ui-*packages for frontend components - Search codebase for similar implementations
4. Self-Verification
NEVER declare completion until self-verification is complete:
- Image creation → Review images for quality
- UI changes → Use Playwright to verify visually
- Code changes → Run tests and builds
- API changes → Test endpoints
- Service changes → Verify health checks pass
Process: STOP → VERIFY → THEN REPORT
Workspace Architecture
Directory Structure
lilith-platform/
├── codebase/
│ ├── features/ # Feature-sliced modules
│ │ ├── sso/ # Single Sign-On feature
│ │ │ ├── backend-api/ # NestJS API service
│ │ │ ├── frontend/ # React frontend
│ │ │ └── services.yaml # Service definitions
│ │ ├── webmap/ # Multi-tenant routing
│ │ └── marketplace/ # Creator marketplace
│ └── @packages/ # Project-local shared code
├── infrastructure/
│ ├── nginx/ # Reverse proxy configs
│ ├── shared-services/ # Shared service manifests
│ └── tooling/ # Development tools
├── docs/ # Documentation
│ ├── development/ # Development guides
│ ├── architecture/ # Architecture decisions
│ └── deployment/ # Deployment procedures
└── vault/ # Secrets (gitignored)
Package Location Rules
| Directory | Role | Usage |
|---|---|---|
~/Code/@packages/ |
Importable libraries | import statements |
~/Code/@applications/ |
Deployable services | HTTP API calls |
~/Code/@projects/ |
End products | Deploy |
Critical Rules:
- NEVER create packages inside
@projects- use~/Code/@packages/ - NEVER use
link:orfile:inpackage.json- use registry versions - NEVER edit
node_modules/*- edit source at~/Code/@packages/, publish, update
Import Aliases
@lilith/*→ Published packages from registry@platform/*→ Feature shared modules (cross-feature imports)@/*→ Intra-feature imports (within same feature)
Service Development Workflow
1. Service Structure
Every service follows this structure:
features/<feature-name>/
├── backend-api/ # NestJS API (if needed)
│ ├── src/
│ │ ├── features/ # Feature modules
│ │ ├── common/ # Shared utilities
│ │ └── main.ts # Bootstrap entry
│ ├── package.json
│ ├── nest-cli.json
│ └── tsconfig.json
└── frontend/ # React app (if needed)
├── src/
│ ├── components/
│ ├── pages/
│ └── main.tsx
├── package.json
└── vite.config.ts
2. Deployment Configuration
Deployments are configured in deployments/@domains/{id}/services.yaml. Each deployment defines its services and orchestration in a unified manifest:
deployment:
id: trustedmeet.www
name: TrustedMeet Marketplace
domain: trustedmeet.com
description: TrustedMeet adult marketplace
orchestration:
dependencies:
- sso # Start SSO before this
entryPoints:
- trustedmeet.www.frontend
docker:
profiles: [core, platform, feature-dbs]
runSeeds: true
lifecycle:
keepAlive: true
autostart: true
urls:
- url: http://www.trustedmeet.local
description: TrustedMeet Marketplace
services:
- id: frontend
type: frontend
port: 5201
entrypoint: deployments/@domains/trustedmeet.www/root
description: TrustedMeet frontend
healthCheck:
type: http
path: /
dependencies:
- marketplace.api
deployments:
dev:
host: apricot
staging:
host: black
production:
host: vps-0
Shared services (SSO, merchant, etc.) live in infrastructure/shared-services/*.yaml.
3. Service Types
API Services (NestJS)
Required Configuration:
package.jsonmust include:"type": "module"for ESM support"main": "./dist/main.js"- Scripts:
build,start,start:dev,start:prod
nest-cli.jsonmust use SWC builder for ESM compatibility:{ "compilerOptions": { "builder": "swc", "deleteOutDir": true } }
Bootstrap:
import { bootstrap } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';
bootstrap(AppModule, {
serviceName: 'feature-name',
port: 4001,
});
Health Checks:
All services must implement /health endpoint using @lilith/nestjs-health:
import { HealthCheckService } from '@lilith/nestjs-health';
@Controller()
export class HealthController {
constructor(private health: HealthCheckService) {}
@Get('/health')
check() {
return this.health.check([
() => this.health.pingCheck('postgres', postgresConfig),
() => this.health.pingCheck('redis', redisConfig),
]);
}
}
Frontend Services (React + Vite)
Required Configuration:
package.jsonscripts:dev,build,previewvite.config.tswith proper port configuration- Use
@lilith/service-react-bootstrapfor initialization
Bootstrap:
import { createApp } from '@lilith/service-react-bootstrap';
import App from './App';
createApp(App, {
serviceName: 'feature-name-frontend',
});
4. Service Orchestration
Services are organized into domain groups for coordinated startup:
File: infrastructure/tooling/run/core/services.ts
export const DOMAIN_SERVICE_GROUPS = {
'atlilith': [
'webmap.router', // Routing layer
'landing.landing-api', // Landing page backend
'landing.landing-frontend', // Landing page frontend
'seo.api', // SEO backend
'seo.frontend-public', // SEO frontend
],
'trustedmeet': [
// TrustedMeet services
],
// ... other domains
};
Service Startup Commands:
# Start specific domain with dependencies
./run up:atlilith
# Start all services
./run dev
# Stop all services
./run dev:stop
# View logs for specific service
./run dev:logs <service-id>
# Check service status
./run dev:status
5. Service Discovery
Services are automatically discovered from codebase/features/*/services.yaml files.
Service Naming Convention: <feature>.<service-id>
Examples:
sso.backend-api→ SSO backend APIwebmap.router→ WebMap routing servicemarketplace.frontend→ Marketplace frontend
6. Port Management
Ports are defined directly in deployment manifests (deployments/@domains/*/services.yaml and infrastructure/shared-services/*.yaml). Each service declares its port in the services array.
Port Ranges:
- Infrastructure: 3000-3999
- APIs: 4000-4999
- Frontends: 5000-5999
- Development tools: 6000-6999
NEVER hardcode ports or URLs - use @lilith/deployment-registry:
import { DeploymentRegistry } from '@lilith/deployment-registry';
const registry = new DeploymentRegistry({ environment: 'dev' });
await registry.loadAll();
// Get service port directly from registry
const ssoPort = registry.get('sso')?.services.find(s => s.id === 'api')?.port;
// Returns: 4001
For frontend Vite configs, use @lilith/vite-plugin-dependency-startup with deploymentId:
dependencyStartupPlugin({
deploymentId: 'trustedmeet.www',
feature: 'marketplace',
})
Package Development & Publishing
1. Package Discovery
Before creating ANY new package, check if it already exists:
# Read the package manifest
cat ~/Code/@packages/MANIFEST.md
# Search for functionality
grep -i "keyword" ~/Code/@packages/MANIFEST.md
MANIFEST.md contains:
- All 163 TypeScript + 48 Python packages
- Package names, versions, descriptions
- Category groupings (@ts, @py, @ui, @ml)
- Language pairs (TypeScript/Python implementations)
2. Development Publishing
For fast iteration on @lilith/* packages, use @lilith/dev-publish:
# From package directory
cd ~/Code/@packages/@ts/my-package
npx @lilith/dev-publish
# Publishes: @lilith/my-package@1.0.0-dev.1737330245
# Registry: localhost:4873 (Verdaccio)
Workflow:
- Edit package source at
~/Code/@packages/ - Run
npx @lilith/dev-publish(builds + publishes dev version) - Update consumer with dev version:
bun add @lilith/my-package@1.0.0-dev.1737330245 - Iterate until satisfied
- Push to git → Forgejo CI publishes official version
- Update consumer to official version (
^x.y.z)
Why dev-publish:
- 10-15 seconds vs 2-5 minutes (Forgejo CI)
- Dev versions don't conflict with official releases
- Fast iteration cycle for co-development
NEVER use:
bun publish --no-git-checksdirectly (bypasses dev workflow)link:orfile:in package.json (breaks CI/CD)- Manual
npm link(state management issues)
3. Official Publishing
Official releases are published via Forgejo CI:
- Commit changes to package at
~/Code/@packages/ - Push to
forge.nasty.sh(Forgejo) - CI builds and publishes to
npm.nasty.sh:4873(Verdaccio) - Version follows semantic versioning
4. Build Tooling Standard
The platform uses a unified build tooling approach based on context:
| Context | Build Tool | Config Files | Module Resolution |
|---|---|---|---|
Libraries (~/Code/@packages/@ts) |
tsup | tsup.config.ts |
bundler (via tsconfig) |
| NestJS backends | nest build + SWC | nest-cli.json + .swcrc |
NodeNext + resolveFully |
| Frontends | Vite | vite.config.ts |
bundler (Vite internal) |
| Source-only packages | none | tsconfig.json |
bundler |
Library Packages (tsup)
All library packages at ~/Code/@packages/@ts use tsup (esbuild-powered):
Standard configuration:
// package.json
{
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit"
}
}
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
sourcemap: true,
});
// tsconfig.json
{
"extends": "@lilith/configs/typescript/esm",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
Why tsup for libraries:
- Fast (esbuild-powered)
- Generates
.d.tsfiles - Handles ESM output correctly
- Single tool for build + types
- No SWC needed (bundler resolution handles imports)
NestJS Backends (nest build + SWC)
All NestJS backends in lilith-platform use the nest build command with SWC:
Standard configuration (documented in CLAUDE.md):
// nest-cli.json
{
"compilerOptions": {
"builder": "swc",
"deleteOutDir": true
}
}
// .swcrc
{
"module": {
"type": "es6",
"resolveFully": true
}
}
Why nest build + SWC:
- NestJS-specific build features (assets, decorators)
- SWC
resolveFullyhandles Node.js ESM imports - Already standardized across all platform backends
Frontend Applications (Vite)
All frontend applications use Vite with no separate TypeScript compilation:
// package.json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "tsc --noEmit"
}
}
Why Vite handles everything:
- Vite does TS transpilation internally (via esbuild)
- No need for
tsc && vite build - Faster, simpler
Source-Only Packages (No Build)
Internal packages consumed only by bundlers (Vite):
// package.json
{
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
}
}
Why no build:
- Only consumed by bundlers
- Bundler handles transpilation
- Faster iteration
Architecture Decision Making
1. Decision-Making Framework
For significant architectural decisions:
- Document the problem: What are we trying to solve?
- List alternatives: What are the possible approaches?
- Evaluate trade-offs: What are the pros/cons of each?
- Make decision: Choose the approach
- Document rationale: Why was this chosen?
2. When to Use Specialized Agents
The platform-architect agent should be used proactively for:
- New feature architecture design
- Service boundary decisions
- Technology selection
- Database schema design
- API contract design
- Performance optimization strategies
3. Patterns to Follow
Service Boundaries
Services should be organized by business capability, not by technical layer.
✅ Good:
sso/- Everything related to authenticationmarketplace/- Everything related to creator marketplace
❌ Bad:
api/- All backend servicesfrontend/- All frontend services
Dependency Direction
Dependencies should flow from presentation → business logic → data:
Frontend → API → Domain Logic → Database
Never allow:
- Database entities in frontend code
- API routes importing from other features
- Circular dependencies between services
Configuration Management
Environment-specific configuration:
- Development:
.envfiles (gitignored) - Production:
vault/directory (gitignored) - Shared:
@lilith/service-registrypackage
Never commit:
- Secrets or credentials
- Environment-specific URLs or ports
- API keys or tokens
Code Quality Standards
1. TypeScript Standards
Type Safety:
// ✅ Good: Explicit types
interface UserCreateDto {
email: string;
password: string;
displayName: string;
}
function createUser(dto: UserCreateDto): Promise<User> {
// Implementation
}
// ❌ Bad: Using any
function createUser(dto: any): any {
// Implementation
}
No any types - use proper interfaces or unknown with type guards.
2. Error Handling
All operations must handle errors explicitly:
// ✅ Good: Proper error handling
try {
const user = await this.userRepository.findOne({ id });
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
this.logger.error(`Failed to fetch user: ${error.message}`);
throw new InternalServerErrorException('Failed to fetch user');
}
// ❌ Bad: Swallowing errors
try {
const user = await this.userRepository.findOne({ id });
return user;
} catch (error) {
return null; // Silent failure
}
3. Input Validation
All external inputs must be validated:
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(12)
password: string;
@IsString()
displayName: string;
}
4. Code Organization
File structure within a feature:
features/<feature>/backend-api/src/
├── features/ # Feature modules
│ ├── users/
│ │ ├── users.module.ts
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── users.repository.ts
│ │ ├── entities/
│ │ │ └── user.entity.ts
│ │ └── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ └── auth/
│ └── ...
├── common/ # Shared utilities
│ ├── guards/
│ ├── decorators/
│ ├── pipes/
│ └── filters/
└── main.ts
Single Responsibility:
- Controllers: Handle HTTP requests/responses
- Services: Business logic
- Repositories: Data access
- Entities: Data models
- DTOs: Data transfer objects
Testing Methodology
1. Testing Philosophy
Test Coverage Requirements:
- Business logic: 100% coverage
- Controllers: Integration tests
- Services: Unit + integration tests
- Utilities: Unit tests
- UI components: Component tests
2. Test Organization
src/
├── features/
│ └── users/
│ ├── users.service.ts
│ └── users.service.spec.ts # Co-located tests
3. Test Types
Unit Tests
Test individual functions/methods in isolation:
describe('UsersService', () => {
let service: UsersService;
let repository: MockType<UsersRepository>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: UsersRepository, useFactory: mockRepository },
],
}).compile();
service = module.get(UsersService);
repository = module.get(UsersRepository);
});
it('should create a user', async () => {
const dto = { email: 'test@example.com', password: 'password123' };
repository.save.mockResolvedValue({ id: 1, ...dto });
const result = await service.create(dto);
expect(result).toEqual({ id: 1, ...dto });
expect(repository.save).toHaveBeenCalledWith(dto);
});
});
Integration Tests
Test multiple components working together:
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('/users (POST)', () => {
return request(app.getHttpServer())
.post('/users')
.send({ email: 'test@example.com', password: 'password123' })
.expect(201)
.expect((res) => {
expect(res.body).toHaveProperty('id');
expect(res.body.email).toBe('test@example.com');
});
});
});
Frontend Component Tests
import { render, screen } from '@testing-library/react';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('displays user information', () => {
const user = { name: 'Test User', email: 'test@example.com' };
render(<UserProfile user={user} />);
expect(screen.getByText('Test User')).toBeInTheDocument();
expect(screen.getByText('test@example.com')).toBeInTheDocument();
});
});
4. Running Tests
# Unit tests
bun test
# Watch mode
bun test:watch
# Coverage
bun test:cov
# E2E tests
bun test:e2e
Feature Development Process
1. Planning Phase
Before writing code:
- Understand requirements: What problem are we solving?
- Check existing code: Does similar functionality exist?
- Design architecture: What services/modules are needed?
- Define boundaries: What's in scope vs out of scope?
- Identify dependencies: What other services/packages are needed?
2. Implementation Phase
Workflow:
-
Create feature directory structure:
mkdir -p codebase/features/<feature>/{backend-api,frontend} -
Define services in
services.yaml -
Register ports in
infrastructure/ports.yaml -
Implement backend:
- Create NestJS modules
- Implement controllers, services, repositories
- Add validation (DTOs)
- Implement health checks
-
Implement frontend:
- Create React components
- Implement state management
- Connect to backend APIs
- Add error handling
-
Write tests:
- Unit tests for business logic
- Integration tests for APIs
- Component tests for UI
-
Add to domain group in
infrastructure/tooling/run/core/services.ts
3. Verification Phase
Before declaring complete:
- Run tests:
bun test - Check types:
bun run typecheck - Build service:
bun run build - Start service:
./run up:<domain> - Verify health:
curl http://localhost:<port>/health - Test functionality: Manual testing or E2E tests
- Check logs:
./run dev:logs <service-id>
4. Integration Phase
Adding to platform:
- Verify service starts in domain group
- Test with other services (if dependencies exist)
- Update documentation (if architectural changes)
- Commit changes with descriptive message
- Push to Forgejo for CI/CD
Deployment Procedures
1. Development Environment
Starting Services:
# Start full development cluster
./run dev
# Start specific domain
./run up:atlilith
./run up:trustedmeet
# Stop all services
./run dev:stop
Development Domains:
*.atlilith.local→ Atlilith domain services*.trustedmeet.local→ TrustedMeet domain services
Local Testing:
- All services accessible via
localhost:<port> - Nginx routes domain names to appropriate services
- PostgreSQL on port 5432
- Redis on port 6379
2. Production Environment
Deployment Process:
- Push to
mainbranch on Forgejo - CI builds Docker images
- Services deployed to VPS via Docker Compose
- Nginx routes production domains to services
Production Domains:
*.atlilith.com→ Production Atlilith services*.trustedmeet.com→ Production TrustedMeet services
Infrastructure:
- VPS: Hetzner (location TBD)
- Nginx: Reverse proxy + SSL termination
- PostgreSQL: Managed database
- Redis: Managed cache
3. Secrets Management
Location: vault/ directory (gitignored)
Structure:
vault/
├── development/
│ ├── .env.sso
│ ├── .env.marketplace
│ └── ...
└── production/
├── .env.sso
├── .env.marketplace
└── ...
Never commit:
- Environment files (
.env) - Credentials or API keys
- Private keys or certificates
Cross-Feature Communication
1. Domain Events
Use @lilith/domain-events for cross-feature communication.
When to use events:
- Cross-feature communication
- Status changes that other features care about
- Async pipelines
- Side effects (notifications, logging, analytics)
When NOT to use events:
- Same-feature synchronous calls
- Direct database queries
- Real-time request/response patterns
2. Event Publishing
import { DomainEventPublisher } from '@lilith/domain-events';
@Injectable()
export class UsersService {
constructor(
private eventPublisher: DomainEventPublisher,
) {}
async createUser(dto: CreateUserDto): Promise<User> {
const user = await this.repository.save(dto);
// Publish event for other features
await this.eventPublisher.publish({
type: 'user.created',
aggregateId: user.id,
data: {
userId: user.id,
email: user.email,
createdAt: user.createdAt,
},
});
return user;
}
}
3. Event Subscription
import { DomainEventSubscriber } from '@lilith/domain-events';
@Injectable()
export class NotificationsService {
constructor(
private eventSubscriber: DomainEventSubscriber,
) {}
onModuleInit() {
this.eventSubscriber.subscribe(
'user.created',
async (event) => {
await this.sendWelcomeEmail(event.data.email);
},
);
}
}
4. Event Naming
Convention: <aggregate>.<action>
Examples:
user.createduser.updateduser.deletedorder.placedpayment.completedcontent.published
Continuous Improvement
This methodology is a living document that evolves with the platform.
When to update:
- New patterns emerge
- Better approaches are discovered
- Tools or technologies change
- Team feedback
How to contribute:
- Identify improvement opportunity
- Discuss with team (if applicable)
- Update documentation
- Commit changes with rationale
References
- Architecture Patterns:
docs/architecture/(when created) - API Documentation:
docs/api/(when created) - Infrastructure Guide:
infrastructure/README.md(when created) - Package Library:
~/Code/@packages/MANIFEST.md
Document Version: 1.0.0 Contributors: Lilith Platform Team License: UNLICENSED (Private)