siesa-agents 2.1.25 → 2.1.26-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/bin/install.js +9 -106
- package/bmad-core/agents/architect.md +0 -2
- package/bmad-core/agents/ux-expert.md +0 -1
- package/bmad-core/data/backend-standards.md +195 -127
- package/claude/hooks/file-restriction-hook.py +51 -0
- package/claude/hooks/track-agent.py +67 -0
- package/claude/settings.local.json +37 -1
- package/package.json +1 -1
- package/bmad-core/data/architecture-patterns.md +0 -261
- package/bmad-core/data/technology-stack.md +0 -81
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SIESA Agents
|
|
1
|
+
# SIESA BMAD Agents
|
|
2
2
|
|
|
3
3
|
Paquete para instalar y configurar agentes SIESA en tu proyecto.
|
|
4
4
|
|
|
@@ -52,7 +52,6 @@ El paquete instala las siguientes carpetas en tu directorio actual:
|
|
|
52
52
|
- **`.bmad-core/`** - Archivos principales del sistema BMAD
|
|
53
53
|
- **`.vscode/`** - Configuración de Visual Studio Code
|
|
54
54
|
- **`.github/`** - Configuración de GitHub Actions y workflows
|
|
55
|
-
- **`.claude/`** - Configuración de Claude Code Commands y workflows
|
|
56
55
|
|
|
57
56
|
## Características
|
|
58
57
|
|
|
@@ -71,17 +70,22 @@ El paquete instala las siguientes carpetas en tu directorio actual:
|
|
|
71
70
|
|
|
72
71
|
Para actualizar una instalación existente, simplemente ejecuta el comando nuevamente:
|
|
73
72
|
|
|
73
|
+
**Versión estable (recomendado):**
|
|
74
74
|
```bash
|
|
75
75
|
npx siesa-agents
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
**Versión de desarrollo:**
|
|
79
|
+
```bash
|
|
80
|
+
npx siesa-agents@dev
|
|
81
|
+
```
|
|
82
|
+
|
|
78
83
|
El sistema detectará automáticamente que ya existe una instalación y la actualizará.
|
|
79
84
|
|
|
80
85
|
## Requisitos
|
|
81
86
|
|
|
82
87
|
- Node.js >= 14.0.0
|
|
83
88
|
- npm >= 6.0.0
|
|
84
|
-
- Python >= 3.7 (requerido para hooks de Claude Code)
|
|
85
89
|
|
|
86
90
|
## Estructura de archivos instalados
|
|
87
91
|
|
|
@@ -95,6 +99,8 @@ tu-proyecto/
|
|
|
95
99
|
└── [workflows y configuración de GitHub]
|
|
96
100
|
```
|
|
97
101
|
|
|
102
|
+
|
|
103
|
+
|
|
98
104
|
## Soporte
|
|
99
105
|
|
|
100
106
|
Si encuentras algún problema durante la instalación, por favor verifica:
|
|
@@ -109,7 +115,4 @@ MIT
|
|
|
109
115
|
|
|
110
116
|
## Autor
|
|
111
117
|
|
|
112
|
-
SIESA - Sistemas de Información Empresarial
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
*Versión actualizada automáticamente por CI/CD*
|
|
118
|
+
SIESA - Sistemas de Información Empresarial
|
package/bin/install.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
class SiesaBmadInstaller {
|
|
8
9
|
constructor() {
|
|
9
10
|
// Definir las carpetas primero (nombres en el paquete vs nombres finales)
|
|
@@ -15,18 +16,9 @@ class SiesaBmadInstaller {
|
|
|
15
16
|
{ source: 'kiro', target: '.kiro' },
|
|
16
17
|
{ source: 'resources', target: '.resources' }
|
|
17
18
|
];
|
|
18
|
-
|
|
19
|
-
// Lista de archivos que se preservan automáticamente (no se crean backups)
|
|
20
|
-
this.ignoredFiles = [
|
|
21
|
-
'data/technical-preferences.md'
|
|
22
|
-
];
|
|
23
|
-
|
|
24
19
|
this.targetDir = process.cwd();
|
|
25
20
|
// Intentar múltiples ubicaciones posibles para el paquete
|
|
26
21
|
this.packageDir = this.findPackageDir();
|
|
27
|
-
|
|
28
|
-
// Almacenamiento temporal para contenido de archivos ignorados
|
|
29
|
-
this.preservedContent = new Map();
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
showBanner() {
|
|
@@ -37,7 +29,8 @@ class SiesaBmadInstaller {
|
|
|
37
29
|
console.log('╚════██║██║██╔══╝ ╚════██║██╔══██║ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║');
|
|
38
30
|
console.log('███████║██║███████╗███████║██║ ██║ ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████║');
|
|
39
31
|
console.log('╚══════╝╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝');
|
|
40
|
-
console.log('');
|
|
32
|
+
console.log('Version de Desarrollador Prueba Arnol');
|
|
33
|
+
console.log('\n');
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
findPackageDir() {
|
|
@@ -134,8 +127,7 @@ class SiesaBmadInstaller {
|
|
|
134
127
|
modifiedFiles.push({
|
|
135
128
|
folder: mapping.target,
|
|
136
129
|
file: relativePath,
|
|
137
|
-
fullPath: targetFile
|
|
138
|
-
is_ignored: this.ignoredFiles.includes(relativePath)
|
|
130
|
+
fullPath: targetFile
|
|
139
131
|
});
|
|
140
132
|
}
|
|
141
133
|
} catch (error) {
|
|
@@ -147,8 +139,7 @@ class SiesaBmadInstaller {
|
|
|
147
139
|
modifiedFiles.push({
|
|
148
140
|
folder: mapping.target,
|
|
149
141
|
file: relativePath,
|
|
150
|
-
fullPath: targetFile
|
|
151
|
-
is_ignored: this.ignoredFiles.includes(relativePath)
|
|
142
|
+
fullPath: targetFile
|
|
152
143
|
});
|
|
153
144
|
}
|
|
154
145
|
}
|
|
@@ -179,10 +170,6 @@ class SiesaBmadInstaller {
|
|
|
179
170
|
}
|
|
180
171
|
|
|
181
172
|
async promptUser(modifiedFiles) {
|
|
182
|
-
|
|
183
|
-
const hasNonIgnoredFiles = modifiedFiles.some(file => file.is_ignored == false)
|
|
184
|
-
if (!hasNonIgnoredFiles) return '2'
|
|
185
|
-
|
|
186
173
|
console.log('\n⚠️ Se detectaron archivos modificados:');
|
|
187
174
|
|
|
188
175
|
// Agrupar por carpeta
|
|
@@ -223,12 +210,6 @@ class SiesaBmadInstaller {
|
|
|
223
210
|
console.log('\n🔄 Creando backup de archivos modificados...');
|
|
224
211
|
|
|
225
212
|
for (const item of modifiedFiles) {
|
|
226
|
-
// No crear backup de archivos ignorados
|
|
227
|
-
if (item.is_ignored) {
|
|
228
|
-
console.log(`✓ Preservando: ${item.file} (sin backup)`);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
213
|
const originalPath = item.fullPath;
|
|
233
214
|
const backupPath = this.getBackupPath(originalPath);
|
|
234
215
|
|
|
@@ -292,20 +273,11 @@ class SiesaBmadInstaller {
|
|
|
292
273
|
async copyWithBackupPreservation(sourcePath, targetPath) {
|
|
293
274
|
// Obtener todos los archivos backup existentes
|
|
294
275
|
const backupFiles = await this.findBackupFiles(targetPath);
|
|
295
|
-
|
|
296
|
-
// Copiar la carpeta
|
|
276
|
+
|
|
277
|
+
// Copiar la carpeta completa sobrescribiendo
|
|
297
278
|
await fs.copy(sourcePath, targetPath, {
|
|
298
279
|
overwrite: true,
|
|
299
|
-
recursive: true
|
|
300
|
-
filter: (src) => {
|
|
301
|
-
const relativePath = path.relative(sourcePath, src);
|
|
302
|
-
// No sobrescribir archivos ignorados si ya existen
|
|
303
|
-
if (this.ignoredFiles.includes(relativePath)) {
|
|
304
|
-
const targetFile = path.join(targetPath, relativePath);
|
|
305
|
-
return !fs.existsSync(targetFile);
|
|
306
|
-
}
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
280
|
+
recursive: true
|
|
309
281
|
});
|
|
310
282
|
|
|
311
283
|
// Restaurar los archivos backup
|
|
@@ -360,16 +332,7 @@ class SiesaBmadInstaller {
|
|
|
360
332
|
if (fs.existsSync(sourcePath)) {
|
|
361
333
|
await fs.copy(sourcePath, targetPath, {
|
|
362
334
|
overwrite: true,
|
|
363
|
-
recursive: true
|
|
364
|
-
filter: (src) => {
|
|
365
|
-
const relativePath = path.relative(sourcePath, src);
|
|
366
|
-
// No sobrescribir archivos ignorados si ya existen
|
|
367
|
-
if (this.ignoredFiles.includes(relativePath)) {
|
|
368
|
-
const targetFile = path.join(targetPath, relativePath);
|
|
369
|
-
return !fs.existsSync(targetFile);
|
|
370
|
-
}
|
|
371
|
-
return true;
|
|
372
|
-
}
|
|
335
|
+
recursive: true
|
|
373
336
|
});
|
|
374
337
|
} else {
|
|
375
338
|
console.warn(`⚠️ Carpeta ${mapping.source} no encontrada en el paquete`);
|
|
@@ -405,9 +368,6 @@ class SiesaBmadInstaller {
|
|
|
405
368
|
await this.performUpdateWithBackups();
|
|
406
369
|
} else {
|
|
407
370
|
// Si no hay backups, hacer actualización normal (remover y copiar)
|
|
408
|
-
// Pero primero preservar archivos ignorados
|
|
409
|
-
await this.preserveIgnoredFiles();
|
|
410
|
-
|
|
411
371
|
for (const mapping of this.folderMappings) {
|
|
412
372
|
const targetPath = path.join(this.targetDir, mapping.target);
|
|
413
373
|
|
|
@@ -418,64 +378,7 @@ class SiesaBmadInstaller {
|
|
|
418
378
|
|
|
419
379
|
// Realizar instalación nueva
|
|
420
380
|
await this.performInstallation();
|
|
421
|
-
|
|
422
|
-
// Restaurar archivos ignorados
|
|
423
|
-
await this.restoreIgnoredFiles();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async preserveIgnoredFiles() {
|
|
428
|
-
console.log('🔒 Preservando archivos de configuración...');
|
|
429
|
-
|
|
430
|
-
for (const mapping of this.folderMappings) {
|
|
431
|
-
const targetFolderPath = path.join(this.targetDir, mapping.target);
|
|
432
|
-
|
|
433
|
-
if (!fs.existsSync(targetFolderPath)) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
for (const ignoredFile of this.ignoredFiles) {
|
|
438
|
-
const filePath = path.join(targetFolderPath, ignoredFile);
|
|
439
|
-
|
|
440
|
-
if (fs.existsSync(filePath)) {
|
|
441
|
-
try {
|
|
442
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
443
|
-
const key = `${mapping.target}/${ignoredFile}`;
|
|
444
|
-
this.preservedContent.set(key, content);
|
|
445
|
-
console.log(`✓ Preservando: ${ignoredFile}`);
|
|
446
|
-
} catch (error) {
|
|
447
|
-
console.warn(`⚠️ Error leyendo ${ignoredFile}: ${error.message}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
async restoreIgnoredFiles() {
|
|
455
|
-
if (this.preservedContent.size === 0) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
console.log('🔄 Restaurando archivos de configuración...');
|
|
460
|
-
|
|
461
|
-
for (const [key, content] of this.preservedContent) {
|
|
462
|
-
const [targetFolder, ...filePathParts] = key.split('/');
|
|
463
|
-
const filePath = path.join(this.targetDir, targetFolder, ...filePathParts);
|
|
464
|
-
|
|
465
|
-
try {
|
|
466
|
-
// Asegurar que el directorio existe
|
|
467
|
-
await fs.ensureDir(path.dirname(filePath));
|
|
468
|
-
|
|
469
|
-
// Restaurar el contenido
|
|
470
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
471
|
-
console.log(`✓ Restaurado: ${filePathParts.join('/')}`);
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.warn(`⚠️ Error restaurando ${filePathParts.join('/')}: ${error.message}`);
|
|
474
|
-
}
|
|
475
381
|
}
|
|
476
|
-
|
|
477
|
-
// Limpiar el mapa después de restaurar
|
|
478
|
-
this.preservedContent.clear();
|
|
479
382
|
}
|
|
480
383
|
|
|
481
384
|
showPostInstallMessage() {
|
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
# Backend Development Standards
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Architecture Principles
|
|
4
|
+
|
|
5
|
+
### Hexagonal Architecture Implementation
|
|
6
|
+
- **Application Core**: Domain entities, value objects, aggregates, and domain services
|
|
7
|
+
- **Primary Ports**: Use cases, commands, queries, and application services
|
|
8
|
+
- **Primary Adapters**: REST controllers, GraphQL resolvers, message handlers
|
|
9
|
+
- **Secondary Ports**: Repository interfaces, external service interfaces
|
|
10
|
+
- **Secondary Adapters**: Prisma repositories, HTTP clients, message publishers
|
|
11
|
+
|
|
12
|
+
### Dependency Rules
|
|
13
|
+
- Application core must not depend on external frameworks
|
|
14
|
+
- All dependencies point inward toward the domain
|
|
15
|
+
- Use dependency inversion for all external concerns
|
|
16
|
+
- Interfaces defined in application layer, implementations in infrastructure
|
|
4
17
|
|
|
5
18
|
## Technology Stack Standards
|
|
6
19
|
|
|
7
20
|
### Core Technologies
|
|
8
21
|
- **NestJS**: 10+ with TypeScript and decorators
|
|
9
22
|
- **TypeScript**: Strict mode enabled, no `any` types
|
|
10
|
-
- **Prisma**: ORM for database operations (no raw queries
|
|
11
|
-
- **Jest
|
|
12
|
-
- **Class-validator
|
|
23
|
+
- **Prisma**: ORM for database operations (no raw queries)
|
|
24
|
+
- **Jest**: Unit and integration testing
|
|
25
|
+
- **Class-validator**: Request validation and transformation
|
|
13
26
|
|
|
14
|
-
### Framework
|
|
15
|
-
- **Default
|
|
16
|
-
- **Database**: Prisma ORM only - no raw SQL queries
|
|
17
|
-
- **Testing**: TDD approach with
|
|
18
|
-
- **Documentation**: Swagger/OpenAPI
|
|
19
|
-
- **Messaging**: NestJS Microservices (Redis, RabbitMQ, or gRPC)
|
|
27
|
+
### Framework Selection Rules
|
|
28
|
+
- **Default**: Always use NestJS 10+ with TypeScript
|
|
29
|
+
- **Database**: Prisma ORM only - no raw SQL queries allowed
|
|
30
|
+
- **Testing**: TDD approach with Jest and Supertest
|
|
31
|
+
- **Documentation**: Swagger/OpenAPI for all endpoints
|
|
20
32
|
|
|
21
33
|
### Development Tools
|
|
22
|
-
- **Nx**: MonoRepo management and build
|
|
34
|
+
- **Nx**: MonoRepo management and build system
|
|
23
35
|
- **ESLint + Prettier**: Code quality and formatting
|
|
24
|
-
- **Husky**:
|
|
25
|
-
- **Winston**: Structured logging
|
|
26
|
-
- **Redis**: Caching
|
|
27
|
-
- **Passport + JWT**: Authentication and authorization
|
|
36
|
+
- **Husky**: Git hooks for pre-commit validation
|
|
37
|
+
- **Winston**: Structured logging
|
|
38
|
+
- **Redis**: Caching and message transport
|
|
28
39
|
|
|
29
|
-
## Domain-Driven Design
|
|
40
|
+
## Domain-Driven Design Standards
|
|
30
41
|
|
|
31
42
|
### Entity Structure
|
|
32
43
|
```typescript
|
|
@@ -35,6 +46,7 @@ export class UserEntity extends AggregateRoot {
|
|
|
35
46
|
public readonly id: UserId,
|
|
36
47
|
private _email: EmailValueObject,
|
|
37
48
|
private _name: NameValueObject,
|
|
49
|
+
private _createdAt: Date,
|
|
38
50
|
) {
|
|
39
51
|
super();
|
|
40
52
|
}
|
|
@@ -44,6 +56,7 @@ export class UserEntity extends AggregateRoot {
|
|
|
44
56
|
UserId.generate(),
|
|
45
57
|
EmailValueObject.create(props.email),
|
|
46
58
|
NameValueObject.create(props.name),
|
|
59
|
+
new Date(),
|
|
47
60
|
);
|
|
48
61
|
user.addDomainEvent(new UserCreatedEvent(user.id));
|
|
49
62
|
return user;
|
|
@@ -78,6 +91,11 @@ export class EmailValueObject {
|
|
|
78
91
|
}
|
|
79
92
|
}
|
|
80
93
|
|
|
94
|
+
private isValidEmail(email: string): boolean {
|
|
95
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
96
|
+
return emailRegex.test(email);
|
|
97
|
+
}
|
|
98
|
+
|
|
81
99
|
equals(other: EmailValueObject): boolean {
|
|
82
100
|
return this.value === other.value;
|
|
83
101
|
}
|
|
@@ -88,45 +106,15 @@ export class EmailValueObject {
|
|
|
88
106
|
}
|
|
89
107
|
```
|
|
90
108
|
|
|
91
|
-
### Repository Pattern
|
|
109
|
+
### Repository Interface Pattern
|
|
92
110
|
```typescript
|
|
93
|
-
// Interface (in application/ports/repositories)
|
|
94
111
|
export interface UserRepositoryInterface {
|
|
95
112
|
save(user: UserEntity): Promise<UserEntity>;
|
|
96
113
|
findById(id: UserId): Promise<UserEntity | null>;
|
|
97
114
|
findByEmail(email: EmailValueObject): Promise<UserEntity | null>;
|
|
115
|
+
findAll(criteria: FindUsersCriteria): Promise<UserEntity[]>;
|
|
98
116
|
delete(id: UserId): Promise<void>;
|
|
99
117
|
}
|
|
100
|
-
|
|
101
|
-
// Implementation (in infrastructure/repositories)
|
|
102
|
-
@Injectable()
|
|
103
|
-
export class PrismaUserRepository implements UserRepositoryInterface {
|
|
104
|
-
constructor(private readonly prisma: PrismaService) {}
|
|
105
|
-
|
|
106
|
-
async save(user: UserEntity): Promise<UserEntity> {
|
|
107
|
-
const data = {
|
|
108
|
-
id: user.id.toString(),
|
|
109
|
-
email: user.email.toString(),
|
|
110
|
-
name: user.name.toString(),
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const savedUser = await this.prisma.user.upsert({
|
|
114
|
-
where: { id: data.id },
|
|
115
|
-
update: data,
|
|
116
|
-
create: data,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return this.toDomain(savedUser);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private toDomain(prismaUser: User): UserEntity {
|
|
123
|
-
return UserEntity.reconstitute({
|
|
124
|
-
id: UserId.create(prismaUser.id),
|
|
125
|
-
email: EmailValueObject.create(prismaUser.email),
|
|
126
|
-
name: NameValueObject.create(prismaUser.name),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
118
|
```
|
|
131
119
|
|
|
132
120
|
## Use Case Standards
|
|
@@ -145,19 +133,20 @@ export class CreateUserUseCase {
|
|
|
145
133
|
async execute(command: CreateUserCommand): Promise<UserResponseDto> {
|
|
146
134
|
// 1. Validate business rules
|
|
147
135
|
await this.validateUserDoesNotExist(command.email);
|
|
148
|
-
|
|
136
|
+
|
|
149
137
|
// 2. Create domain entity
|
|
150
138
|
const user = UserEntity.create({
|
|
151
139
|
email: command.email,
|
|
152
140
|
name: command.name,
|
|
153
141
|
});
|
|
154
|
-
|
|
142
|
+
|
|
155
143
|
// 3. Persist entity
|
|
156
144
|
const savedUser = await this.userRepository.save(user);
|
|
157
|
-
|
|
145
|
+
|
|
158
146
|
// 4. Publish domain events
|
|
159
147
|
await this.eventBus.publishAll(savedUser.getUncommittedEvents());
|
|
160
|
-
|
|
148
|
+
savedUser.markEventsAsCommitted();
|
|
149
|
+
|
|
161
150
|
// 5. Return response DTO
|
|
162
151
|
return UserResponseDto.fromEntity(savedUser);
|
|
163
152
|
}
|
|
@@ -183,16 +172,20 @@ export class CreateUserCommand {
|
|
|
183
172
|
@IsNotEmpty()
|
|
184
173
|
@Length(2, 50)
|
|
185
174
|
readonly name: string;
|
|
175
|
+
|
|
176
|
+
@IsOptional()
|
|
177
|
+
@IsString()
|
|
178
|
+
readonly organizationId?: string;
|
|
186
179
|
}
|
|
187
180
|
```
|
|
188
181
|
|
|
189
182
|
## Testing Standards
|
|
190
183
|
|
|
191
184
|
### Testing Strategy
|
|
192
|
-
- **Unit Tests**: Domain entities, value objects, use cases
|
|
193
|
-
- **Integration Tests**: Repository implementations,
|
|
194
|
-
- **E2E Tests**: Complete API workflows
|
|
195
|
-
- **
|
|
185
|
+
- **Unit Tests**: Domain entities, value objects, use cases
|
|
186
|
+
- **Integration Tests**: Repository implementations, external services
|
|
187
|
+
- **E2E Tests**: Complete API workflows
|
|
188
|
+
- **Contract Tests**: External service integrations
|
|
196
189
|
|
|
197
190
|
### Test Structure
|
|
198
191
|
```typescript
|
|
@@ -205,8 +198,19 @@ describe('CreateUserUseCase', () => {
|
|
|
205
198
|
const module = await Test.createTestingModule({
|
|
206
199
|
providers: [
|
|
207
200
|
CreateUserUseCase,
|
|
208
|
-
{
|
|
209
|
-
|
|
201
|
+
{
|
|
202
|
+
provide: USER_REPOSITORY,
|
|
203
|
+
useValue: {
|
|
204
|
+
save: jest.fn(),
|
|
205
|
+
findByEmail: jest.fn(),
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
provide: EVENT_BUS,
|
|
210
|
+
useValue: {
|
|
211
|
+
publishAll: jest.fn(),
|
|
212
|
+
},
|
|
213
|
+
},
|
|
210
214
|
],
|
|
211
215
|
}).compile();
|
|
212
216
|
|
|
@@ -215,33 +219,46 @@ describe('CreateUserUseCase', () => {
|
|
|
215
219
|
eventBus = module.get(EVENT_BUS);
|
|
216
220
|
});
|
|
217
221
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
describe('execute', () => {
|
|
223
|
+
it('should create user successfully', async () => {
|
|
224
|
+
// Arrange
|
|
225
|
+
const command = new CreateUserCommand();
|
|
226
|
+
command.email = 'test@example.com';
|
|
227
|
+
command.name = 'Test User';
|
|
223
228
|
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
const expectedUser = UserEntity.create({
|
|
230
|
+
email: command.email,
|
|
231
|
+
name: command.name,
|
|
232
|
+
});
|
|
226
233
|
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
userRepository.findByEmail.mockResolvedValue(null);
|
|
235
|
+
userRepository.save.mockResolvedValue(expectedUser);
|
|
229
236
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
expect(userRepository.save).toHaveBeenCalledWith(expect.any(UserEntity));
|
|
233
|
-
expect(eventBus.publishAll).toHaveBeenCalled();
|
|
234
|
-
});
|
|
237
|
+
// Act
|
|
238
|
+
const result = await useCase.execute(command);
|
|
235
239
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
// Assert
|
|
241
|
+
expect(result.email).toBe(command.email);
|
|
242
|
+
expect(userRepository.save).toHaveBeenCalledWith(expect.any(UserEntity));
|
|
243
|
+
expect(eventBus.publishAll).toHaveBeenCalled();
|
|
244
|
+
});
|
|
240
245
|
|
|
241
|
-
|
|
246
|
+
it('should throw error when user already exists', async () => {
|
|
247
|
+
// Arrange
|
|
248
|
+
const command = new CreateUserCommand();
|
|
249
|
+
command.email = 'existing@example.com';
|
|
250
|
+
command.name = 'Test User';
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
const existingUser = UserEntity.create({
|
|
253
|
+
email: command.email,
|
|
254
|
+
name: 'Existing User',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
userRepository.findByEmail.mockResolvedValue(existingUser);
|
|
258
|
+
|
|
259
|
+
// Act & Assert
|
|
260
|
+
await expect(useCase.execute(command)).rejects.toThrow(UserAlreadyExistsException);
|
|
261
|
+
});
|
|
245
262
|
});
|
|
246
263
|
});
|
|
247
264
|
```
|
|
@@ -260,20 +277,23 @@ export class UserController {
|
|
|
260
277
|
|
|
261
278
|
@Post()
|
|
262
279
|
@ApiOperation({ summary: 'Create a new user' })
|
|
263
|
-
@ApiResponse({ status: 201, type: UserResponseDto })
|
|
280
|
+
@ApiResponse({ status: 201, description: 'User created successfully', type: UserResponseDto })
|
|
264
281
|
@ApiResponse({ status: 400, description: 'Bad request' })
|
|
265
|
-
|
|
282
|
+
@ApiResponse({ status: 409, description: 'User already exists' })
|
|
283
|
+
async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
|
|
266
284
|
const command = new CreateUserCommand();
|
|
267
|
-
Object.assign(command,
|
|
285
|
+
Object.assign(command, createUserDto);
|
|
268
286
|
return this.createUserUseCase.execute(command);
|
|
269
287
|
}
|
|
270
288
|
|
|
271
289
|
@Get(':id')
|
|
272
290
|
@ApiOperation({ summary: 'Get user by ID' })
|
|
273
|
-
@
|
|
291
|
+
@ApiParam({ name: 'id', description: 'User ID' })
|
|
292
|
+
@ApiResponse({ status: 200, description: 'User found', type: UserResponseDto })
|
|
274
293
|
@ApiResponse({ status: 404, description: 'User not found' })
|
|
275
294
|
async getUser(@Param('id', ParseUUIDPipe) id: string): Promise<UserResponseDto> {
|
|
276
|
-
|
|
295
|
+
const query = new GetUserQuery(id);
|
|
296
|
+
return this.getUserUseCase.execute(query);
|
|
277
297
|
}
|
|
278
298
|
}
|
|
279
299
|
```
|
|
@@ -288,7 +308,9 @@ model User {
|
|
|
288
308
|
name String
|
|
289
309
|
createdAt DateTime @default(now())
|
|
290
310
|
updatedAt DateTime @updatedAt
|
|
291
|
-
|
|
311
|
+
|
|
312
|
+
// Relationships
|
|
313
|
+
orders Order[]
|
|
292
314
|
|
|
293
315
|
@@map("users")
|
|
294
316
|
}
|
|
@@ -298,6 +320,8 @@ model Order {
|
|
|
298
320
|
total Decimal @db.Decimal(10, 2)
|
|
299
321
|
status OrderStatus
|
|
300
322
|
userId String
|
|
323
|
+
|
|
324
|
+
// Relationships
|
|
301
325
|
user User @relation(fields: [userId], references: [id])
|
|
302
326
|
items OrderItem[]
|
|
303
327
|
|
|
@@ -313,60 +337,104 @@ enum OrderStatus {
|
|
|
313
337
|
}
|
|
314
338
|
```
|
|
315
339
|
|
|
316
|
-
###
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
340
|
+
### Repository Implementation
|
|
341
|
+
```typescript
|
|
342
|
+
@Injectable()
|
|
343
|
+
export class PrismaUserRepository implements UserRepositoryInterface {
|
|
344
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
345
|
+
|
|
346
|
+
async save(user: UserEntity): Promise<UserEntity> {
|
|
347
|
+
const data = {
|
|
348
|
+
id: user.id.toString(),
|
|
349
|
+
email: user.email.toString(),
|
|
350
|
+
name: user.name.toString(),
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const savedUser = await this.prisma.user.upsert({
|
|
354
|
+
where: { id: data.id },
|
|
355
|
+
update: data,
|
|
356
|
+
create: data,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return this.toDomain(savedUser);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async findById(id: UserId): Promise<UserEntity | null> {
|
|
363
|
+
const user = await this.prisma.user.findUnique({
|
|
364
|
+
where: { id: id.toString() },
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return user ? this.toDomain(user) : null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private toDomain(prismaUser: User): UserEntity {
|
|
371
|
+
return UserEntity.reconstitute({
|
|
372
|
+
id: UserId.create(prismaUser.id),
|
|
373
|
+
email: EmailValueObject.create(prismaUser.email),
|
|
374
|
+
name: NameValueObject.create(prismaUser.name),
|
|
375
|
+
createdAt: prismaUser.createdAt,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
323
380
|
|
|
324
381
|
## Security Standards
|
|
325
382
|
|
|
326
383
|
### Authentication & Authorization
|
|
327
|
-
-
|
|
328
|
-
-
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
384
|
+
- JWT tokens with proper expiration
|
|
385
|
+
- Role-based access control (RBAC)
|
|
386
|
+
- Input validation on all endpoints
|
|
387
|
+
- Rate limiting for public endpoints
|
|
388
|
+
- HTTPS only in production
|
|
332
389
|
|
|
333
390
|
### Data Protection
|
|
334
|
-
- Encrypt sensitive data at rest
|
|
335
|
-
-
|
|
336
|
-
- Implement audit logging
|
|
337
|
-
-
|
|
338
|
-
-
|
|
391
|
+
- Encrypt sensitive data at rest
|
|
392
|
+
- Use environment variables for secrets
|
|
393
|
+
- Implement audit logging
|
|
394
|
+
- Regular security updates
|
|
395
|
+
- OWASP compliance
|
|
339
396
|
|
|
340
397
|
## Performance Standards
|
|
341
398
|
|
|
342
399
|
### Database Optimization
|
|
343
|
-
- Proper indexing
|
|
344
|
-
- Connection pooling
|
|
345
|
-
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
400
|
+
- Proper indexing strategies
|
|
401
|
+
- Connection pooling
|
|
402
|
+
- Query optimization
|
|
403
|
+
- Pagination for large datasets
|
|
404
|
+
- Database monitoring
|
|
348
405
|
|
|
349
406
|
### Caching Strategy
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
-
|
|
353
|
-
-
|
|
407
|
+
- Redis for session data
|
|
408
|
+
- Application-level caching
|
|
409
|
+
- HTTP caching headers
|
|
410
|
+
- CDN for static assets
|
|
411
|
+
- Cache invalidation patterns
|
|
354
412
|
|
|
355
|
-
##
|
|
413
|
+
## MonoRepo Organization
|
|
356
414
|
|
|
357
|
-
###
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
415
|
+
### Shared Libraries Structure
|
|
416
|
+
```
|
|
417
|
+
libs/
|
|
418
|
+
├── common/
|
|
419
|
+
│ ├── decorators/
|
|
420
|
+
│ ├── filters/
|
|
421
|
+
│ ├── guards/
|
|
422
|
+
│ ├── interceptors/
|
|
423
|
+
│ ├── pipes/
|
|
424
|
+
│ └── utils/
|
|
425
|
+
├── domain-core/
|
|
426
|
+
│ ├── base/
|
|
427
|
+
│ ├── interfaces/
|
|
428
|
+
│ └── exceptions/
|
|
429
|
+
└── database/
|
|
430
|
+
├── base-repository.ts
|
|
431
|
+
├── transaction.decorator.ts
|
|
432
|
+
└── prisma.service.ts
|
|
433
|
+
```
|
|
362
434
|
|
|
363
|
-
###
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
"timestamp": "2024-01-15T10:30:00Z",
|
|
370
|
-
"path": "/api/users"
|
|
371
|
-
}
|
|
372
|
-
```
|
|
435
|
+
### Service Independence
|
|
436
|
+
- Each microservice has its own database
|
|
437
|
+
- Shared code through libraries only
|
|
438
|
+
- Independent deployment pipelines
|
|
439
|
+
- Service-to-service communication via events
|
|
440
|
+
- No direct database access between services
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
# Leer JSON desde stdin
|
|
7
|
+
data = json.load(sys.stdin)
|
|
8
|
+
|
|
9
|
+
# Obtener información del archivo y sesión
|
|
10
|
+
file_path = data.get('tool_input', {}).get('file_path', '')
|
|
11
|
+
extension = os.path.splitext(file_path)[1].lower() if file_path else ''
|
|
12
|
+
session_id = data.get('session_id', '')
|
|
13
|
+
cwd = data.get('cwd', '')
|
|
14
|
+
|
|
15
|
+
# Construir ruta relativa al log desde el cwd
|
|
16
|
+
log_file = os.path.join(cwd, '.claude', 'logs', 'active_agents.json')
|
|
17
|
+
|
|
18
|
+
# Agentes que solo pueden escribir markdown
|
|
19
|
+
MARKDOWN_ONLY_AGENTS = ['PO', 'SM', 'PM', 'ANALYST', 'ARCHITECT', 'UX-EXPERT']
|
|
20
|
+
|
|
21
|
+
# Verificar si la sesión actual tiene un agente activo
|
|
22
|
+
if session_id and os.path.exists(log_file):
|
|
23
|
+
try:
|
|
24
|
+
with open(log_file, 'r', encoding='utf-8') as f:
|
|
25
|
+
active_agents = json.load(f)
|
|
26
|
+
|
|
27
|
+
# Si la sesión actual tiene un agente activo
|
|
28
|
+
if session_id in active_agents:
|
|
29
|
+
agent_type = active_agents[session_id]['agent']
|
|
30
|
+
|
|
31
|
+
# Si el agente está en la lista de solo markdown
|
|
32
|
+
if agent_type in MARKDOWN_ONLY_AGENTS:
|
|
33
|
+
# Solo permitir archivos markdown
|
|
34
|
+
if extension != '.md':
|
|
35
|
+
result = {
|
|
36
|
+
"hookSpecificOutput": {
|
|
37
|
+
"hookEventName": "PreToolUse",
|
|
38
|
+
"permissionDecision": "deny",
|
|
39
|
+
"permissionDecisionReason": f"⛔ El agente de tipo {agent_type} solo puede redactar archivos markdown"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
print(json.dumps(result))
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
except:
|
|
45
|
+
# Si hay error leyendo el log, permitir la operación
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# Si no está bloqueado, permitir la operación (no imprimir nada)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
# En caso de error, permitir la operación
|
|
51
|
+
pass
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# Leer JSON desde stdin
|
|
8
|
+
data = json.load(sys.stdin)
|
|
9
|
+
|
|
10
|
+
session_id = data.get('session_id', '')
|
|
11
|
+
prompt = data.get('prompt', '').lower()
|
|
12
|
+
cwd = data.get('cwd', '')
|
|
13
|
+
|
|
14
|
+
# Construir ruta relativa al log desde el cwd
|
|
15
|
+
log_file = os.path.join(cwd, '.claude', 'logs', 'active_agents.json')
|
|
16
|
+
|
|
17
|
+
# Crear directorio si no existe
|
|
18
|
+
log_dir = os.path.dirname(log_file)
|
|
19
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
# Lista completa de agentes disponibles
|
|
22
|
+
agent_identifiers = {
|
|
23
|
+
'agents:po': 'PO',
|
|
24
|
+
'agents:sm': 'SM',
|
|
25
|
+
'agents:pm': 'PM',
|
|
26
|
+
'agents:analyst': 'ANALYST',
|
|
27
|
+
'agents:architect': 'ARCHITECT',
|
|
28
|
+
'agents:dev': 'DEV',
|
|
29
|
+
'agents:backend': 'BACKEND',
|
|
30
|
+
'agents:frontend': 'FRONTEND',
|
|
31
|
+
'agents:qa': 'QA',
|
|
32
|
+
'agents:ux-expert': 'UX-EXPERT',
|
|
33
|
+
'agents:bmad-master': 'BMAD-MASTER',
|
|
34
|
+
'agents:bmad-orchestrator': 'BMAD-ORCHESTRATOR'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Detectar si se está invocando un agente
|
|
38
|
+
agent_type = None
|
|
39
|
+
for identifier, agent_name in agent_identifiers.items():
|
|
40
|
+
if identifier in prompt or f'/bmad:{identifier}' in prompt:
|
|
41
|
+
agent_type = agent_name
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if agent_type and session_id:
|
|
45
|
+
# Leer log existente
|
|
46
|
+
active_agents = {}
|
|
47
|
+
if os.path.exists(log_file):
|
|
48
|
+
try:
|
|
49
|
+
with open(log_file, 'r', encoding='utf-8') as f:
|
|
50
|
+
active_agents = json.load(f)
|
|
51
|
+
except:
|
|
52
|
+
active_agents = {}
|
|
53
|
+
|
|
54
|
+
# Actualizar o agregar la sesión con el agente actual
|
|
55
|
+
active_agents[session_id] = {
|
|
56
|
+
'agent': agent_type,
|
|
57
|
+
'timestamp': datetime.now().isoformat(),
|
|
58
|
+
'last_prompt': prompt[:100] # Guardar inicio del prompt para debug
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Guardar log actualizado
|
|
62
|
+
with open(log_file, 'w', encoding='utf-8') as f:
|
|
63
|
+
json.dump(active_agents, f, indent=2, ensure_ascii=False)
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
# En caso de error, no bloquear la operación
|
|
67
|
+
pass
|
|
@@ -16,5 +16,41 @@
|
|
|
16
16
|
],
|
|
17
17
|
"deny": [],
|
|
18
18
|
"ask": []
|
|
19
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"hooks": {
|
|
21
|
+
"UserPromptSubmit": [
|
|
22
|
+
{
|
|
23
|
+
"matcher": ".*",
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"command": "python .claude/hooks/track-agent.py"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"PreToolUse": [
|
|
33
|
+
{
|
|
34
|
+
"matcher": "Write|Edit",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "python .claude/hooks/file-restriction-hook.py"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"SessionEnd": [
|
|
44
|
+
{
|
|
45
|
+
"matcher": ".*",
|
|
46
|
+
"hooks": [
|
|
47
|
+
{
|
|
48
|
+
"type": "command",
|
|
49
|
+
"command": "python .claude/hooks/cleanup-agent.py"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"disableAllHooks": false
|
|
20
56
|
}
|
package/package.json
CHANGED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
# Architecture Patterns & Design Decisions
|
|
2
|
-
|
|
3
|
-
## Frontend Architecture
|
|
4
|
-
|
|
5
|
-
### Architecture Style
|
|
6
|
-
- **Clean Architecture** + **Domain-Driven Design (DDD)**
|
|
7
|
-
|
|
8
|
-
### Folder Structure
|
|
9
|
-
|
|
10
|
-
Next.js 15 App Router Structure with Clean Architecture + DDD:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
├── app/ # Next.js App Router directory
|
|
14
|
-
│ ├── (dashboard)/ # Route groups for dashboard
|
|
15
|
-
│ ├── sales/ # Routes for sales module
|
|
16
|
-
│ │ ├── quotes/ # Quote management pages
|
|
17
|
-
│ │ └── invoices/ # Invoice pages
|
|
18
|
-
│ ├── inventory/ # Inventory routes
|
|
19
|
-
│ ├── globals.css # Global styles
|
|
20
|
-
│ ├── layout.tsx # Root layout component
|
|
21
|
-
│ ├── page.tsx # Home page
|
|
22
|
-
│ ├── loading.tsx # Global loading UI
|
|
23
|
-
│ └── not-found.tsx # 404 page
|
|
24
|
-
│
|
|
25
|
-
├── src/
|
|
26
|
-
│ ├── modules/ # Business modules following DDD
|
|
27
|
-
│ │ ├── sales/ # Sales module
|
|
28
|
-
│ │ │ ├── quotes/ # Quote domain
|
|
29
|
-
│ │ │ │ ├── cart/ # Shopping cart feature
|
|
30
|
-
│ │ │ │ │ ├── domain/
|
|
31
|
-
│ │ │ │ │ │ ├── entities/
|
|
32
|
-
│ │ │ │ │ │ ├── repositories/
|
|
33
|
-
│ │ │ │ │ │ ├── services/
|
|
34
|
-
│ │ │ │ │ │ └── types/
|
|
35
|
-
│ │ │ │ │ ├── application/
|
|
36
|
-
│ │ │ │ │ │ ├── use-cases/
|
|
37
|
-
│ │ │ │ │ │ ├── hooks/
|
|
38
|
-
│ │ │ │ │ │ └── store/
|
|
39
|
-
│ │ │ │ │ ├── infrastructure/
|
|
40
|
-
│ │ │ │ │ │ ├── repositories/
|
|
41
|
-
│ │ │ │ │ │ ├── api/
|
|
42
|
-
│ │ │ │ │ │ └── adapters/
|
|
43
|
-
│ │ │ │ │ └── presentation/
|
|
44
|
-
│ │ │ │ │ └── components/ # Only components, pages in app/
|
|
45
|
-
│ │ │ │ └── products/ # Products feature
|
|
46
|
-
│ │ │ └── billing/ # Billing domain
|
|
47
|
-
│ │ ├── inventory/ # Inventory module
|
|
48
|
-
│ │ └── users/ # User module
|
|
49
|
-
│ │
|
|
50
|
-
│ ├── shared/
|
|
51
|
-
│ │ ├── components/ # Reusable UI components
|
|
52
|
-
│ │ ├── hooks/ # Shared hooks
|
|
53
|
-
│ │ ├── utils/ # Utility functions
|
|
54
|
-
│ │ ├── types/ # Common TypeScript types
|
|
55
|
-
│ │ └── constants/ # App constants
|
|
56
|
-
│ │
|
|
57
|
-
│ ├── providers/ # React context providers
|
|
58
|
-
│ ├── store/ # Global Zustand stores
|
|
59
|
-
│ └── middleware.ts # Next.js middleware
|
|
60
|
-
│
|
|
61
|
-
├── lib/ # Next.js utilities and configurations
|
|
62
|
-
├── components/ # Global UI components (alternative to src/shared)
|
|
63
|
-
├── public/ # Static assets and PWA manifest
|
|
64
|
-
└── styles/ # Additional stylesheets
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Core Principles
|
|
68
|
-
|
|
69
|
-
#### Clean Architecture First
|
|
70
|
-
Strict separation of:
|
|
71
|
-
- **Domain layer** - Business entities, repositories interfaces, domain services, and types
|
|
72
|
-
- **Application layer** - Use cases, hooks, and state management (Zustand stores)
|
|
73
|
-
- **Infrastructure layer** - Repository implementations, API clients, and adapters
|
|
74
|
-
- **Presentation layer** - UI components (pages go in `app/`, components in `presentation/`)
|
|
75
|
-
|
|
76
|
-
#### Domain-Driven Design
|
|
77
|
-
Business logic drives architecture decisions. Organize by business modules and domains, not technical layers.
|
|
78
|
-
|
|
79
|
-
#### Component Composition
|
|
80
|
-
Build complex UIs from simple, reusable components.
|
|
81
|
-
|
|
82
|
-
#### Type Safety
|
|
83
|
-
Leverage TypeScript for compile-time safety and developer experience.
|
|
84
|
-
|
|
85
|
-
#### Performance by Design
|
|
86
|
-
- Lazy loading
|
|
87
|
-
- Memoization
|
|
88
|
-
- Bundle optimization
|
|
89
|
-
|
|
90
|
-
#### Accessibility as Standard
|
|
91
|
-
WCAG 2.1 AA compliance in all components.
|
|
92
|
-
|
|
93
|
-
#### Test-Driven Development
|
|
94
|
-
Unit tests for all use cases and components.
|
|
95
|
-
|
|
96
|
-
#### Progressive Web App
|
|
97
|
-
Offline-first approach with service workers.
|
|
98
|
-
|
|
99
|
-
#### Minimal and Functional
|
|
100
|
-
Only build what's explicitly requested, nothing more.
|
|
101
|
-
|
|
102
|
-
#### User-Centered Design
|
|
103
|
-
Start with user needs and work backward to implementation.
|
|
104
|
-
|
|
105
|
-
#### MCP Shadcn Available
|
|
106
|
-
Use MCP to install Shadcn components instead of creating manually.
|
|
107
|
-
|
|
108
|
-
### Framework Selection Rules
|
|
109
|
-
|
|
110
|
-
**Default**: Always use Next.js 15 with App Router unless explicitly told otherwise.
|
|
111
|
-
|
|
112
|
-
**Exceptions**: Only use pure React + Vite when user specifically mentions offline-first functionality or requests non-Next.js setup.
|
|
113
|
-
|
|
114
|
-
**Reasoning**: Next.js provides better developer experience, built-in optimization, and easier deployment while maintaining PWA capabilities.
|
|
115
|
-
|
|
116
|
-
## Backend Architecture
|
|
117
|
-
|
|
118
|
-
### Architecture Style
|
|
119
|
-
- **Hexagonal Architecture** (Ports & Adapters) + **Domain-Driven Design (DDD)**
|
|
120
|
-
|
|
121
|
-
### Folder Structure
|
|
122
|
-
|
|
123
|
-
MonoRepo Structure with Hexagonal Architecture + DDD:
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
├── apps/ # Microservices applications
|
|
127
|
-
│ ├── sales-service/ # Sales domain microservice
|
|
128
|
-
│ │ ├── src/
|
|
129
|
-
│ │ │ ├── modules/
|
|
130
|
-
│ │ │ │ ├── quotes/ # Quote bounded context
|
|
131
|
-
│ │ │ │ │ ├── application/
|
|
132
|
-
│ │ │ │ │ │ ├── ports/ # Interfaces (secondary ports)
|
|
133
|
-
│ │ │ │ │ │ │ ├── repositories/
|
|
134
|
-
│ │ │ │ │ │ │ └── services/
|
|
135
|
-
│ │ │ │ │ │ ├── use-cases/ # Primary ports
|
|
136
|
-
│ │ │ │ │ │ ├── commands/
|
|
137
|
-
│ │ │ │ │ │ ├── queries/
|
|
138
|
-
│ │ │ │ │ │ └── dto/
|
|
139
|
-
│ │ │ │ │ ├── domain/
|
|
140
|
-
│ │ │ │ │ │ ├── entities/
|
|
141
|
-
│ │ │ │ │ │ ├── value-objects/
|
|
142
|
-
│ │ │ │ │ │ ├── aggregates/
|
|
143
|
-
│ │ │ │ │ │ ├── events/
|
|
144
|
-
│ │ │ │ │ │ └── services/ # Domain services
|
|
145
|
-
│ │ │ │ │ └── infrastructure/ # Adapters (secondary adapters)
|
|
146
|
-
│ │ │ │ │ ├── repositories/ # Prisma implementations
|
|
147
|
-
│ │ │ │ │ ├── services/ # External service adapters
|
|
148
|
-
│ │ │ │ │ └── events/
|
|
149
|
-
│ │ │ │ └── products/ # Product bounded context
|
|
150
|
-
│ │ │ ├── api/ # Primary adapters
|
|
151
|
-
│ │ │ │ ├── controllers/
|
|
152
|
-
│ │ │ │ ├── guards/
|
|
153
|
-
│ │ │ │ ├── middlewares/
|
|
154
|
-
│ │ │ │ └── filters/
|
|
155
|
-
│ │ │ ├── config/
|
|
156
|
-
│ │ │ ├── main.ts
|
|
157
|
-
│ │ │ └── app.module.ts
|
|
158
|
-
│ │ ├── test/
|
|
159
|
-
│ │ ├── prisma/
|
|
160
|
-
│ │ │ ├── schema.prisma
|
|
161
|
-
│ │ │ └── migrations/
|
|
162
|
-
│ │ └── package.json
|
|
163
|
-
│ │
|
|
164
|
-
│ ├── inventory-service/ # Inventory domain microservice
|
|
165
|
-
│ └── user-service/ # User domain microservice
|
|
166
|
-
│
|
|
167
|
-
├── libs/ # Shared libraries
|
|
168
|
-
│ ├── common/ # Common utilities
|
|
169
|
-
│ │ ├── src/
|
|
170
|
-
│ │ │ ├── decorators/
|
|
171
|
-
│ │ │ ├── filters/
|
|
172
|
-
│ │ │ ├── guards/
|
|
173
|
-
│ │ │ ├── interceptors/
|
|
174
|
-
│ │ │ ├── pipes/
|
|
175
|
-
│ │ │ ├── types/
|
|
176
|
-
│ │ │ └── utils/
|
|
177
|
-
│ │ └── package.json
|
|
178
|
-
│ │
|
|
179
|
-
│ ├── domain-core/ # Shared domain concepts
|
|
180
|
-
│ │ ├── src/
|
|
181
|
-
│ │ │ ├── base/
|
|
182
|
-
│ │ │ │ ├── aggregate-root.ts
|
|
183
|
-
│ │ │ │ ├── entity.ts
|
|
184
|
-
│ │ │ │ ├── value-object.ts
|
|
185
|
-
│ │ │ │ └── domain-event.ts
|
|
186
|
-
│ │ │ ├── interfaces/
|
|
187
|
-
│ │ │ └── exceptions/
|
|
188
|
-
│ │ └── package.json
|
|
189
|
-
│ │
|
|
190
|
-
│ └── database/ # Shared database utilities
|
|
191
|
-
│ ├── src/
|
|
192
|
-
│ │ ├── base-repository.ts
|
|
193
|
-
│ │ ├── transaction.decorator.ts
|
|
194
|
-
│ │ └── prisma.service.ts
|
|
195
|
-
│ └── package.json
|
|
196
|
-
│
|
|
197
|
-
├── tools/ # Development tools
|
|
198
|
-
├── nx.json # Nx workspace configuration
|
|
199
|
-
├── package.json # Root package.json
|
|
200
|
-
└── tsconfig.base.json # Base TypeScript config
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Core Principles
|
|
204
|
-
|
|
205
|
-
#### Hexagonal Architecture First
|
|
206
|
-
Strict separation of concerns following ports & adapters pattern:
|
|
207
|
-
- **Domain Layer**: Pure business logic with entities, value objects, aggregates, and domain services
|
|
208
|
-
- **Application Layer**: Use cases orchestrating domain logic, defining ports (interfaces)
|
|
209
|
-
- **Infrastructure Layer**: Adapters implementing ports (Prisma repos, HTTP clients, message publishers)
|
|
210
|
-
- **API Layer**: Primary adapters exposing application via REST/GraphQL (controllers, resolvers)
|
|
211
|
-
|
|
212
|
-
#### Domain-Driven Design
|
|
213
|
-
Business logic drives all architectural decisions:
|
|
214
|
-
- **Bounded Contexts**: Each module represents a bounded context (quotes, products, billing)
|
|
215
|
-
- **Ubiquitous Language**: Code reflects business terminology
|
|
216
|
-
- **Aggregates**: Consistency boundaries for domain entities
|
|
217
|
-
- **Domain Events**: Communicate changes across bounded contexts
|
|
218
|
-
- **Repository Pattern**: Abstract data access behind interfaces
|
|
219
|
-
|
|
220
|
-
#### Dependency Rules
|
|
221
|
-
- Domain layer has zero dependencies on frameworks or external libraries
|
|
222
|
-
- All dependencies point inward toward the domain core
|
|
223
|
-
- Use dependency inversion for all external concerns (databases, APIs, messaging)
|
|
224
|
-
- Interfaces defined in application layer, implementations in infrastructure layer
|
|
225
|
-
|
|
226
|
-
#### Microservices Independence
|
|
227
|
-
- Each microservice has its own database (no shared databases)
|
|
228
|
-
- Shared code through libraries only (common, domain-core, database)
|
|
229
|
-
- Independent deployment pipelines per service
|
|
230
|
-
- Service-to-service communication via events (async messaging)
|
|
231
|
-
- No direct database access between services
|
|
232
|
-
|
|
233
|
-
#### Test-Driven Development
|
|
234
|
-
- Unit tests for domain entities, value objects, and use cases
|
|
235
|
-
- Integration tests for repository implementations
|
|
236
|
-
- E2E tests for complete API workflows
|
|
237
|
-
- TDD approach: write tests before implementation
|
|
238
|
-
|
|
239
|
-
#### Type Safety & Validation
|
|
240
|
-
- Leverage TypeScript strict mode for compile-time safety
|
|
241
|
-
- Domain validation in value objects and entities
|
|
242
|
-
- DTO validation at API boundaries with class-validator
|
|
243
|
-
- No `any` types allowed
|
|
244
|
-
|
|
245
|
-
#### Security by Design
|
|
246
|
-
- Authentication and authorization at every layer
|
|
247
|
-
- Input validation on all endpoints
|
|
248
|
-
- OWASP Top 10 compliance
|
|
249
|
-
- Audit logging for critical operations
|
|
250
|
-
|
|
251
|
-
### Framework Selection Rules
|
|
252
|
-
|
|
253
|
-
**Default**: Always use NestJS 10+ with TypeScript for backend services.
|
|
254
|
-
|
|
255
|
-
**Database**: Prisma ORM only - no raw SQL queries allowed.
|
|
256
|
-
|
|
257
|
-
**Testing**: Jest + Supertest with TDD approach.
|
|
258
|
-
|
|
259
|
-
**Documentation**: Swagger/OpenAPI auto-generated from decorators.
|
|
260
|
-
|
|
261
|
-
**Reasoning**: NestJS provides excellent DI container, decorator-based development, and native support for microservices patterns while enforcing SOLID principles.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Technology Stack
|
|
2
|
-
|
|
3
|
-
## Frontend Stack
|
|
4
|
-
|
|
5
|
-
### Framework
|
|
6
|
-
- **Next.js 15** with TypeScript (App Router)
|
|
7
|
-
- Default framework unless explicitly told otherwise
|
|
8
|
-
- Built-in Turbopack/Webpack for building
|
|
9
|
-
- File-based routing with App Router
|
|
10
|
-
- Exception: Use pure React + Vite only when user specifically mentions offline-first functionality or requests non-Next.js setup
|
|
11
|
-
|
|
12
|
-
### State Management
|
|
13
|
-
- **Zustand**
|
|
14
|
-
|
|
15
|
-
### UI Framework & Styling
|
|
16
|
-
- **Shadcn/ui** (component library)
|
|
17
|
-
- **Radix UI** (primitives)
|
|
18
|
-
- **TailwindCSS v4** (styling)
|
|
19
|
-
|
|
20
|
-
### Architecture
|
|
21
|
-
- **Clean Architecture** + **Domain-Driven Design (DDD)**
|
|
22
|
-
|
|
23
|
-
### Testing
|
|
24
|
-
- **Vitest** (test runner)
|
|
25
|
-
- **React Testing Library** (component testing)
|
|
26
|
-
- **MSW** (Mock Service Worker - API mocking)
|
|
27
|
-
|
|
28
|
-
### Forms & Validation
|
|
29
|
-
- **React Hook Form** (form management)
|
|
30
|
-
- **Zod** (schema validation)
|
|
31
|
-
|
|
32
|
-
### HTTP Client
|
|
33
|
-
- **Axios** with interceptors
|
|
34
|
-
|
|
35
|
-
### Progressive Web App (PWA)
|
|
36
|
-
- **Next.js PWA plugin**
|
|
37
|
-
- **Workbox** (service worker library)
|
|
38
|
-
|
|
39
|
-
### Routing
|
|
40
|
-
- **Next.js App Router** (file-based routing)
|
|
41
|
-
|
|
42
|
-
## Core Principles
|
|
43
|
-
|
|
44
|
-
### Clean Architecture First
|
|
45
|
-
Strict separation of:
|
|
46
|
-
- Domain layer
|
|
47
|
-
- Application layer
|
|
48
|
-
- Infrastructure layer
|
|
49
|
-
- Presentation layer
|
|
50
|
-
|
|
51
|
-
### Domain-Driven Design
|
|
52
|
-
Business logic drives architecture decisions
|
|
53
|
-
|
|
54
|
-
### Component Composition
|
|
55
|
-
Build complex UIs from simple, reusable components
|
|
56
|
-
|
|
57
|
-
### Type Safety
|
|
58
|
-
Leverage TypeScript for compile-time safety and developer experience
|
|
59
|
-
|
|
60
|
-
### Performance by Design
|
|
61
|
-
- Lazy loading
|
|
62
|
-
- Memoization
|
|
63
|
-
- Bundle optimization
|
|
64
|
-
|
|
65
|
-
### Accessibility as Standard
|
|
66
|
-
WCAG 2.1 AA compliance in all components
|
|
67
|
-
|
|
68
|
-
### Test-Driven Development
|
|
69
|
-
Unit tests for all use cases and components
|
|
70
|
-
|
|
71
|
-
### Progressive Web App
|
|
72
|
-
Offline-first approach with service workers
|
|
73
|
-
|
|
74
|
-
### Minimal and Functional
|
|
75
|
-
Only build what's explicitly requested, nothing more
|
|
76
|
-
|
|
77
|
-
### User-Centered Design
|
|
78
|
-
Start with user needs and work backward to implementation
|
|
79
|
-
|
|
80
|
-
### MCP Shadcn Available
|
|
81
|
-
Use MCP to install Shadcn components instead of creating manually
|