Claude Code Plugins

Community-maintained marketplace

Feedback

tzurot-architecture

@mattnigh/skills_collection
0
0

Microservices architecture for Tzurot v3 - Service boundaries, responsibilities, dependency rules, and anti-patterns from v2. Use when deciding where code belongs or designing new features.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name tzurot-architecture
description Microservices architecture for Tzurot v3 - Service boundaries, responsibilities, dependency rules, and anti-patterns from v2. Use when deciding where code belongs or designing new features.
lastUpdated 2025-12-20

Tzurot v3 Architecture

Use this skill when: Adding new features, deciding where code belongs, designing system interactions, or refactoring service boundaries.

Quick Reference

Discord User
    ↓
bot-client (Discord.js)
    ↓ HTTP
api-gateway (Express + BullMQ)
    ↓ Redis Queue
ai-worker (AI + pgvector)
    ↓
OpenRouter/Gemini API

Core Principles

  1. Simple, clean classes - No DDD over-engineering (learned from v2)
  2. Clear service boundaries - Each service has single responsibility
  3. No circular dependencies - Services can't import from each other
  4. Shared code in common-types - Cross-service types, utils, services
  5. Constructor injection - Simple dependency passing, no DI containers

Three Microservices

Service Responsibility Does Does NOT
bot-client Discord interface Events, webhooks, commands, formatting Business logic, AI calls, direct DB
api-gateway HTTP API + queue Endpoints, validation, job creation AI processing, Discord interaction
ai-worker AI + memory Jobs, memory, AI calls, embeddings HTTP endpoints, Discord interaction

Where to Put New Code

Type Location
Webhook/message formatting bot-client/
Slash commands bot-client/commands/
HTTP endpoints api-gateway/routes/
Job creation api-gateway/queue.ts
AI provider clients ai-worker/providers/
Memory/embeddings ai-worker/services/
Shared types/constants common-types/
Discord type guards common-types/types/

Autocomplete Utilities (CRITICAL)

ALWAYS check for existing utilities before writing autocomplete handlers.

Available in bot-client/src/utils/autocomplete/:

Utility Purpose Option Names
handlePersonalityAutocomplete Personality selection personality, character
handlePersonaAutocomplete Profile/persona selection profile, persona
// ✅ GOOD - Delegate to shared utility
import { handlePersonalityAutocomplete } from '../../utils/autocomplete/personalityAutocomplete.js';

await handlePersonalityAutocomplete(interaction, {
  optionName: 'personality',
  showVisibility: true,
  ownedOnly: false,
});

// ❌ BAD - Duplicating 50+ lines of autocomplete logic

Error Message Patterns

Layer Pattern Example
api-gateway Clean JSON, NO emojis { "error": "NOT_FOUND", "message": "Persona not found" }
bot-client ADD emojis for users '❌ Profile not found.'
// ✅ Gateway - clean for programmatic use
sendError(res, ErrorResponses.notFound('Persona'));

// ✅ Bot - emoji for users
await interaction.editReply({ content: '❌ Profile not found.' });

Anti-Patterns from v2 (DON'T DO)

Pattern Why Not v3 Alternative
Generic IRepository<T> Too abstract Concrete service methods
DI containers Over-engineered Direct instantiation
Controller→UseCase→Service→Repository→ORM Too many layers Route→Service→Prisma
Complex event bus Unnecessary Redis pub/sub for cache only
Value objects everywhere Overhead Simple validation functions
// ❌ v2 - Container hell
container.bind('PersonalityService').to(PersonalityService);
const service = container.get('PersonalityService');

// ✅ v3 - Simple
const service = new PersonalityService(prisma);

Dependency Injection

// ✅ GOOD - Simple constructor injection
class MyService {
  constructor(
    private prisma: PrismaClient,
    private redis: Redis
  ) {}
}

const service = new MyService(prisma, redis);

When to Extract a Service

Extract when:

  • Shared across multiple microservices → common-types
  • Complex business logic
  • Stateful operations
  • Easier testability needed

Keep inline when:

  • Used in one place only
  • Stateless utility function
  • Very simple logic

Database Access

Direct Prisma in services - No repository pattern

// ✅ Direct Prisma
async getPersonality(id: string) {
  return this.prisma.personality.findUnique({ where: { id } });
}

// ❌ Generic repository
interface PersonalityRepository {
  findById(id: string): Promise<Personality>;
}

Configuration

  • Environment variables: Secrets (tokens, DB URLs)
  • common-types constants: Application config (timeouts, limits)
import { TIMEOUTS, RETRY_CONFIG } from '@tzurot/common-types';
const timeout = TIMEOUTS.LLM_INVOCATION;

Related Skills

  • tzurot-async-flow - BullMQ job patterns
  • tzurot-db-vector - Database patterns
  • tzurot-types - Type definitions
  • tzurot-gemini-collab - Major design decisions

References

  • Full architecture: CLAUDE.md#architecture
  • Project structure: CLAUDE.md#project-structure
  • Architecture decisions: docs/architecture/ARCHITECTURE_DECISIONS.md