@the-bearded-bear/claude-craft 8.18.2 → 8.19.0-next.2d709ba

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 (137) hide show
  1. package/Dev/i18n/base/Angular/rules/00-project-context.md.template +1 -1
  2. package/Dev/i18n/base/Angular/rules/06-tooling.md +34 -24
  3. package/Dev/i18n/base/Angular/rules/07-testing-angular.md +63 -51
  4. package/Dev/i18n/base/Angular/rules/08-quality-tools.md +7 -8
  5. package/Dev/i18n/base/CSharp/rules/06-tooling.md +4 -0
  6. package/Dev/i18n/base/CSharp/rules/07-testing-csharp.md +60 -8
  7. package/Dev/i18n/base/CSharp/rules/12-aspire-cloud-native.md +5 -4
  8. package/Dev/i18n/base/Common/skills/dynamic-workflows/SKILL.md +72 -0
  9. package/Dev/i18n/base/PHP/commands/check-code-quality.md +1 -1
  10. package/Dev/i18n/base/VueJS/rules/06-tooling.md +15 -17
  11. package/Dev/i18n/base/VueJS/rules/08-quality-tools.md +12 -9
  12. package/Dev/i18n/de/Flutter/rules/02-architecture.md +4 -4
  13. package/Dev/i18n/de/Flutter/rules/08-quality-tools.md +1 -1
  14. package/Dev/i18n/de/PHP/agents/php-reviewer.md +1 -1
  15. package/Dev/i18n/de/PHP/commands/check-compliance.md +1 -1
  16. package/Dev/i18n/de/PHP/rules/06-tooling.md +1 -1
  17. package/Dev/i18n/de/PHP/rules/08-quality-tools.md +1 -1
  18. package/Dev/i18n/de/PHP/rules/11-security-php.md +24 -15
  19. package/Dev/i18n/de/Paperclip/CLAUDE.md.template +8 -4
  20. package/Dev/i18n/de/Paperclip/README.md +2 -2
  21. package/Dev/i18n/de/Paperclip/commands/generate-agent-config.md +2 -2
  22. package/Dev/i18n/de/Paperclip/commands/setup-company.md +3 -3
  23. package/Dev/i18n/de/Paperclip/rules/02-architecture-paperclip.md +1 -1
  24. package/Dev/i18n/de/Paperclip/rules/12-adapter-protocol.md +1 -1
  25. package/Dev/i18n/de/Python/rules/11-security-python.md +11 -7
  26. package/Dev/i18n/de/React/commands/bundle-analyze.md +2 -2
  27. package/Dev/i18n/de/React/commands/check-testing.md +1 -1
  28. package/Dev/i18n/de/React/rules/02-architecture.md +1 -1
  29. package/Dev/i18n/de/React/rules/06-tooling.md +2 -2
  30. package/Dev/i18n/de/React/rules/07-testing-react.md +1 -1
  31. package/Dev/i18n/de/React/rules/11-security-react.md +1 -1
  32. package/Dev/i18n/de/ReactNative/rules/06-tooling.md +7 -5
  33. package/Dev/i18n/de/ReactNative/rules/13-state-management.md +3 -2
  34. package/Dev/i18n/de/Symfony/rules/11-security-symfony.md +22 -6
  35. package/Dev/i18n/en/Flutter/rules/02-architecture.md +4 -4
  36. package/Dev/i18n/en/Flutter/rules/08-quality-tools.md +1 -1
  37. package/Dev/i18n/en/PHP/agents/php-reviewer.md +1 -1
  38. package/Dev/i18n/en/PHP/rules/06-tooling.md +1 -1
  39. package/Dev/i18n/en/PHP/rules/08-quality-tools.md +1 -1
  40. package/Dev/i18n/en/PHP/rules/11-security-php.md +24 -15
  41. package/Dev/i18n/en/Paperclip/CLAUDE.md.template +8 -4
  42. package/Dev/i18n/en/Paperclip/README.md +2 -2
  43. package/Dev/i18n/en/Paperclip/commands/generate-agent-config.md +2 -2
  44. package/Dev/i18n/en/Paperclip/commands/setup-company.md +3 -3
  45. package/Dev/i18n/en/Paperclip/rules/02-architecture-paperclip.md +1 -1
  46. package/Dev/i18n/en/Paperclip/rules/12-adapter-protocol.md +1 -1
  47. package/Dev/i18n/en/Python/rules/02-architecture.md +5 -5
  48. package/Dev/i18n/en/Python/rules/11-security-python.md +31 -24
  49. package/Dev/i18n/en/React/commands/bundle-analyze.md +2 -2
  50. package/Dev/i18n/en/React/commands/check-testing.md +1 -1
  51. package/Dev/i18n/en/React/rules/02-architecture.md +1 -1
  52. package/Dev/i18n/en/React/rules/06-tooling.md +2 -2
  53. package/Dev/i18n/en/React/rules/07-testing-react.md +1 -1
  54. package/Dev/i18n/en/React/rules/11-security-react.md +1 -1
  55. package/Dev/i18n/en/ReactNative/rules/06-tooling.md +7 -5
  56. package/Dev/i18n/en/ReactNative/rules/13-state-management.md +25 -2
  57. package/Dev/i18n/en/Symfony/rules/11-security-symfony.md +22 -6
  58. package/Dev/i18n/es/Flutter/rules/02-architecture.md +4 -4
  59. package/Dev/i18n/es/Flutter/rules/08-quality-tools.md +1 -1
  60. package/Dev/i18n/es/PHP/agents/php-reviewer.md +1 -1
  61. package/Dev/i18n/es/PHP/commands/check-compliance.md +1 -1
  62. package/Dev/i18n/es/PHP/rules/06-tooling.md +1 -1
  63. package/Dev/i18n/es/PHP/rules/08-quality-tools.md +1 -1
  64. package/Dev/i18n/es/PHP/rules/11-security-php.md +24 -15
  65. package/Dev/i18n/es/Paperclip/CLAUDE.md.template +8 -4
  66. package/Dev/i18n/es/Paperclip/README.md +2 -2
  67. package/Dev/i18n/es/Paperclip/commands/generate-agent-config.md +2 -2
  68. package/Dev/i18n/es/Paperclip/commands/setup-company.md +3 -3
  69. package/Dev/i18n/es/Paperclip/rules/02-architecture-paperclip.md +1 -1
  70. package/Dev/i18n/es/Paperclip/rules/12-adapter-protocol.md +1 -1
  71. package/Dev/i18n/es/Python/rules/11-security-python.md +11 -7
  72. package/Dev/i18n/es/React/commands/bundle-analyze.md +2 -2
  73. package/Dev/i18n/es/React/commands/check-security.md +1 -1
  74. package/Dev/i18n/es/React/commands/check-testing.md +1 -1
  75. package/Dev/i18n/es/React/rules/02-architecture.md +1 -1
  76. package/Dev/i18n/es/React/rules/06-tooling.md +2 -2
  77. package/Dev/i18n/es/React/rules/07-testing-react.md +1 -1
  78. package/Dev/i18n/es/React/rules/11-security-react.md +1 -1
  79. package/Dev/i18n/es/ReactNative/rules/06-tooling.md +7 -5
  80. package/Dev/i18n/es/ReactNative/rules/13-state-management.md +3 -2
  81. package/Dev/i18n/es/Symfony/rules/11-security-symfony.md +22 -6
  82. package/Dev/i18n/fr/Flutter/rules/02-architecture.md +6 -6
  83. package/Dev/i18n/fr/Flutter/rules/08-quality-tools.md +1 -1
  84. package/Dev/i18n/fr/PHP/agents/php-reviewer.md +1 -1
  85. package/Dev/i18n/fr/PHP/commands/check-compliance.md +1 -1
  86. package/Dev/i18n/fr/PHP/rules/06-tooling.md +1 -1
  87. package/Dev/i18n/fr/PHP/rules/08-quality-tools.md +1 -1
  88. package/Dev/i18n/fr/Paperclip/CLAUDE.md.template +2 -2
  89. package/Dev/i18n/fr/Paperclip/README.md +2 -2
  90. package/Dev/i18n/fr/Paperclip/commands/generate-agent-config.md +2 -2
  91. package/Dev/i18n/fr/Paperclip/commands/setup-company.md +3 -3
  92. package/Dev/i18n/fr/Paperclip/rules/02-architecture-paperclip.md +1 -1
  93. package/Dev/i18n/fr/Paperclip/rules/12-adapter-protocol.md +1 -1
  94. package/Dev/i18n/fr/Python/rules/02-architecture.md +5 -5
  95. package/Dev/i18n/fr/Python/rules/11-security-python.md +31 -24
  96. package/Dev/i18n/fr/React/commands/check-testing.md +1 -1
  97. package/Dev/i18n/fr/React/rules/02-architecture.md +1 -1
  98. package/Dev/i18n/fr/React/rules/06-tooling.md +2 -2
  99. package/Dev/i18n/fr/React/rules/07-testing-react.md +1 -1
  100. package/Dev/i18n/fr/React/rules/11-security-react.md +1 -1
  101. package/Dev/i18n/fr/ReactNative/rules/06-tooling.md +7 -5
  102. package/Dev/i18n/fr/ReactNative/rules/13-state-management.md +3 -2
  103. package/Dev/i18n/fr/Symfony/rules/11-security-symfony.md +22 -6
  104. package/Dev/i18n/pt/Flutter/rules/08-quality-tools.md +1 -1
  105. package/Dev/i18n/pt/PHP/agents/php-reviewer.md +1 -1
  106. package/Dev/i18n/pt/PHP/commands/check-compliance.md +1 -1
  107. package/Dev/i18n/pt/PHP/rules/06-tooling.md +1 -1
  108. package/Dev/i18n/pt/PHP/rules/08-quality-tools.md +1 -1
  109. package/Dev/i18n/pt/PHP/rules/11-security-php.md +24 -15
  110. package/Dev/i18n/pt/Paperclip/CLAUDE.md.template +8 -4
  111. package/Dev/i18n/pt/Paperclip/README.md +2 -2
  112. package/Dev/i18n/pt/Paperclip/commands/generate-agent-config.md +2 -2
  113. package/Dev/i18n/pt/Paperclip/commands/setup-company.md +3 -3
  114. package/Dev/i18n/pt/Paperclip/rules/02-architecture-paperclip.md +1 -1
  115. package/Dev/i18n/pt/Paperclip/rules/12-adapter-protocol.md +1 -1
  116. package/Dev/i18n/pt/Python/rules/11-security-python.md +31 -24
  117. package/Dev/i18n/pt/React/commands/bundle-analyze.md +2 -2
  118. package/Dev/i18n/pt/React/commands/check-testing.md +1 -1
  119. package/Dev/i18n/pt/React/rules/02-architecture.md +1 -1
  120. package/Dev/i18n/pt/React/rules/06-tooling.md +2 -2
  121. package/Dev/i18n/pt/React/rules/07-testing-react.md +1 -1
  122. package/Dev/i18n/pt/React/rules/11-security-react.md +1 -1
  123. package/Dev/i18n/pt/ReactNative/rules/06-tooling.md +7 -5
  124. package/Dev/i18n/pt/ReactNative/rules/13-state-management.md +3 -2
  125. package/Dev/i18n/pt/Symfony/rules/11-security-symfony.md +22 -6
  126. package/README.md +5 -5
  127. package/bundles/cursor/.cursorrules +5 -5
  128. package/bundles/windsurf/.windsurfrules +5 -5
  129. package/cli/kanban/client/dist/assets/{BurndownView-k0Da2Sz-.js → BurndownView-BxC56Ur9.js} +2 -2
  130. package/cli/kanban/client/dist/assets/{DepsView-Cp0MVMVy.js → DepsView-CA50ivBK.js} +1 -1
  131. package/cli/kanban/client/dist/assets/{DocsView-Bduxxrv0.js → DocsView-CF2Qkapy.js} +1 -1
  132. package/cli/kanban/client/dist/assets/{SprintsView-C7qdhg6F.js → SprintsView-96Z-d962.js} +1 -1
  133. package/cli/kanban/client/dist/assets/{cytoscape.esm-BQ_ULxwV.js → cytoscape.esm-Orb7NpID.js} +2 -2
  134. package/cli/kanban/client/dist/assets/{index-D8HBRPRV.js → index-CXWHm5hf.js} +4 -4
  135. package/cli/kanban/client/dist/index.html +1 -1
  136. package/cli/lib/doctor.js +1 -1
  137. package/package.json +5 -5
@@ -13,7 +13,7 @@
13
13
  - **Language**: TypeScript 5.x
14
14
  - **Styling**: {{STYLING}} (SCSS/Tailwind/Angular Material)
15
15
  - **State Management**: {{STATE_MANAGEMENT}} (Signals/NgRx/NGXS)
16
- - **Testing**: {{TESTING}} (Vitest/Jest + Cypress)
16
+ - **Testing**: {{TESTING}} (Vitest/Jest + Playwright)
17
17
  - **Build Tool**: Angular CLI / Vite
18
18
 
19
19
  ## Architecture
@@ -227,19 +227,16 @@ export default defineConfig({
227
227
 
228
228
  **src/test-setup.ts**
229
229
  ```typescript
230
- import '@analogjs/vitest-angular/setup-zone';
231
- import { getTestBed } from '@angular/core/testing';
232
- import {
233
- BrowserDynamicTestingModule,
234
- platformBrowserDynamicTesting
235
- } from '@angular/platform-browser-dynamic/testing';
236
-
237
- getTestBed().initTestEnvironment(
238
- BrowserDynamicTestingModule,
239
- platformBrowserDynamicTesting()
240
- );
230
+ // Angular 22 — zoneless (do NOT import setup-zone: it re-introduces Zone.js and masks
231
+ // signal timing bugs in tests. setup-zone is ONLY for zone-based apps.)
232
+ import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed';
233
+ setupTestBed({ zoneless: true });
241
234
  ```
242
235
 
236
+ > **Important:** Never use `import '@analogjs/vitest-angular/setup-zone'` in an Angular 21+
237
+ > zoneless project. It re-activates Zone.js in tests and makes change-detection behavior
238
+ > inconsistent with the real app.
239
+
243
240
  ### Jest (Alternative)
244
241
 
245
242
  ```bash
@@ -263,25 +260,38 @@ module.exports = {
263
260
  };
264
261
  ```
265
262
 
266
- ### Cypress for E2E
263
+ ### Playwright for E2E (claude-craft standard)
264
+
265
+ > **claude-craft standard:** Playwright is the required E2E tool for all JS/TS projects.
266
+ > Cypress remains compatible for legacy projects but should not be used for new Angular projects.
267
267
 
268
268
  ```bash
269
- npm install -D cypress @cypress/schematic
270
- ng add @cypress/schematic
269
+ npm install -D @playwright/test
270
+ npx playwright install chromium
271
271
  ```
272
272
 
273
- **cypress.config.ts**
273
+ **playwright.config.ts**
274
274
  ```typescript
275
- import { defineConfig } from 'cypress';
275
+ import { defineConfig, devices } from '@playwright/test';
276
276
 
277
277
  export default defineConfig({
278
- e2e: {
279
- baseUrl: 'http://localhost:4200',
280
- supportFile: 'cypress/support/e2e.ts',
281
- specPattern: 'cypress/e2e/**/*.cy.ts',
282
- video: false,
283
- screenshotOnRunFailure: true
284
- }
278
+ testDir: './e2e',
279
+ fullyParallel: true,
280
+ forbidOnly: !!process.env['CI'],
281
+ retries: process.env['CI'] ? 2 : 0,
282
+ reporter: 'html',
283
+ webServer: {
284
+ command: 'ng serve',
285
+ url: 'http://localhost:4200',
286
+ reuseExistingServer: !process.env['CI'],
287
+ },
288
+ use: {
289
+ baseURL: 'http://localhost:4200',
290
+ trace: 'on-first-retry',
291
+ },
292
+ projects: [
293
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
294
+ ],
285
295
  });
286
296
  ```
287
297
 
@@ -510,7 +520,7 @@ Essential Angular tooling:
510
520
  | ESLint + Angular ESLint | Code linting |
511
521
  | Prettier | Code formatting |
512
522
  | Vitest/Jest | Unit testing |
513
- | Cypress | E2E testing |
523
+ | Playwright | E2E testing |
514
524
  | NgRx/Signals | State management |
515
525
  | Webpack Bundle Analyzer | Bundle analysis |
516
526
  | Angular DevTools | Debugging |
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```
8
8
  /\
9
- / \ E2E Tests (Cypress/Playwright)
9
+ / \ E2E Tests (Playwright)
10
10
  /----\ 10% - Critical user journeys
11
11
  / \
12
12
  /--------\ Integration Tests
@@ -57,19 +57,16 @@ export default defineConfig({
57
57
 
58
58
  **src/test-setup.ts**
59
59
  ```typescript
60
- import '@analogjs/vitest-angular/setup-zone';
61
- import { getTestBed } from '@angular/core/testing';
62
- import {
63
- BrowserDynamicTestingModule,
64
- platformBrowserDynamicTesting
65
- } from '@angular/platform-browser-dynamic/testing';
66
-
67
- getTestBed().initTestEnvironment(
68
- BrowserDynamicTestingModule,
69
- platformBrowserDynamicTesting()
70
- );
60
+ // Angular 22 — zoneless (do NOT import setup-zone: it re-introduces Zone.js and masks
61
+ // signal timing bugs in tests. setup-zone is ONLY for zone-based apps.)
62
+ import { setupTestBed } from '@analogjs/vitest-angular/setup-testbed';
63
+ setupTestBed({ zoneless: true });
71
64
  ```
72
65
 
66
+ > **Important:** Never use `import '@analogjs/vitest-angular/setup-zone'` in an Angular 21+
67
+ > zoneless project. It re-activates Zone.js in tests and makes change-detection behavior
68
+ > inconsistent with the real app.
69
+
73
70
  ## Component Testing
74
71
 
75
72
  ### Basic Component Test
@@ -611,63 +608,78 @@ describe('authInterceptor', () => {
611
608
  });
612
609
  ```
613
610
 
614
- ## E2E Testing with Cypress
611
+ ## E2E Testing with Playwright
612
+
613
+ > **claude-craft standard:** Playwright is the required E2E tool for all JS/TS projects
614
+ > (see `.claude/rules/07-testing.md`). Cypress remains compatible for existing legacy projects
615
+ > but should not be adopted for new Angular projects.
615
616
 
616
617
  ### Setup
617
618
 
619
+ ```bash
620
+ npm install -D @playwright/test
621
+ npx playwright install chromium
622
+ ```
623
+
624
+ **playwright.config.ts**
618
625
  ```typescript
619
- // cypress/support/commands.ts
620
- declare global {
621
- namespace Cypress {
622
- interface Chainable {
623
- login(email: string, password: string): Chainable<void>;
624
- }
625
- }
626
- }
627
-
628
- Cypress.Commands.add('login', (email: string, password: string) => {
629
- cy.visit('/auth/login');
630
- cy.get('[data-cy=email]').type(email);
631
- cy.get('[data-cy=password]').type(password);
632
- cy.get('[data-cy=submit]').click();
633
- cy.url().should('include', '/dashboard');
626
+ import { defineConfig, devices } from '@playwright/test';
627
+
628
+ export default defineConfig({
629
+ testDir: './e2e',
630
+ fullyParallel: true,
631
+ forbidOnly: !!process.env['CI'],
632
+ retries: process.env['CI'] ? 2 : 0,
633
+ reporter: 'html',
634
+ webServer: {
635
+ command: 'ng serve',
636
+ url: 'http://localhost:4200',
637
+ reuseExistingServer: !process.env['CI'],
638
+ },
639
+ use: {
640
+ baseURL: 'http://localhost:4200',
641
+ trace: 'on-first-retry',
642
+ },
643
+ projects: [
644
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
645
+ ],
634
646
  });
635
647
  ```
636
648
 
637
649
  ### E2E Test Example
638
650
 
639
651
  ```typescript
640
- // cypress/e2e/users.cy.ts
641
- describe('Users Feature', () => {
642
- beforeEach(() => {
643
- cy.login('admin@test.com', 'password');
644
- });
652
+ // e2e/users.spec.ts
653
+ import { test, expect } from '@playwright/test';
654
+
655
+ test.describe('Users Feature', () => {
656
+ test.use({ storageState: 'e2e/.auth/user.json' });
645
657
 
646
- it('should display users list', () => {
647
- cy.visit('/users');
658
+ test('should display users list', async ({ page }) => {
659
+ await page.goto('/users');
648
660
 
649
- cy.get('[data-cy=users-list]').should('be.visible');
650
- cy.get('[data-cy=user-card]').should('have.length.greaterThan', 0);
661
+ await expect(page.getByTestId('users-list')).toBeVisible();
662
+ await expect(page.getByTestId('user-card')).toHaveCount(expect.any(Number));
651
663
  });
652
664
 
653
- it('should create new user', () => {
654
- cy.visit('/users');
655
- cy.get('[data-cy=add-user]').click();
665
+ test('should create new user', async ({ page }) => {
666
+ await page.goto('/users');
667
+ await page.getByTestId('add-user').click();
656
668
 
657
- cy.get('[data-cy=user-name]').type('New User');
658
- cy.get('[data-cy=user-email]').type('new@test.com');
659
- cy.get('[data-cy=save]').click();
669
+ await page.getByTestId('user-name').fill('New User');
670
+ await page.getByTestId('user-email').fill('new@test.com');
671
+ await page.getByTestId('save').click();
660
672
 
661
- cy.get('[data-cy=toast]').should('contain', 'User created');
662
- cy.get('[data-cy=users-list]').should('contain', 'New User');
673
+ await expect(page.getByTestId('toast')).toContainText('User created');
674
+ await expect(page.getByTestId('users-list')).toContainText('New User');
663
675
  });
664
676
 
665
- it('should search users', () => {
666
- cy.visit('/users');
677
+ test('should search users', async ({ page }) => {
678
+ await page.goto('/users');
667
679
 
668
- cy.get('[data-cy=search]').type('John');
669
- cy.get('[data-cy=user-card]').should('have.length', 1);
670
- cy.get('[data-cy=user-card]').should('contain', 'John');
680
+ await page.getByTestId('search').fill('John');
681
+ await expect(page.getByTestId('user-card')).toHaveCount(1);
682
+ await expect(page.getByTestId('user-card').first()).toContainText('John');
671
683
  });
672
684
  });
673
685
  ```
@@ -730,7 +742,7 @@ beforeEach(() => {
730
742
  |-----------|------|----------|
731
743
  | Unit | Vitest/Jest | 70% |
732
744
  | Integration | TestBed | 20% |
733
- | E2E | Cypress | 10% |
745
+ | E2E | Playwright | 10% |
734
746
 
735
747
  Testing priorities:
736
748
  1. Critical business logic
@@ -399,12 +399,11 @@ jobs:
399
399
  - name: Install dependencies
400
400
  run: npm ci
401
401
 
402
- - name: Cypress run
403
- uses: cypress-io/github-action@v6
404
- with:
405
- build: npm run build
406
- start: npm run serve:ci
407
- wait-on: 'http://localhost:4200'
402
+ - name: Playwright install
403
+ run: npx playwright install --with-deps chromium
404
+
405
+ - name: Playwright run
406
+ run: npm run e2e
408
407
  ```
409
408
 
410
409
  ## Security Scanning
@@ -461,8 +460,8 @@ sonar.javascript.lcov.reportPaths=coverage/lcov.info
461
460
  "test": "vitest",
462
461
  "test:ci": "vitest run --coverage",
463
462
  "test:watch": "vitest watch",
464
- "e2e": "cypress run",
465
- "e2e:open": "cypress open",
463
+ "e2e": "playwright test",
464
+ "e2e:ui": "playwright test --ui",
466
465
 
467
466
  "lint": "ng lint",
468
467
  "lint:fix": "ng lint --fix",
@@ -71,7 +71,11 @@ dotnet publish -c Release --self-contained -r linux-x64
71
71
  <PackageReference Include="xunit.runner.visualstudio" Version="2.*">
72
72
  <PrivateAssets>all</PrivateAssets>
73
73
  </PackageReference>
74
+ <!-- Moq 4.20.0 (Aug 2023) briefly embedded SponsorLink (scraped git emails); reverted in 4.20.2.
75
+ 4.20.70 is clean and MIT. New projects may prefer NSubstitute (MIT, cleaner API). -->
74
76
  <PackageReference Include="Moq" Version="4.*" />
77
+ <!-- Alternative: NSubstitute (MIT, .NET community default 2026)
78
+ <PackageReference Include="NSubstitute" Version="5.*" /> -->
75
79
  <PackageReference Include="FluentAssertions" Version="6.*" />
76
80
  <PackageReference Include="Bogus" Version="35.*" />
77
81
  <PackageReference Include="Testcontainers" Version="3.*" />
@@ -266,6 +266,36 @@ _loggerMock.Verify(
266
266
  Times.Once);
267
267
  ```
268
268
 
269
+ ### Alternative: NSubstitute
270
+
271
+ > **Moq vs NSubstitute (2026):** Moq 4.20.0 (Aug 2023) briefly embedded SponsorLink (scraped git emails) — reverted in 4.20.2, 4.20.70 is clean and MIT. The .NET community has broadly adopted **NSubstitute** (MIT) for its cleaner API. Both frameworks are valid; NSubstitute is recommended for new projects.
272
+ >
273
+ > NuGet: `NSubstitute` Version 5.*
274
+
275
+ ```csharp
276
+ // NSubstitute: equivalent of the Moq examples above
277
+ var orderRepo = Substitute.For<IOrderRepository>();
278
+ var emailService = Substitute.For<IEmailService>();
279
+
280
+ var sut = new OrderService(orderRepo, emailService);
281
+
282
+ // Setup
283
+ var order = CreateTestOrder();
284
+ orderRepo.GetByIdAsync(order.Id, Arg.Any<CancellationToken>()).Returns(order);
285
+
286
+ // Act
287
+ await sut.ProcessOrderAsync(order.Id);
288
+
289
+ // Verify
290
+ await emailService.Received(1).SendOrderConfirmationAsync(
291
+ Arg.Is<Order>(o => o.Id == order.Id),
292
+ Arg.Any<CancellationToken>());
293
+
294
+ // Verify NOT called
295
+ await orderRepo.DidNotReceive().DeleteAsync(
296
+ Arg.Any<Order>(), Arg.Any<CancellationToken>());
297
+ ```
298
+
269
299
  ## FluentAssertions
270
300
 
271
301
  ### Basic Assertions
@@ -392,31 +422,53 @@ public class CustomerFaker : Faker<Customer>
392
422
 
393
423
  ## Integration Testing
394
424
 
395
- ### WebApplicationFactory
425
+ > **Critical anti-pattern: do not use `UseInMemoryDatabase` for integration tests.** The EF Core in-memory provider enforces no FK constraints, unique constraints, transactions, or `RowVersion`. Tests pass in-memory and silently fail against real PostgreSQL/SQL Server in production. Use **Testcontainers** (see below) or SQLite with `UseRelationalNulls()` as a Docker-free fallback.
426
+
427
+ ### WebApplicationFactory (with Testcontainers)
396
428
 
397
429
  ```csharp
398
- // Custom WebApplicationFactory
399
- public class CustomWebApplicationFactory : WebApplicationFactory<Program>
430
+ // NuGet: Testcontainers.PostgreSql 4.* (Version 4.x — always call WithImage explicitly)
431
+ public class CustomWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
400
432
  {
433
+ private PostgreSqlContainer _postgres = null!;
434
+
435
+ public async Task InitializeAsync()
436
+ {
437
+ _postgres = new PostgreSqlBuilder()
438
+ .WithImage("postgres:16-alpine") // Required in 4.x — no default image
439
+ .WithDatabase("testdb")
440
+ .WithUsername("test")
441
+ .WithPassword("test")
442
+ .Build();
443
+ await _postgres.StartAsync();
444
+ }
445
+
446
+ public new async Task DisposeAsync() => await _postgres.DisposeAsync();
447
+
401
448
  protected override void ConfigureWebHost(IWebHostBuilder builder)
402
449
  {
403
450
  builder.ConfigureServices(services =>
404
451
  {
405
- // Remove real database
452
+ // Replace real database with test container
406
453
  var descriptor = services.SingleOrDefault(
407
454
  d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
408
455
  if (descriptor != null)
409
456
  services.Remove(descriptor);
410
457
 
411
- // Add in-memory database
412
458
  services.AddDbContext<ApplicationDbContext>(options =>
413
- {
414
- options.UseInMemoryDatabase("TestDb");
415
- });
459
+ options.UseNpgsql(_postgres.GetConnectionString()));
416
460
 
417
461
  // Replace external services with fakes
418
462
  services.AddScoped<IEmailService, FakeEmailService>();
419
463
  });
464
+
465
+ builder.Configure(app =>
466
+ {
467
+ // Apply migrations on the test database
468
+ using var scope = app.ApplicationServices.CreateScope();
469
+ var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
470
+ db.Database.MigrateAsync().GetAwaiter().GetResult();
471
+ });
420
472
  }
421
473
  }
422
474
 
@@ -211,8 +211,9 @@ var builder = WebApplication.CreateBuilder(args);
211
211
  builder.AddServiceDefaults();
212
212
 
213
213
  // Add application services
214
- builder.Services.AddEndpointsApiExplorer();
215
- builder.Services.AddSwaggerGen();
214
+ // .NET 10: native OpenAPI + Scalar UI (Swashbuckle removed from template since .NET 9)
215
+ // Packages: Microsoft.AspNetCore.OpenApi + Scalar.AspNetCore (see Directory.Packages.props)
216
+ builder.Services.AddOpenApi();
216
217
 
217
218
  // Add database (Aspire provides connection string automatically)
218
219
  builder.AddNpgsqlDbContext<ApplicationDbContext>("ordersdb");
@@ -227,8 +228,8 @@ app.MapDefaultEndpoints();
227
228
 
228
229
  if (app.Environment.IsDevelopment())
229
230
  {
230
- app.UseSwagger();
231
- app.UseSwaggerUI();
231
+ app.MapOpenApi();
232
+ app.MapScalarApiReference();
232
233
  }
233
234
 
234
235
  app.MapOrderEndpoints();
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: dynamic-workflows
3
+ description: Orchestrate dozens-to-hundreds of subagents from a script Claude writes (Claude Code Dynamic Workflows, trigger `ultracode`). Use when a task exceeds a single agent's context or needs more than ~4 concurrent workers — large audits, migrations, multi-source research, fan-out reviews. Distinct from Agent Teams (synchronous, ≤4 workers) and ralph-run (sequential single-context loop).
4
+ context: fork
5
+ ---
6
+
7
+ # Dynamic Workflows — orchestration multi-agents scriptée
8
+
9
+ Dynamic Workflows (Claude Code 2.1.154+, mot-clé déclencheur **`ultracode`**) laissent Claude
10
+ **écrire un script JavaScript** qui orchestre des sous-agents de façon déterministe (boucles,
11
+ conditions, fan-out). Le script tourne en arrière-plan ; on suit l'exécution via `/workflows`.
12
+
13
+ > ⚠️ Opt-in coûteux : un workflow peut lancer des dizaines à centaines de sous-agents. Ne l'utiliser
14
+ > que sur demande explicite (mot « ultracode », « use a workflow », « fan out agents ») ou quand le
15
+ > palier de complexité ci-dessous est franchi.
16
+
17
+ ## Quand l'utiliser — les 3 paliers d'orchestration
18
+
19
+ | Palier | Outil | Concurrence | Quand |
20
+ |--------|-------|-------------|-------|
21
+ | 1 | **Sub-agent simple** (Task/Agent) | 1 | Une investigation isolée, garder le contexte principal propre |
22
+ | 2 | **Agent Teams** (`/team:audit`, `/team:sprint`, `/team:security`) | 1 leader + ~3 workers | Travail parallèle borné, synchrone, qui tient en un round |
23
+ | 3 | **Dynamic Workflows** (`ultracode`) | jusqu'à ~1000 sous-agents (cap concurrent ~16) | La tâche dépasse le contexte d'un agent OU exige >4 workers OU une boucle/pipeline déterministe |
24
+
25
+ **Heuristique break-even :** dès que tu écrirais « lance N agents, puis pour chacun fais X, puis
26
+ agrège » — c'est un workflow. Si N ≤ 4 et une seule passe suffit, reste sur Agent Teams.
27
+
28
+ ## Patterns composables (prompts minimaux)
29
+
30
+ - **Fan-out & synthesize** — N lecteurs en parallèle sur des sous-systèmes disjoints → un agent
31
+ synthétise. (audit, cartographie de code)
32
+ - **Adversarial verification** — chaque finding passé à ≥1 sceptique chargé de le **réfuter** ;
33
+ ne garder que les `CONFIRMED`. *Le pattern qualité le plus important pour la génération autonome.*
34
+ - **Generate-and-filter** — générer large (idées, candidats), puis filtrer par un juge.
35
+ - **Classify-and-act** — un classifieur route chaque item vers le traitement adapté.
36
+ - **Pipeline** — chaque item traverse toutes les étapes sans barrière (latence = pire chaîne, pas
37
+ somme des étapes). C'est le défaut multi-étapes.
38
+ - **Loop-until-done / loop-until-dry** — relancer des chercheurs jusqu'à K rounds sans nouveauté
39
+ (découverte de taille inconnue : bugs, edge cases).
40
+
41
+ ## Squelette type (review → verify)
42
+
43
+ ```js
44
+ const results = await pipeline(
45
+ DIMENSIONS,
46
+ d => agent(d.prompt, { phase: 'Review', schema: FINDINGS }),
47
+ review => parallel(review.findings.map(f => () =>
48
+ agent(`Adversarially verify: ${f.title}. Default refuted=true if unsure.`,
49
+ { phase: 'Verify', schema: VERDICT }).then(v => ({ ...f, verdict: v }))))
50
+ )
51
+ const confirmed = results.flat().filter(Boolean).filter(f => f.verdict?.isReal)
52
+ ```
53
+
54
+ `pipeline()` par défaut (pas de barrière) ; `parallel()` seulement quand l'étape N a besoin de TOUS
55
+ les résultats de N-1 (dédup, early-exit, comparaison croisée). `schema` force une sortie structurée
56
+ validée. Pour des éditions concurrentes de fichiers, `isolation: 'worktree'`.
57
+
58
+ ## Monitoring
59
+
60
+ - **`/workflows`** : progression live (phases, agents, tokens).
61
+ - Le script revient avec un résultat agrégé ; lire ce résultat, décider la suite (souvent plusieurs
62
+ workflows en séquence : comprendre → concevoir → implémenter → revoir).
63
+
64
+ ## À ne pas confondre
65
+
66
+ - **ralph-run** (`/common:ralph-run`) : boucle séquentielle mono-contexte jusqu'à une DoD — pas de
67
+ parallélisme. Pour une tâche qui dépasse ce modèle, basculer sur un Dynamic Workflow.
68
+ - **`/effort ultracode`** : palier d'effort CLI (débit code max sur Opus 4.8) — orthogonal au
69
+ déclencheur `ultracode` des workflows, même si souvent utilisés ensemble.
70
+
71
+ > Référence : [Claude Code Workflows](https://code.claude.com/docs/en/workflows) · voir aussi
72
+ > `@.claude/commands/common/sub-agents-patterns.md` (tableau comparatif des orchestrations).
@@ -96,7 +96,7 @@ return (new PhpCsFixer\Config())
96
96
  ->setRiskyAllowed(true)
97
97
  ->setRules([
98
98
  '@PSR12' => true,
99
- '@PHP84Migration' => true,
99
+ '@PHP85Migration' => true,
100
100
  '@Symfony' => true,
101
101
  '@Symfony:risky' => true,
102
102
 
@@ -36,12 +36,13 @@ export default defineConfig({
36
36
  build: {
37
37
  target: 'esnext',
38
38
  sourcemap: true,
39
- rollupOptions: {
39
+ // Vite 8 / Rolldown (default): object form of manualChunks removed — use codeSplitting.groups
40
+ rolldownOptions: {
40
41
  output: {
41
- // ⚠️ Vite 8 (Rolldown): manualChunks is deprecated when Rolldown is active (default bundler in Vite 8).
42
- // Remove or condition to `build.rolldown !== true`. Rolldown produces equivalent or better automatic chunking.
43
- manualChunks: {
44
- 'vue-vendor': ['vue', 'vue-router', 'pinia'],
42
+ codeSplitting: {
43
+ groups: [
44
+ { name: 'vue-vendor', test: /node_modules\/(vue|pinia|vue-router)/ },
45
+ ],
45
46
  },
46
47
  },
47
48
  },
@@ -49,6 +50,9 @@ export default defineConfig({
49
50
  })
50
51
  ```
51
52
 
53
+ > **Vite 8 / Rolldown (default):** The object form of `manualChunks` is **removed** (not supported). Use `build.rolldownOptions.output.codeSplitting.groups`.
54
+ > **Vite 7 / Rolldown disabled (legacy):** use `build.rollupOptions.output.manualChunks` — `// Only if Rolldown is explicitly disabled`.
55
+
52
56
  ### Environment Variables
53
57
 
54
58
  ```bash
@@ -142,8 +146,8 @@ export default [
142
146
  ```json
143
147
  {
144
148
  "scripts": {
145
- "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx",
146
- "lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx --fix"
149
+ "lint": "eslint .",
150
+ "lint:fix": "eslint . --fix"
147
151
  }
148
152
  }
149
153
  ```
@@ -340,7 +344,7 @@ const router = createRouter({ history: createWebHistory() });
340
344
  "lint:fix": "eslint . --fix",
341
345
  "format": "prettier --write src/",
342
346
  "format:check": "prettier --check src/",
343
- "prepare": "husky install"
347
+ "prepare": "husky"
344
348
  },
345
349
  "dependencies": {
346
350
  "vue": "^3.5.0",
@@ -368,7 +372,7 @@ const router = createRouter({ history: createWebHistory() });
368
372
 
369
373
  ```bash
370
374
  pnpm add -D husky lint-staged
371
- pnpm exec husky install
375
+ pnpm exec husky init
372
376
  ```
373
377
 
374
378
  ### Configuration
@@ -389,18 +393,12 @@ pnpm exec husky install
389
393
  ```
390
394
 
391
395
  ```bash
392
- # .husky/pre-commit
393
- #!/usr/bin/env sh
394
- . "$(dirname -- "$0")/_/husky.sh"
395
-
396
+ # .husky/pre-commit (Husky v9 — no wrapper sourcing needed)
396
397
  pnpm lint-staged
397
398
  ```
398
399
 
399
400
  ```bash
400
- # .husky/pre-push
401
- #!/usr/bin/env sh
402
- . "$(dirname -- "$0")/_/husky.sh"
403
-
401
+ # .husky/pre-push (Husky v9 — no wrapper sourcing needed)
404
402
  pnpm type-check
405
403
  pnpm test:unit
406
404
  ```
@@ -330,16 +330,16 @@ export default defineConfig({
330
330
  ### Bundle Size Limits
331
331
 
332
332
  ```typescript
333
- // vite.config.ts
333
+ // vite.config.ts — Vite 8 / Rolldown (default)
334
334
  export default defineConfig({
335
335
  build: {
336
- rollupOptions: {
336
+ rolldownOptions: {
337
337
  output: {
338
- // ⚠️ Vite 8 (Rolldown): manualChunks is deprecated when Rolldown is the active bundler.
339
- // Remove or condition to build.rolldown !== true for Vite 8+ projects.
340
- manualChunks: {
341
- 'vue-vendor': ['vue', 'vue-router', 'pinia'],
342
- 'ui-vendor': ['@headlessui/vue', '@heroicons/vue'],
338
+ codeSplitting: {
339
+ groups: [
340
+ { name: 'vue-vendor', test: /node_modules\/(vue|pinia|vue-router)/ },
341
+ { name: 'ui-vendor', test: /node_modules\/@headlessui|@heroicons/ },
342
+ ],
343
343
  },
344
344
  },
345
345
  },
@@ -348,6 +348,9 @@ export default defineConfig({
348
348
  })
349
349
  ```
350
350
 
351
+ > **Vite 8 / Rolldown (default):** The object form of `manualChunks` is **removed** (not supported). Use `build.rolldownOptions.output.codeSplitting.groups`.
352
+ > **Vite 7 / Rolldown disabled (legacy):** use `build.rollupOptions.output.manualChunks` — `// Only if Rolldown is explicitly disabled`.
353
+
351
354
  ## Dependency Audit
352
355
 
353
356
  ### npm audit
@@ -379,7 +382,7 @@ pnpm update -i
379
382
 
380
383
  ```typescript
381
384
  // src/utils/webVitals.ts
382
- import { onCLS, onFID, onFCP, onLCP, onTTFB } from 'web-vitals'
385
+ import { onCLS, onINP, onFCP, onLCP, onTTFB } from 'web-vitals'
383
386
 
384
387
  type ReportHandler = (metric: {
385
388
  name: string
@@ -389,7 +392,7 @@ type ReportHandler = (metric: {
389
392
 
390
393
  export function reportWebVitals(onReport: ReportHandler) {
391
394
  onCLS(onReport)
392
- onFID(onReport)
395
+ onINP(onReport) // INP replaced FID as a Core Web Vital (March 2024)
393
396
  onFCP(onReport)
394
397
  onLCP(onReport)
395
398
  onTTFB(onReport)