cad-workflow 1.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/bin/cli.js +529 -0
  4. package/bin/wrapper.js +32 -0
  5. package/config/install-config.yaml +167 -0
  6. package/package.json +42 -0
  7. package/src/base/.cad/config.yaml.tpl +25 -0
  8. package/src/base/.cad/workflow-status.yaml.tpl +18 -0
  9. package/src/base/.claude/settings.local.json.tpl +8 -0
  10. package/src/base/CLAUDE.md +69 -0
  11. package/src/base/commands/cad.md +547 -0
  12. package/src/base/commands/commit.md +103 -0
  13. package/src/base/commands/comprendre.md +96 -0
  14. package/src/base/commands/concevoir.md +121 -0
  15. package/src/base/commands/documenter.md +97 -0
  16. package/src/base/commands/e2e.md +79 -0
  17. package/src/base/commands/implementer.md +98 -0
  18. package/src/base/commands/review.md +85 -0
  19. package/src/base/commands/status.md +55 -0
  20. package/src/base/skills/clean-code/SKILL.md +92 -0
  21. package/src/base/skills/tdd/SKILL.md +132 -0
  22. package/src/integrations/jira/.mcp.json.tpl +19 -0
  23. package/src/integrations/jira/commands/jira-setup.md +34 -0
  24. package/src/stacks/backend-only/agents/backend-developer.md +167 -0
  25. package/src/stacks/backend-only/agents/backend-reviewer.md +89 -0
  26. package/src/stacks/backend-only/agents/orchestrator.md +69 -0
  27. package/src/stacks/backend-only/skills/clean-hexa-backend/SKILL.md +187 -0
  28. package/src/stacks/backend-only/skills/clean-hexa-backend/templates/adapter.template.ts +75 -0
  29. package/src/stacks/backend-only/skills/clean-hexa-backend/templates/controller.template.ts +131 -0
  30. package/src/stacks/backend-only/skills/clean-hexa-backend/templates/entity.template.ts +87 -0
  31. package/src/stacks/backend-only/skills/clean-hexa-backend/templates/port.template.ts +62 -0
  32. package/src/stacks/backend-only/skills/clean-hexa-backend/templates/use-case.template.ts +77 -0
  33. package/src/stacks/backend-only/skills/mutation-testing/SKILL.md +129 -0
  34. package/src/stacks/mobile/agents/backend-developer.md +167 -0
  35. package/src/stacks/mobile/agents/backend-reviewer.md +89 -0
  36. package/src/stacks/mobile/agents/mobile-developer.md +70 -0
  37. package/src/stacks/mobile/agents/mobile-reviewer.md +175 -0
  38. package/src/stacks/mobile/agents/orchestrator.md +69 -0
  39. package/src/stacks/mobile/skills/clean-hexa-backend/SKILL.md +187 -0
  40. package/src/stacks/mobile/skills/clean-hexa-backend/templates/adapter.template.ts +75 -0
  41. package/src/stacks/mobile/skills/clean-hexa-backend/templates/controller.template.ts +131 -0
  42. package/src/stacks/mobile/skills/clean-hexa-backend/templates/entity.template.ts +87 -0
  43. package/src/stacks/mobile/skills/clean-hexa-backend/templates/port.template.ts +62 -0
  44. package/src/stacks/mobile/skills/clean-hexa-backend/templates/use-case.template.ts +77 -0
  45. package/src/stacks/mobile/skills/clean-hexa-mobile/SKILL.md +984 -0
  46. package/src/stacks/mobile/skills/mutation-testing/SKILL.md +129 -0
  47. package/src/stacks/web/agents/backend-developer.md +167 -0
  48. package/src/stacks/web/agents/backend-reviewer.md +89 -0
  49. package/src/stacks/web/agents/frontend-developer.md +65 -0
  50. package/src/stacks/web/agents/frontend-reviewer.md +92 -0
  51. package/src/stacks/web/agents/orchestrator.md +69 -0
  52. package/src/stacks/web/skills/clean-hexa-backend/SKILL.md +187 -0
  53. package/src/stacks/web/skills/clean-hexa-backend/templates/adapter.template.ts +75 -0
  54. package/src/stacks/web/skills/clean-hexa-backend/templates/controller.template.ts +131 -0
  55. package/src/stacks/web/skills/clean-hexa-backend/templates/entity.template.ts +87 -0
  56. package/src/stacks/web/skills/clean-hexa-backend/templates/port.template.ts +62 -0
  57. package/src/stacks/web/skills/clean-hexa-backend/templates/use-case.template.ts +77 -0
  58. package/src/stacks/web/skills/clean-hexa-frontend/SKILL.md +172 -0
  59. package/src/stacks/web/skills/mutation-testing/SKILL.md +129 -0
@@ -0,0 +1,75 @@
1
+ // Template: TypeORM Repository Adapter (Backend)
2
+ // Location: src/infrastructure/persistence/repositories/typeorm-[name].repository.ts
3
+
4
+ import { Injectable } from '@nestjs/common';
5
+ import { InjectRepository } from '@nestjs/typeorm';
6
+ import { Repository } from 'typeorm';
7
+ // TODO: Import port interface from domain
8
+ // TODO: Import domain entity
9
+ // TODO: Import ORM entity
10
+ // TODO: Import persistence mapper
11
+
12
+ /**
13
+ * TypeORM Repository Adapter Template
14
+ *
15
+ * Rules:
16
+ * - Implements domain port interface
17
+ * - Uses TypeORM for persistence
18
+ * - Maps between ORM entities and domain entities
19
+ * - Handles database-specific concerns
20
+ */
21
+
22
+ @Injectable()
23
+ export class TypeOrm[Name]Repository implements I[Name]Repository {
24
+ constructor(
25
+ @InjectRepository([Name]OrmEntity)
26
+ private readonly repository: Repository<[Name]OrmEntity>,
27
+ ) {}
28
+
29
+ async findById(id: string): Promise<[DomainEntity] | null> {
30
+ const ormEntity = await this.repository.findOne({ where: { id } });
31
+
32
+ if (!ormEntity) {
33
+ return null;
34
+ }
35
+
36
+ return [Name]PersistenceMapper.toDomain(ormEntity);
37
+ }
38
+
39
+ async findAll(options?: { skip?: number; take?: number }): Promise<[DomainEntity][]> {
40
+ const ormEntities = await this.repository.find({
41
+ skip: options?.skip,
42
+ take: options?.take,
43
+ order: { createdAt: 'DESC' },
44
+ });
45
+
46
+ return ormEntities.map([Name]PersistenceMapper.toDomain);
47
+ }
48
+
49
+ async findBy(criteria: Partial<[DomainEntity]>): Promise<[DomainEntity][]> {
50
+ const ormEntities = await this.repository.find({
51
+ where: this.mapCriteriaToOrm(criteria),
52
+ });
53
+
54
+ return ormEntities.map([Name]PersistenceMapper.toDomain);
55
+ }
56
+
57
+ async save(entity: [DomainEntity]): Promise<void> {
58
+ const ormEntity = [Name]PersistenceMapper.toOrm(entity);
59
+ await this.repository.save(ormEntity);
60
+ }
61
+
62
+ async delete(id: string): Promise<void> {
63
+ await this.repository.delete(id);
64
+ }
65
+
66
+ async exists(id: string): Promise<boolean> {
67
+ const count = await this.repository.count({ where: { id } });
68
+ return count > 0;
69
+ }
70
+
71
+ private mapCriteriaToOrm(criteria: Partial<[DomainEntity]>): Partial<[Name]OrmEntity> {
72
+ // TODO: Map domain criteria to ORM criteria
73
+ return {};
74
+ }
75
+ }
@@ -0,0 +1,131 @@
1
+ // Template: Controller (Backend)
2
+ // Location: src/presentation/controllers/[name].controller.ts
3
+
4
+ import {
5
+ Controller,
6
+ Get,
7
+ Post,
8
+ Put,
9
+ Delete,
10
+ Body,
11
+ Param,
12
+ Query,
13
+ HttpCode,
14
+ HttpStatus,
15
+ UseGuards,
16
+ ParseUUIDPipe,
17
+ } from '@nestjs/common';
18
+ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
19
+ // TODO: Import use cases
20
+ // TODO: Import DTOs
21
+ // TODO: Import guards if needed
22
+
23
+ /**
24
+ * Controller Template
25
+ *
26
+ * Rules:
27
+ * - Thin controller (no business logic)
28
+ * - Delegate to use cases
29
+ * - Handle HTTP concerns (status codes, validation)
30
+ * - Document with Swagger decorators
31
+ */
32
+
33
+ // Request DTOs
34
+ export class Create[Name]RequestDto {
35
+ // TODO: Add properties with class-validator decorators
36
+ // @IsString()
37
+ // @IsNotEmpty()
38
+ // name: string;
39
+ }
40
+
41
+ export class Update[Name]RequestDto {
42
+ // TODO: Add properties
43
+ }
44
+
45
+ // Response DTOs
46
+ export class [Name]ResponseDto {
47
+ id: string;
48
+ // TODO: Add properties
49
+ createdAt: Date;
50
+ }
51
+
52
+ @ApiTags('[names]')
53
+ @Controller('[names]')
54
+ export class [Name]Controller {
55
+ constructor(
56
+ private readonly create[Name]UseCase: Create[Name]UseCase,
57
+ private readonly get[Name]UseCase: Get[Name]UseCase,
58
+ private readonly update[Name]UseCase: Update[Name]UseCase,
59
+ private readonly delete[Name]UseCase: Delete[Name]UseCase,
60
+ ) {}
61
+
62
+ @Post()
63
+ @HttpCode(HttpStatus.CREATED)
64
+ @ApiOperation({ summary: 'Create a new [name]' })
65
+ @ApiResponse({ status: 201, type: [Name]ResponseDto })
66
+ async create(@Body() dto: Create[Name]RequestDto): Promise<[Name]ResponseDto> {
67
+ const result = await this.create[Name]UseCase.execute({
68
+ // Map DTO to command
69
+ });
70
+
71
+ return this.toResponseDto(result);
72
+ }
73
+
74
+ @Get(':id')
75
+ @ApiOperation({ summary: 'Get [name] by ID' })
76
+ @ApiResponse({ status: 200, type: [Name]ResponseDto })
77
+ @ApiResponse({ status: 404, description: '[Name] not found' })
78
+ async findOne(
79
+ @Param('id', ParseUUIDPipe) id: string,
80
+ ): Promise<[Name]ResponseDto> {
81
+ const result = await this.get[Name]UseCase.execute({ id });
82
+
83
+ if (!result) {
84
+ // Throw NotFoundException or let exception filter handle
85
+ }
86
+
87
+ return this.toResponseDto(result);
88
+ }
89
+
90
+ @Get()
91
+ @ApiOperation({ summary: 'List all [names]' })
92
+ @ApiResponse({ status: 200, type: [Name]ResponseDto, isArray: true })
93
+ async findAll(
94
+ @Query('skip') skip?: number,
95
+ @Query('take') take?: number,
96
+ ): Promise<[Name]ResponseDto[]> {
97
+ const results = await this.list[Names]UseCase.execute({ skip, take });
98
+ return results.map(this.toResponseDto);
99
+ }
100
+
101
+ @Put(':id')
102
+ @ApiOperation({ summary: 'Update [name]' })
103
+ @ApiResponse({ status: 200, type: [Name]ResponseDto })
104
+ async update(
105
+ @Param('id', ParseUUIDPipe) id: string,
106
+ @Body() dto: Update[Name]RequestDto,
107
+ ): Promise<[Name]ResponseDto> {
108
+ const result = await this.update[Name]UseCase.execute({
109
+ id,
110
+ // Map DTO to command
111
+ });
112
+
113
+ return this.toResponseDto(result);
114
+ }
115
+
116
+ @Delete(':id')
117
+ @HttpCode(HttpStatus.NO_CONTENT)
118
+ @ApiOperation({ summary: 'Delete [name]' })
119
+ @ApiResponse({ status: 204 })
120
+ async delete(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
121
+ await this.delete[Name]UseCase.execute({ id });
122
+ }
123
+
124
+ private toResponseDto(entity: any): [Name]ResponseDto {
125
+ // TODO: Map entity to response DTO
126
+ return {
127
+ id: entity.id,
128
+ createdAt: entity.createdAt,
129
+ };
130
+ }
131
+ }
@@ -0,0 +1,87 @@
1
+ // Template: Domain Entity (Backend)
2
+ // Location: src/domain/entities/[name].entity.ts
3
+
4
+ /**
5
+ * Domain Entity Template
6
+ *
7
+ * Rules:
8
+ * - Immutable where possible
9
+ * - No NestJS/TypeORM imports
10
+ * - Private constructor + static factory methods
11
+ * - Business logic encapsulated
12
+ * - Separate from ORM entities
13
+ */
14
+
15
+ // TODO: Import value objects
16
+ // TODO: Import domain errors
17
+
18
+ export class [EntityName] {
19
+ private constructor(
20
+ public readonly id: string,
21
+ public readonly createdAt: Date,
22
+ public readonly updatedAt: Date,
23
+ // TODO: Add entity properties
24
+ ) {}
25
+
26
+ /**
27
+ * Factory method for creating new entities
28
+ */
29
+ static create(/* params */): [EntityName] {
30
+ // TODO: Add validation logic
31
+ const now = new Date();
32
+ return new [EntityName](
33
+ this.generateId(),
34
+ now,
35
+ now,
36
+ // ... properties
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Factory method for reconstituting from persistence
42
+ */
43
+ static reconstitute(props: {
44
+ id: string;
45
+ createdAt: Date;
46
+ updatedAt: Date;
47
+ // TODO: Add properties
48
+ }): [EntityName] {
49
+ return new [EntityName](
50
+ props.id,
51
+ props.createdAt,
52
+ props.updatedAt,
53
+ // ... properties
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Business methods that return new instances (immutability)
59
+ */
60
+ // TODO: Add domain behavior methods
61
+ // Example:
62
+ // confirm(): [EntityName] {
63
+ // if (this.status !== Status.PENDING) {
64
+ // throw new InvalidStateError('Can only confirm pending orders');
65
+ // }
66
+ // return new [EntityName](
67
+ // this.id,
68
+ // this.createdAt,
69
+ // new Date(),
70
+ // Status.CONFIRMED,
71
+ // );
72
+ // }
73
+
74
+ /**
75
+ * Domain validation
76
+ */
77
+ // private validate(): void {
78
+ // if (!this.someProperty) {
79
+ // throw new ValidationError('Property is required');
80
+ // }
81
+ // }
82
+
83
+ private static generateId(): string {
84
+ // Use UUID v4 or similar
85
+ return require('crypto').randomUUID();
86
+ }
87
+ }
@@ -0,0 +1,62 @@
1
+ // Template: Port Interface (Backend)
2
+ // Location: src/domain/ports/repositories/[name].repository.port.ts
3
+
4
+ // TODO: Import entity from domain
5
+
6
+ /**
7
+ * Port Interface Template
8
+ *
9
+ * Rules:
10
+ * - Define contract, not implementation
11
+ * - Use domain types only (no TypeORM, no Prisma)
12
+ * - Export Symbol for NestJS DI
13
+ * - Async methods return Promise
14
+ */
15
+
16
+ // Injection token for NestJS DI
17
+ export const [NAME]_REPOSITORY = Symbol('[NAME]_REPOSITORY');
18
+
19
+ /**
20
+ * Repository port for [Name] aggregate
21
+ */
22
+ export interface I[Name]Repository {
23
+ /**
24
+ * Find entity by ID
25
+ * @returns Promise resolving to entity or null if not found
26
+ */
27
+ findById(id: string): Promise<[Entity] | null>;
28
+
29
+ /**
30
+ * Find all entities with optional pagination
31
+ */
32
+ findAll(options?: {
33
+ skip?: number;
34
+ take?: number;
35
+ }): Promise<[Entity][]>;
36
+
37
+ /**
38
+ * Find entities matching criteria
39
+ */
40
+ findBy(criteria: Partial<[Entity]>): Promise<[Entity][]>;
41
+
42
+ /**
43
+ * Save entity (create or update)
44
+ */
45
+ save(entity: [Entity]): Promise<void>;
46
+
47
+ /**
48
+ * Delete entity by ID
49
+ */
50
+ delete(id: string): Promise<void>;
51
+
52
+ /**
53
+ * Check if entity exists
54
+ */
55
+ exists(id: string): Promise<boolean>;
56
+
57
+ // TODO: Add domain-specific query methods
58
+ // Example:
59
+ // findByStatus(status: EntityStatus): Promise<[Entity][]>;
60
+ // findByUserId(userId: string): Promise<[Entity][]>;
61
+ // countByStatus(status: EntityStatus): Promise<number>;
62
+ }
@@ -0,0 +1,77 @@
1
+ // Template: Use Case (Backend)
2
+ // Location: src/application/use-cases/[name]/[name].use-case.ts
3
+
4
+ import { Injectable, Inject } from '@nestjs/common';
5
+ // TODO: Import port symbol and interface from domain
6
+ // TODO: Import entity from domain
7
+ // TODO: Import domain errors
8
+
9
+ /**
10
+ * Use Case Template
11
+ *
12
+ * Rules:
13
+ * - One class = One business action
14
+ * - Single execute() method
15
+ * - Inject ports via Symbol tokens
16
+ * - Return Result<T> or throw domain errors
17
+ */
18
+
19
+ // Command/Input DTO
20
+ export class [Name]Command {
21
+ constructor(
22
+ // TODO: Define command properties
23
+ public readonly userId: string,
24
+ ) {}
25
+ }
26
+
27
+ // Result DTO
28
+ export interface [Name]Result {
29
+ // TODO: Define result properties
30
+ id: string;
31
+ success: boolean;
32
+ }
33
+
34
+ @Injectable()
35
+ export class [Name]UseCase {
36
+ constructor(
37
+ @Inject([REPOSITORY]_REPOSITORY)
38
+ private readonly repository: I[Repository]Repository,
39
+ // TODO: Inject other ports if needed
40
+ // @Inject(PAYMENT_SERVICE)
41
+ // private readonly paymentService: IPaymentService,
42
+ ) {}
43
+
44
+ /**
45
+ * Execute the use case
46
+ * @throws DomainError on business rule violation
47
+ */
48
+ async execute(command: [Name]Command): Promise<[Name]Result> {
49
+ // 1. Validate command
50
+ this.validateCommand(command);
51
+
52
+ // 2. Load required aggregates
53
+ // const entity = await this.repository.findById(command.entityId);
54
+ // if (!entity) {
55
+ // throw new EntityNotFoundError(command.entityId);
56
+ // }
57
+
58
+ // 3. Execute domain logic
59
+ // const updatedEntity = entity.doSomething();
60
+
61
+ // 4. Persist changes
62
+ // await this.repository.save(updatedEntity);
63
+
64
+ // 5. Return result
65
+ return {
66
+ id: 'generated-id',
67
+ success: true,
68
+ };
69
+ }
70
+
71
+ private validateCommand(command: [Name]Command): void {
72
+ // TODO: Add validation logic
73
+ // if (!command.userId) {
74
+ // throw new ValidationError('userId is required');
75
+ // }
76
+ }
77
+ }
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: mutation-testing
3
+ description: Mutation testing avec Stryker pour valider la qualite des tests.
4
+ ---
5
+
6
+ # Skill Mutation Testing
7
+
8
+ Le mutation testing valide que vos tests detectent vraiment les bugs.
9
+
10
+ ## Principe
11
+
12
+ 1. Stryker modifie (mute) votre code de production
13
+ 2. Execute vos tests sur le code mute
14
+ 3. Si les tests passent = mutant survit = test insuffisant
15
+ 4. Si les tests echouent = mutant tue = test efficace
16
+
17
+ ## Types de mutations
18
+
19
+ ### Mutations arithmetiques
20
+ ```typescript
21
+ // Original
22
+ const total = price * quantity;
23
+
24
+ // Mutant
25
+ const total = price / quantity; // * -> /
26
+ const total = price + quantity; // * -> +
27
+ ```
28
+
29
+ ### Mutations conditionnelles
30
+ ```typescript
31
+ // Original
32
+ if (age >= 18)
33
+
34
+ // Mutants
35
+ if (age > 18) // >= -> >
36
+ if (age <= 18) // >= -> <=
37
+ if (true) // condition -> true
38
+ if (false) // condition -> false
39
+ ```
40
+
41
+ ### Mutations de blocs
42
+ ```typescript
43
+ // Original
44
+ if (condition) {
45
+ doSomething();
46
+ }
47
+
48
+ // Mutant
49
+ if (condition) {
50
+ // Block removed
51
+ }
52
+ ```
53
+
54
+ ## Configuration Stryker
55
+
56
+ ```json
57
+ // stryker.conf.json
58
+ {
59
+ "mutate": [
60
+ "src/**/*.ts",
61
+ "!src/**/*.spec.ts",
62
+ "!src/**/*.module.ts"
63
+ ],
64
+ "testRunner": "jest",
65
+ "reporters": ["html", "clear-text", "progress"],
66
+ "thresholds": {
67
+ "high": 80,
68
+ "low": 60,
69
+ "break": 75
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Interpretation du score
75
+
76
+ - **Score > 80%** : Excellent, tests robustes
77
+ - **Score 60-80%** : Acceptable, ameliorations possibles
78
+ - **Score < 60%** : Tests insuffisants
79
+
80
+ ## Mutants survivants
81
+
82
+ Quand un mutant survit, analyser :
83
+
84
+ 1. **Test manquant** : Ajouter un test pour ce cas
85
+ 2. **Code mort** : Le code n'est pas necessaire
86
+ 3. **Test trop general** : Affiner les assertions
87
+
88
+ ### Exemple
89
+ ```typescript
90
+ // Code
91
+ function calculateDiscount(price: number, isPremium: boolean): number {
92
+ if (isPremium) {
93
+ return price * 0.9; // 10% discount
94
+ }
95
+ return price;
96
+ }
97
+
98
+ // Test insuffisant
99
+ it('should apply discount for premium', () => {
100
+ expect(calculateDiscount(100, true)).toBeLessThan(100);
101
+ // Mutant survit: price * 0.8 passe aussi!
102
+ });
103
+
104
+ // Test robuste
105
+ it('should apply 10% discount for premium', () => {
106
+ expect(calculateDiscount(100, true)).toBe(90);
107
+ // Mutant tue: seul 0.9 donne 90
108
+ });
109
+ ```
110
+
111
+ ## Commandes
112
+
113
+ ```bash
114
+ # Lancer mutation testing complet
115
+ npm run stryker
116
+
117
+ # Sur fichiers specifiques
118
+ npx stryker run --mutate "src/application/use-cases/**/*.ts"
119
+
120
+ # Rapport HTML
121
+ open reports/mutation/html/index.html
122
+ ```
123
+
124
+ ## Integration workflow
125
+
126
+ Phase 4 (Review) inclut obligatoirement :
127
+ 1. Score mutation >= 75%
128
+ 2. Analyse des mutants survivants
129
+ 3. Ajout de tests si necessaire