Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,97 @@ Events.emit('data:users:created', newUser);

---

## 🧬 THE COMPRESSION PRINCIPLE (Fundamental Law)

**One logical decision, one place. No exceptions.**

This applies to BOTH program memory and data memory:

| Type | Uncompressed (BAD) | Compressed (GOOD) |
|------|-------------------|-------------------|
| **Logic** | `findRoom()` in 5 files | `resolveRoomIdentifier()` in RoutingService |
| **Data** | `UUID_PATTERN` in 3 files | `isUUID()` exported from one place |
| **Constants** | Magic strings everywhere | `ROOM_UNIQUE_IDS.GENERAL` |

**The ideal codebase is maximally compressed:**
```
Root Primitives (minimal) ← Commands.execute(), Events.emit()
↓
Derived Abstractions ← RoutingService, ContentService
↓
Application Code ← References, never reimplements
```

**Why this matters:**
- Duplication = redundancy = **drift** (copies diverge over time = bugs)
- Compression = elegance = **coherence** (one truth = consistency)
- The most elegant equation is the most minimal: **E = mcΒ²**

**The test:** For ANY decision (logic or data), can you point to exactly ONE place in the codebase? If not, you have uncompressed redundancy that WILL cause bugs.

**The goal:** Build from root primitives. Let elegant architecture emerge from compression. When abstractions are right, code reads like intent.

---

## πŸ”¬ THE METHODICAL PROCESS (Building With Intent)

**Be SUPER methodical. No skipping steps. This is the discipline that makes elegance real.**

### The Outlier Validation Strategy

Don't build exhaustively. Don't build hopefully. **Build diversely to prove the interface:**

```
Wrong: Build adapters 1, 2, 3, 4, 5... (exhaustive - wastes time)
Wrong: Build adapter 1, assume 2-5 work (hopeful - will break)
Right: Build adapter 1 (local/simple) + adapter N (most different)
If both fit cleanly β†’ interface is proven β†’ rest are trivial
```

**Example - AI Provider Adapters:**
1. Build Ollama adapter (local, unique API)
2. Build cloud adapter (remote, auth, rate limits)
3. Try LoRA fine-tuning on each
4. If interface handles both extremes β†’ it handles everything

This is like testing edge cases: if edges pass, middle is guaranteed.

### The Mandatory Steps

For ANY new pattern or abstraction:

```
1. IDENTIFY - See the pattern emerging (2-3 similar implementations)
2. DESIGN - Draft the interface/abstraction
3. OUTLIER A - Build first implementation (pick something local/simple)
4. OUTLIER B - Build second implementation (pick something maximally DIFFERENT)
5. VALIDATE - Does the interface fit both WITHOUT forcing? If no, redesign.
6. GENERATOR - Write generator to encode the pattern
7. DOCUMENT - Update README, add to CLAUDE.md if architectural
8. STOP - Don't build remaining implementations until needed
```

**NEVER skip steps 4-6.** Step 4 (outlier B) catches bad abstractions early. Step 5 (validate) prevents wishful thinking. Step 6 (generator) ensures the pattern is followed forever.

### Building With Intent (Not Over-Engineering)

```
Over-engineering: Build the future NOW (10 adapters day 1)
Under-engineering: Build only NOW, refactor "later" (never happens)
Intent: Build NOW in a shape that WELCOMES the future
```

**The "first adapter that seems silly"** - it's not silly. It's laying rails. When adapter 2 comes, it slots in. When adapter 3 comes, you realize the interface was right. The first adapter was a TEST of your idealized future.

It's OK to:
- Build one adapter even if the pattern seems overkill
- Design the interface as if 10 implementations exist
- Add a TODO noting the intended extension point

**The restraint:** See the next few moves like chess, but don't PLAY them all now. Lay the pattern, validate with outliers, write the generator, stop.

---

## 🎯 CORE PHILOSOPHY: Continuous Improvement

**"A good developer improves the entire system continuously, not just their own new stuff."**
Expand Down
File renamed without changes.
14 changes: 8 additions & 6 deletions src/debug/jtag/api/data-seed/UserDataSeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PersonaUser, type PersonaUserData } from '../../domain/user/PersonaUser
import { SystemUser } from '../../domain/user/SystemUser';
import { BaseUser } from '../../domain/user/BaseUser';
import { generateUUID, type UUID } from '../../system/core/types/CrossPlatformUUID';
import { DEFAULT_USERS } from '../../system/data/domains/DefaultEntities';
import type { BaseUserDataWithRelationships } from '../../domain/user/UserRelationships';
import { USER_IDS, USER_CONFIG, COLLECTIONS } from './SeedConstants';
import { MODEL_IDS } from '../../system/shared/Constants';
Expand Down Expand Up @@ -39,8 +40,9 @@ export class UserDataSeed {
*/
public static generateSeedUsers(): UserSeedData {
// Create human user with proper domain object
// Use DEFAULT_USERS.HUMAN constant directly for single source of truth
const humanUserData: HumanUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.HUMAN,
sessionId: generateUUID(),
displayName: USER_CONFIG.HUMAN.DISPLAY_NAME || USER_CONFIG.HUMAN.NAME,
citizenType: 'human',
Expand Down Expand Up @@ -70,7 +72,7 @@ export class UserDataSeed {

// Claude Code - AI Assistant Agent
const claudeAgentData: AgentUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.CLAUDE_CODE,
sessionId: generateUUID(),
displayName: USER_CONFIG.CLAUDE.NAME,
citizenType: 'ai',
Expand All @@ -97,7 +99,7 @@ export class UserDataSeed {

// GeneralAI - General Assistant Persona
const generalPersonaData: PersonaUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.GENERAL_AI,
sessionId: generateUUID(),
displayName: 'GeneralAI',
citizenType: 'ai',
Expand Down Expand Up @@ -132,7 +134,7 @@ export class UserDataSeed {

// CodeAI - Code Analysis Specialist Agent
const codeAgentData: AgentUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.CODE_AI,
sessionId: generateUUID(),
displayName: 'CodeAI',
citizenType: 'ai',
Expand Down Expand Up @@ -161,7 +163,7 @@ export class UserDataSeed {

// PlannerAI - Strategic Planning Assistant Agent
const plannerAgentData: AgentUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.PLANNER_AI,
sessionId: generateUUID(),
displayName: 'PlannerAI',
citizenType: 'ai',
Expand Down Expand Up @@ -190,7 +192,7 @@ export class UserDataSeed {

// Auto Route - Smart Agent Selection Agent
const autoRouteAgentData: AgentUserData = {
userId: generateUUID(),
userId: DEFAULT_USERS.AUTO_ROUTE,
sessionId: generateUUID(),
displayName: 'Auto Route',
citizenType: 'ai',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import type {
} from '../shared/ChatAnalyzeTypes';
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
import { ChatMessageEntity } from '@system/data/entities/ChatMessageEntity';
import { RoomEntity } from '@system/data/entities/RoomEntity';
import type { UUID } from '@system/core/types/CrossPlatformUUID';
import crypto from 'crypto';
import { resolveRoomIdentifier } from '@system/routing/RoutingService';

export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {

Expand All @@ -24,8 +23,23 @@ export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {
protected async executeChatAnalyze(params: ChatAnalyzeParams): Promise<ChatAnalyzeResult> {
const { roomId, checkDuplicates = true, checkTimestamps = true, limit = 500 } = params;

// Resolve room name to UUID if needed
const resolvedRoomId = await this.resolveRoom(roomId, params);
// Resolve room name to UUID (single source of truth: RoutingService)
const resolved = await resolveRoomIdentifier(roomId);
if (!resolved) {
return {
success: false,
roomId,
totalMessages: 0,
analysis: {
hasDuplicates: false,
duplicateCount: 0,
hasTimestampIssues: false,
anomalyCount: 0,
},
error: `Room not found: ${roomId}`,
} as ChatAnalyzeResult;
}
const resolvedRoomId = resolved.id;

// Get all messages from room
const listResult = await Commands.execute<DataListParams, DataListResult<ChatMessageEntity>>(
Expand Down Expand Up @@ -182,43 +196,4 @@ export class ChatAnalyzeServerCommand extends ChatAnalyzeCommand {

return anomalies;
}

/**
* Resolve room name or ID to actual UUID
* Accepts either a room name (e.g., "general") or a room UUID
*/
private async resolveRoom(roomIdOrName: string, params: ChatAnalyzeParams): Promise<UUID> {
// Check if it already looks like a UUID
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (uuidPattern.test(roomIdOrName)) {
return roomIdOrName as UUID;
}

// Query all rooms to find by name
const result = await Commands.execute<DataListParams, DataListResult<RoomEntity>>(
DATA_COMMANDS.LIST,
{
collection: RoomEntity.collection,
filter: {},
context: params.context,
sessionId: params.sessionId
}
);

if (!result.success || !result.items) {
throw new Error('Failed to query rooms');
}

// Find by ID or name
const room = result.items.find((r: RoomEntity) =>
r.id === roomIdOrName || r.name === roomIdOrName
);

if (!room) {
const roomNames = result.items.map((r: RoomEntity) => r.name).join(', ');
throw new Error(`Room not found: ${roomIdOrName}. Available rooms: ${roomNames}`);
}

return room.id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { ChatPollCommand } from '../shared/ChatPollCommand';
import type { ChatPollParams, ChatPollResult } from '../shared/ChatPollTypes';
import { DataDaemon } from '@daemons/data-daemon/shared/DataDaemon';
import type { ChatMessageEntity } from '@system/data/entities/ChatMessageEntity';
import type { RoomEntity } from '@system/data/entities/RoomEntity';
import type { UUID } from '@system/core/types/CrossPlatformUUID';
import { resolveRoomIdentifier } from '@system/routing/RoutingService';

export class ChatPollServerCommand extends ChatPollCommand {

Expand All @@ -19,18 +19,13 @@ export class ChatPollServerCommand extends ChatPollCommand {

protected async executeChatPoll(params: ChatPollParams): Promise<ChatPollResult> {
try {
// Resolve room name to roomId if provided
// Resolve room identifier (single source of truth: RoutingService)
let roomId: UUID | undefined = params.roomId;

if (params.room && !roomId) {
const roomsResult = await DataDaemon.query<RoomEntity>({
collection: 'rooms',
filter: { name: params.room },
limit: 1
});

if (roomsResult.success && roomsResult.data && roomsResult.data.length > 0) {
roomId = roomsResult.data[0].id;
const resolved = await resolveRoomIdentifier(params.room);
if (resolved) {
roomId = resolved.id;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import { transformPayload } from '@system/core/types/JTAGTypes';
import type { ICommandDaemon } from '@daemons/command-daemon/shared/CommandBase';
import { ChatSendCommand } from '../shared/ChatSendCommand';
import type { ChatSendParams, ChatSendResult } from '../shared/ChatSendTypes';
import { RoomEntity } from '@system/data/entities/RoomEntity';
import { UserEntity } from '@system/data/entities/UserEntity';
import { ChatMessageEntity, type MediaItem } from '@system/data/entities/ChatMessageEntity';
import type { UUID } from '@system/core/types/CrossPlatformUUID';
import { Commands } from '@system/core/shared/Commands';
import type { DataListParams, DataListResult } from '@commands/data/list/shared/DataListTypes';
import type { DataCreateParams, DataCreateResult } from '@commands/data/create/shared/DataCreateTypes';
import { UserIdentityResolver } from '@system/user/shared/UserIdentityResolver';
import { resolveRoomIdentifier } from '@system/routing/RoutingService';

export class ChatSendServerCommand extends ChatSendCommand {

Expand All @@ -27,9 +27,12 @@ export class ChatSendServerCommand extends ChatSendCommand {
protected async executeChatSend(params: ChatSendParams): Promise<ChatSendResult> {
console.log('πŸ”§ ChatSendServerCommand.executeChatSend START', { room: params.room });

// 1. Find room
const room = await this.findRoom(params.room || 'general', params);
console.log('πŸ”§ ChatSendServerCommand.executeChatSend ROOM FOUND', { roomId: room.id, roomName: room.entity.name });
// 1. Find room (single source of truth: RoutingService)
const resolved = await resolveRoomIdentifier(params.room || 'general');
if (!resolved) {
throw new Error(`Room not found: ${params.room || 'general'}`);
}
console.log('πŸ”§ ChatSendServerCommand.executeChatSend ROOM FOUND', { roomId: resolved.id, roomName: resolved.displayName });

// 2. Get sender - auto-detect caller identity (Claude Code, Joel, etc.)
const sender = params.senderId
Expand All @@ -39,7 +42,7 @@ export class ChatSendServerCommand extends ChatSendCommand {

// 3. Create message entity
const messageEntity = new ChatMessageEntity();
messageEntity.roomId = room.id; // Use DataRecord.id, not entity.id
messageEntity.roomId = resolved.id; // From RoutingService resolution
messageEntity.senderId = sender.id; // sender is also DataRecord with .id
console.log('πŸ”§ ChatSendServerCommand.executeChatSend MESSAGE ENTITY', { roomId: messageEntity.roomId, senderId: messageEntity.senderId });
messageEntity.senderName = sender.entity.displayName;
Expand Down Expand Up @@ -108,58 +111,17 @@ export class ChatSendServerCommand extends ChatSendCommand {
// 5. Generate short ID (last 6 chars of UUID - from BaseEntity.id)
const shortId = storedEntity.id.slice(-6);

console.log(`βœ… Message sent: #${shortId} to ${room.entity.name}`);
console.log(`βœ… Message sent: #${shortId} to ${resolved.displayName}`);

return transformPayload(params, {
success: true,
message: `Message sent to ${room.entity.name} (#${shortId})`,
message: `Message sent to ${resolved.displayName} (#${shortId})`,
messageEntity: storedEntity,
shortId: shortId,
roomId: room.id
roomId: resolved.id
});
}

/**
* Find room by ID or name
*/
private async findRoom(roomIdOrName: string, params: ChatSendParams): Promise<{ id: UUID; entity: RoomEntity }> {
console.log('πŸ”§ findRoom START', { roomIdOrName });

// Query all rooms using data/list command
const result = await Commands.execute<DataListParams, DataListResult<RoomEntity>>(
DATA_COMMANDS.LIST,
{
collection: RoomEntity.collection,
filter: {},
context: params.context,
sessionId: params.sessionId
}
);

console.log('πŸ”§ findRoom QUERY RESULT', {
success: result.success,
recordCount: result.items?.length || 0
});

if (!result.success || !result.items) {
throw new Error('Failed to query rooms');
}

// Find by ID or name
const room = result.items.find((r: RoomEntity) =>
r.id === roomIdOrName || r.name === roomIdOrName
);

if (!room) {
const roomNames = result.items.map((r: RoomEntity) => r.name).join(', ');
console.log('πŸ”§ findRoom NOT FOUND', { roomIdOrName, availableRooms: roomNames });
throw new Error(`Room not found: ${roomIdOrName}. Available: ${roomNames}`);
}

console.log('πŸ”§ findRoom FOUND', { roomId: room.id, roomName: room.name });
return { id: room.id, entity: room };
}

/**
* Find user by ID
*/
Expand Down
Loading
Loading