siesa-agents 2.1.23 → 2.1.24-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 +22 -119
- package/bmad-core/agents/architect.md +0 -2
- package/bmad-core/agents/dev.md +2 -0
- package/bmad-core/agents/ux-expert.md +0 -1
- package/bmad-core/checklists/story-dod-checklist.md +1 -0
- package/bmad-core/data/backend-standards.md +195 -127
- package/bmad-core/data/frontend-standards.md +37 -1
- 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/kiro/README.md +13 -0
- package/kiro/steering/agent-detection.md +46 -0
- package/package.json +2 -1
- package/bmad-core/data/architecture-patterns.md +0 -261
- package/bmad-core/data/technology-stack.md +0 -81
|
@@ -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
|
|
@@ -335,4 +335,40 @@ import { Button } from '@/components/ui/button';
|
|
|
335
335
|
- Cache-first for static assets
|
|
336
336
|
- Network-first for dynamic data
|
|
337
337
|
- Fallback pages for offline scenarios
|
|
338
|
-
- Sync when connection restored
|
|
338
|
+
- Sync when connection restored
|
|
339
|
+
|
|
340
|
+
## Language Standards (Frontend & Backend)
|
|
341
|
+
|
|
342
|
+
### Spanish for All User-Facing Content (CRITICAL RULE)
|
|
343
|
+
|
|
344
|
+
**MANDATORY: All text visible to end users MUST be in Spanish.**
|
|
345
|
+
|
|
346
|
+
#### ✅ Spanish Required:
|
|
347
|
+
- UI labels, buttons, forms, messages, notifications
|
|
348
|
+
- API responses, validation errors, email templates
|
|
349
|
+
- Any text the user sees (frontend or backend)
|
|
350
|
+
|
|
351
|
+
#### ✅ English Required:
|
|
352
|
+
- Code (variables, functions, classes)
|
|
353
|
+
- Technical logs, comments, git commits
|
|
354
|
+
- Developer documentation
|
|
355
|
+
|
|
356
|
+
#### ❌ Never mix languages in user-facing text
|
|
357
|
+
|
|
358
|
+
**Examples:**
|
|
359
|
+
```typescript
|
|
360
|
+
// ✅ CORRECT
|
|
361
|
+
<Button>Guardar</Button>
|
|
362
|
+
toast.success("Datos guardados correctamente");
|
|
363
|
+
throw new BadRequestException('No se pudo crear el usuario');
|
|
364
|
+
|
|
365
|
+
// ❌ INCORRECT
|
|
366
|
+
<Button>Save cambios</Button>
|
|
367
|
+
toast.error("Failed al guardar");
|
|
368
|
+
throw new BadRequestException('Invalid datos proporcionados');
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Implementation:**
|
|
372
|
+
- Create message constants in Spanish
|
|
373
|
+
- Validate all user-facing text before story completion
|
|
374
|
+
- Technical logs stay in English, user messages in Spanish
|
|
@@ -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/kiro/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Kiro Configuration
|
|
2
|
+
|
|
3
|
+
Esta carpeta contiene las configuraciones y specs para Kiro IDE.
|
|
4
|
+
|
|
5
|
+
## Estructura
|
|
6
|
+
|
|
7
|
+
- **specs/** - Especificaciones de features (requirements, design, tasks)
|
|
8
|
+
- **steering/** - Reglas y contexto adicional para el agente
|
|
9
|
+
- **settings/** - Configuraciones de Kiro (MCP, etc.)
|
|
10
|
+
|
|
11
|
+
## Uso
|
|
12
|
+
|
|
13
|
+
Las specs siguen el workflow: Requirements → Design → Tasks → Implementation
|