Skip to content
Open
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
300 changes: 300 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# BuildingAI - Agent Coding Guidelines

This document provides essential information for agentic coding assistants working in this repository.

## Essential Commands

### Building
```bash
# Build all packages
pnpm run build

# Build specific package
cd packages/api && pnpm run build
cd packages/web/buildingai-ui && pnpm run generate
```

### Development
```bash
# Start all services (API + Web)
pnpm dev

# Start specific service
pnpm dev:api # NestJS API backend (port 4090)
pnpm dev:web # Nuxt 4 frontend (port 4091)
```

### Linting & Formatting
```bash
# Lint all packages
pnpm run lint
pnpm run lint:fix # Auto-fix lint issues

# Format code
pnpm run format # Prettier formatting

# Type checking
pnpm run typecheck # TypeScript type validation
```

### Testing
```bash
# Run all tests
cd packages/api && pnpm test

# Run specific test file
jest path/to/test.spec.ts

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:cov

# Debug tests
pnpm test:debug
```

### Docker
```bash
# Start PostgreSQL and Redis containers
pnpm docker:up

# Stop containers
pnpm docker:down
```

## Code Style Guidelines

### Formatting (Prettier)
- Print width: 100 characters
- Tab width: 4 spaces
- Semicolons: required
- Quotes: double quotes (not single)
- Trailing commas: all
- End of line: LF (Unix-style)

### ESLint Configuration
- Uses TypeScript ESLint with flat config
- `@typescript-eslint/no-explicit-any`: off
- `@typescript-eslint/no-unsafe-*`: off/warn
- `@typescript-eslint/no-floating-promises`: warn
- `no-undef`: off (TypeScript handles this)

### Naming Conventions
- **Classes/PascalCase**: `TagService`, `CreateTagDto`, `TagController`
- **Variables/Functions/camelCase**: `tagService`, `createTag`, `findById`
- **Files/kebab-case**: `tag.service.ts`, `create-tag.dto.ts`, `tag.controller.ts`
- **Constants/SCREAMING_SNAKE_CASE**: `TAG_TYPE`, `MAX_RETRY_COUNT`
- **Interfaces/PascalCase**: `TagService`, `TagController`, `IOptions` (if needed)

### Import Organization
Order imports in this specific sequence:
1. Node.js built-in modules
2. Third-party packages
3. Internal @buildingai packages (workspace:*)
4. Internal @modules and @common imports
5. Relative imports (./ or ../)

Example:
```typescript
import fs from "node:fs";
import { Injectable } from "@nestjs/common";
import { BaseService } from "@buildingai/base";
import { HttpErrorFactory } from "@buildingai/errors";
import { CreateTagDto } from "@modules/tag/dto";
import { findStackTargetFile } from "@common/utils";
```

### JSDoc Comments
Always add JSDoc comments for classes and public methods:
```typescript
/**
* Tag service for managing tag entities
*/
@Injectable()
export class TagService extends BaseService<Tag> {
/**
* Create a new tag
*
* @param createTagDto - DTO for creating a tag
* @returns The created tag
*/
async createTag(createTagDto: CreateTagDto): Promise<Partial<Tag>> {
// Implementation
}
}
```

### Error Handling
Use the centralized error factory:
```typescript
// Services: throw errors directly
throw HttpErrorFactory.badRequest("A tag with the same name already exists");
throw HttpErrorFactory.notFound(`Tag ${id} does not exist`);

// Controllers: try-catch and return response objects
async batchRemove(@Body("ids") ids: string[]) {
try {
await this.tagService.batchDelete(ids);
return { success: true, message: "Batch deletion succeeded" };
} catch (error) {
return { success: false, message: error.message };
}
}
```

### TypeScript Best Practices
- Always use type annotations for function parameters and return values
- Prefer `type` over `interface` unless you need interface features
- Use utility types: `Partial`, `Pick`, `Omit`, `Record`
- Avoid `any` when possible; use `unknown` for truly dynamic data

## Backend (NestJS) Patterns

### Module Structure
```
modules/feature-name/
├── feature-name.module.ts # Module definition
├── controllers/
│ ├── console/ # Admin/management endpoints
│ └── web/ # Public/client endpoints
├── services/
│ └── feature-name.service.ts # Business logic
├── dto/ # Data Transfer Objects
│ ├── create-feature.dto.ts
│ ├── update-feature.dto.ts
│ └── query-feature.dto.ts
└── interfaces/ # TypeScript interfaces (if needed)
```

### Service Patterns
```typescript
@Injectable()
export class FeatureService extends BaseService<Feature> {
constructor(@InjectRepository(Feature) private readonly repo: Repository<Feature>) {
super(repo);
}

async createFeature(dto: CreateFeatureDto): Promise<Partial<Feature>> {
// Validation
const existing = await this.repo.findOne({ where: { name: dto.name } });
if (existing) {
throw HttpErrorFactory.badRequest("Name already exists");
}

// Create
return super.create(dto);
}
}
```

### Controller Patterns
```typescript
@Controller("feature")
export class FeatureController extends BaseController {
constructor(private readonly featureService: FeatureService) {
super();
}

@Get()
async findAll(@Query() queryDto: QueryFeatureDto) {
return this.featureService.list(queryDto);
}

@Post()
async create(@Body() createDto: CreateFeatureDto) {
return this.featureService.createFeature(createDto);
}
}
```

### DTO Validation
```typescript
import { IsString, IsOptional, Length } from "class-validator";

export class CreateFeatureDto {
@IsString({ message: "Name must be a string" })
@Length(1, 100, { message: "Name length must be between 1 and 100" })
name: string;

@IsOptional()
@IsString()
description?: string;
}
```

## Frontend (Nuxt 4) Patterns

### File Structure
```
app/
├── pages/ # File-based routing
│ ├── console/ # Admin console pages
│ └── index.vue
├── components/ # Reusable components
├── layouts/ # Layout components
├── middleware/ # Route middleware
├── stores/ # Pinia stores
└── utils/ # Utility functions
```

### Component Naming
- Use PascalCase for component filenames: `UserCard.vue`, `DataTable.vue`
- Use kebab-case in templates: `<user-card />`, `<data-table />`

### Type Definitions
```typescript
// interfaces/api.ts
export interface User {
id: string;
name: string;
email: string;
}

export interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
```

## Monorepo Architecture

This is a pnpm workspace with Turbo for build orchestration:
- `packages/@buildingai/*`: Shared packages (22 packages)
- `packages/api`: NestJS backend
- `packages/web/buildingai-ui`: Nuxt 4 frontend
- `packages/cli`: CLI tools
- `extensions/`: Extension plugins

When adding internal imports, always use workspace protocol:
```json
{
"dependencies": {
"@buildingai/base": "workspace:*",
"@buildingai/errors": "workspace:*"
}
}
```

## Environment Requirements
- **Node.js**: >=22.20.x <23
- **pnpm**: 10.x
- **PostgreSQL**: 17.x
- **Redis**: 8.x

## Before Committing
Always run:
```bash
pnpm run lint # Ensure no lint errors
pnpm run typecheck # Ensure TypeScript compiles
pnpm test # Ensure tests pass (if applicable)
```

## Key Technologies
- **Backend**: NestJS 11.x, TypeORM 0.3.x, BullMQ 5.x
- **Frontend**: Nuxt 4, Vue 3.5.x, Pinia 3.x, NuxtUI 3.x
- **Database**: PostgreSQL 17.x with pgvector extension
- **Cache**: Redis 8.x
- **Build**: Turbo 2.6.x, Vite 7.2.x, SWC
Loading