@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.
- package/Dev/i18n/base/Angular/rules/00-project-context.md.template +1 -1
- package/Dev/i18n/base/Angular/rules/06-tooling.md +34 -24
- package/Dev/i18n/base/Angular/rules/07-testing-angular.md +63 -51
- package/Dev/i18n/base/Angular/rules/08-quality-tools.md +7 -8
- package/Dev/i18n/base/CSharp/rules/06-tooling.md +4 -0
- package/Dev/i18n/base/CSharp/rules/07-testing-csharp.md +60 -8
- package/Dev/i18n/base/CSharp/rules/12-aspire-cloud-native.md +5 -4
- package/Dev/i18n/base/Common/skills/dynamic-workflows/SKILL.md +72 -0
- package/Dev/i18n/base/PHP/commands/check-code-quality.md +1 -1
- package/Dev/i18n/base/VueJS/rules/06-tooling.md +15 -17
- package/Dev/i18n/base/VueJS/rules/08-quality-tools.md +12 -9
- package/Dev/i18n/de/Flutter/rules/02-architecture.md +4 -4
- package/Dev/i18n/de/Flutter/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/de/PHP/agents/php-reviewer.md +1 -1
- package/Dev/i18n/de/PHP/commands/check-compliance.md +1 -1
- package/Dev/i18n/de/PHP/rules/06-tooling.md +1 -1
- package/Dev/i18n/de/PHP/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/de/PHP/rules/11-security-php.md +24 -15
- package/Dev/i18n/de/Paperclip/CLAUDE.md.template +8 -4
- package/Dev/i18n/de/Paperclip/README.md +2 -2
- package/Dev/i18n/de/Paperclip/commands/generate-agent-config.md +2 -2
- package/Dev/i18n/de/Paperclip/commands/setup-company.md +3 -3
- package/Dev/i18n/de/Paperclip/rules/02-architecture-paperclip.md +1 -1
- package/Dev/i18n/de/Paperclip/rules/12-adapter-protocol.md +1 -1
- package/Dev/i18n/de/Python/rules/11-security-python.md +11 -7
- package/Dev/i18n/de/React/commands/bundle-analyze.md +2 -2
- package/Dev/i18n/de/React/commands/check-testing.md +1 -1
- package/Dev/i18n/de/React/rules/02-architecture.md +1 -1
- package/Dev/i18n/de/React/rules/06-tooling.md +2 -2
- package/Dev/i18n/de/React/rules/07-testing-react.md +1 -1
- package/Dev/i18n/de/React/rules/11-security-react.md +1 -1
- package/Dev/i18n/de/ReactNative/rules/06-tooling.md +7 -5
- package/Dev/i18n/de/ReactNative/rules/13-state-management.md +3 -2
- package/Dev/i18n/de/Symfony/rules/11-security-symfony.md +22 -6
- package/Dev/i18n/en/Flutter/rules/02-architecture.md +4 -4
- package/Dev/i18n/en/Flutter/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/en/PHP/agents/php-reviewer.md +1 -1
- package/Dev/i18n/en/PHP/rules/06-tooling.md +1 -1
- package/Dev/i18n/en/PHP/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/en/PHP/rules/11-security-php.md +24 -15
- package/Dev/i18n/en/Paperclip/CLAUDE.md.template +8 -4
- package/Dev/i18n/en/Paperclip/README.md +2 -2
- package/Dev/i18n/en/Paperclip/commands/generate-agent-config.md +2 -2
- package/Dev/i18n/en/Paperclip/commands/setup-company.md +3 -3
- package/Dev/i18n/en/Paperclip/rules/02-architecture-paperclip.md +1 -1
- package/Dev/i18n/en/Paperclip/rules/12-adapter-protocol.md +1 -1
- package/Dev/i18n/en/Python/rules/02-architecture.md +5 -5
- package/Dev/i18n/en/Python/rules/11-security-python.md +31 -24
- package/Dev/i18n/en/React/commands/bundle-analyze.md +2 -2
- package/Dev/i18n/en/React/commands/check-testing.md +1 -1
- package/Dev/i18n/en/React/rules/02-architecture.md +1 -1
- package/Dev/i18n/en/React/rules/06-tooling.md +2 -2
- package/Dev/i18n/en/React/rules/07-testing-react.md +1 -1
- package/Dev/i18n/en/React/rules/11-security-react.md +1 -1
- package/Dev/i18n/en/ReactNative/rules/06-tooling.md +7 -5
- package/Dev/i18n/en/ReactNative/rules/13-state-management.md +25 -2
- package/Dev/i18n/en/Symfony/rules/11-security-symfony.md +22 -6
- package/Dev/i18n/es/Flutter/rules/02-architecture.md +4 -4
- package/Dev/i18n/es/Flutter/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/es/PHP/agents/php-reviewer.md +1 -1
- package/Dev/i18n/es/PHP/commands/check-compliance.md +1 -1
- package/Dev/i18n/es/PHP/rules/06-tooling.md +1 -1
- package/Dev/i18n/es/PHP/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/es/PHP/rules/11-security-php.md +24 -15
- package/Dev/i18n/es/Paperclip/CLAUDE.md.template +8 -4
- package/Dev/i18n/es/Paperclip/README.md +2 -2
- package/Dev/i18n/es/Paperclip/commands/generate-agent-config.md +2 -2
- package/Dev/i18n/es/Paperclip/commands/setup-company.md +3 -3
- package/Dev/i18n/es/Paperclip/rules/02-architecture-paperclip.md +1 -1
- package/Dev/i18n/es/Paperclip/rules/12-adapter-protocol.md +1 -1
- package/Dev/i18n/es/Python/rules/11-security-python.md +11 -7
- package/Dev/i18n/es/React/commands/bundle-analyze.md +2 -2
- package/Dev/i18n/es/React/commands/check-security.md +1 -1
- package/Dev/i18n/es/React/commands/check-testing.md +1 -1
- package/Dev/i18n/es/React/rules/02-architecture.md +1 -1
- package/Dev/i18n/es/React/rules/06-tooling.md +2 -2
- package/Dev/i18n/es/React/rules/07-testing-react.md +1 -1
- package/Dev/i18n/es/React/rules/11-security-react.md +1 -1
- package/Dev/i18n/es/ReactNative/rules/06-tooling.md +7 -5
- package/Dev/i18n/es/ReactNative/rules/13-state-management.md +3 -2
- package/Dev/i18n/es/Symfony/rules/11-security-symfony.md +22 -6
- package/Dev/i18n/fr/Flutter/rules/02-architecture.md +6 -6
- package/Dev/i18n/fr/Flutter/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/fr/PHP/agents/php-reviewer.md +1 -1
- package/Dev/i18n/fr/PHP/commands/check-compliance.md +1 -1
- package/Dev/i18n/fr/PHP/rules/06-tooling.md +1 -1
- package/Dev/i18n/fr/PHP/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/fr/Paperclip/CLAUDE.md.template +2 -2
- package/Dev/i18n/fr/Paperclip/README.md +2 -2
- package/Dev/i18n/fr/Paperclip/commands/generate-agent-config.md +2 -2
- package/Dev/i18n/fr/Paperclip/commands/setup-company.md +3 -3
- package/Dev/i18n/fr/Paperclip/rules/02-architecture-paperclip.md +1 -1
- package/Dev/i18n/fr/Paperclip/rules/12-adapter-protocol.md +1 -1
- package/Dev/i18n/fr/Python/rules/02-architecture.md +5 -5
- package/Dev/i18n/fr/Python/rules/11-security-python.md +31 -24
- package/Dev/i18n/fr/React/commands/check-testing.md +1 -1
- package/Dev/i18n/fr/React/rules/02-architecture.md +1 -1
- package/Dev/i18n/fr/React/rules/06-tooling.md +2 -2
- package/Dev/i18n/fr/React/rules/07-testing-react.md +1 -1
- package/Dev/i18n/fr/React/rules/11-security-react.md +1 -1
- package/Dev/i18n/fr/ReactNative/rules/06-tooling.md +7 -5
- package/Dev/i18n/fr/ReactNative/rules/13-state-management.md +3 -2
- package/Dev/i18n/fr/Symfony/rules/11-security-symfony.md +22 -6
- package/Dev/i18n/pt/Flutter/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/pt/PHP/agents/php-reviewer.md +1 -1
- package/Dev/i18n/pt/PHP/commands/check-compliance.md +1 -1
- package/Dev/i18n/pt/PHP/rules/06-tooling.md +1 -1
- package/Dev/i18n/pt/PHP/rules/08-quality-tools.md +1 -1
- package/Dev/i18n/pt/PHP/rules/11-security-php.md +24 -15
- package/Dev/i18n/pt/Paperclip/CLAUDE.md.template +8 -4
- package/Dev/i18n/pt/Paperclip/README.md +2 -2
- package/Dev/i18n/pt/Paperclip/commands/generate-agent-config.md +2 -2
- package/Dev/i18n/pt/Paperclip/commands/setup-company.md +3 -3
- package/Dev/i18n/pt/Paperclip/rules/02-architecture-paperclip.md +1 -1
- package/Dev/i18n/pt/Paperclip/rules/12-adapter-protocol.md +1 -1
- package/Dev/i18n/pt/Python/rules/11-security-python.md +31 -24
- package/Dev/i18n/pt/React/commands/bundle-analyze.md +2 -2
- package/Dev/i18n/pt/React/commands/check-testing.md +1 -1
- package/Dev/i18n/pt/React/rules/02-architecture.md +1 -1
- package/Dev/i18n/pt/React/rules/06-tooling.md +2 -2
- package/Dev/i18n/pt/React/rules/07-testing-react.md +1 -1
- package/Dev/i18n/pt/React/rules/11-security-react.md +1 -1
- package/Dev/i18n/pt/ReactNative/rules/06-tooling.md +7 -5
- package/Dev/i18n/pt/ReactNative/rules/13-state-management.md +3 -2
- package/Dev/i18n/pt/Symfony/rules/11-security-symfony.md +22 -6
- package/README.md +5 -5
- package/bundles/cursor/.cursorrules +5 -5
- package/bundles/windsurf/.windsurfrules +5 -5
- package/cli/kanban/client/dist/assets/{BurndownView-k0Da2Sz-.js → BurndownView-BxC56Ur9.js} +2 -2
- package/cli/kanban/client/dist/assets/{DepsView-Cp0MVMVy.js → DepsView-CA50ivBK.js} +1 -1
- package/cli/kanban/client/dist/assets/{DocsView-Bduxxrv0.js → DocsView-CF2Qkapy.js} +1 -1
- package/cli/kanban/client/dist/assets/{SprintsView-C7qdhg6F.js → SprintsView-96Z-d962.js} +1 -1
- package/cli/kanban/client/dist/assets/{cytoscape.esm-BQ_ULxwV.js → cytoscape.esm-Orb7NpID.js} +2 -2
- package/cli/kanban/client/dist/assets/{index-D8HBRPRV.js → index-CXWHm5hf.js} +4 -4
- package/cli/kanban/client/dist/index.html +1 -1
- package/cli/lib/doctor.js +1 -1
- 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 +
|
|
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
|
|
231
|
-
|
|
232
|
-
import {
|
|
233
|
-
|
|
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
|
-
###
|
|
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
|
|
270
|
-
|
|
269
|
+
npm install -D @playwright/test
|
|
270
|
+
npx playwright install chromium
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
**
|
|
273
|
+
**playwright.config.ts**
|
|
274
274
|
```typescript
|
|
275
|
-
import { defineConfig } from '
|
|
275
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
276
276
|
|
|
277
277
|
export default defineConfig({
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
|
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 (
|
|
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
|
|
61
|
-
|
|
62
|
-
import {
|
|
63
|
-
|
|
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
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
-
|
|
647
|
-
|
|
658
|
+
test('should display users list', async ({ page }) => {
|
|
659
|
+
await page.goto('/users');
|
|
648
660
|
|
|
649
|
-
|
|
650
|
-
|
|
661
|
+
await expect(page.getByTestId('users-list')).toBeVisible();
|
|
662
|
+
await expect(page.getByTestId('user-card')).toHaveCount(expect.any(Number));
|
|
651
663
|
});
|
|
652
664
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
665
|
+
test('should create new user', async ({ page }) => {
|
|
666
|
+
await page.goto('/users');
|
|
667
|
+
await page.getByTestId('add-user').click();
|
|
656
668
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
662
|
-
|
|
673
|
+
await expect(page.getByTestId('toast')).toContainText('User created');
|
|
674
|
+
await expect(page.getByTestId('users-list')).toContainText('New User');
|
|
663
675
|
});
|
|
664
676
|
|
|
665
|
-
|
|
666
|
-
|
|
677
|
+
test('should search users', async ({ page }) => {
|
|
678
|
+
await page.goto('/users');
|
|
667
679
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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 |
|
|
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:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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": "
|
|
465
|
-
"e2e:
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
215
|
-
|
|
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.
|
|
231
|
-
app.
|
|
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).
|
|
@@ -36,12 +36,13 @@ export default defineConfig({
|
|
|
36
36
|
build: {
|
|
37
37
|
target: 'esnext',
|
|
38
38
|
sourcemap: true,
|
|
39
|
-
|
|
39
|
+
// Vite 8 / Rolldown (default): object form of manualChunks removed — use codeSplitting.groups
|
|
40
|
+
rolldownOptions: {
|
|
40
41
|
output: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 .
|
|
146
|
-
"lint:fix": "eslint . --
|
|
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
|
|
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
|
|
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
|
-
|
|
336
|
+
rolldownOptions: {
|
|
337
337
|
output: {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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,
|
|
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
|
-
|
|
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)
|