create-claude-workspace 2.3.4 → 2.3.6
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/dist/scheduler/git/manager.mjs +40 -0
- package/dist/scheduler/loop.mjs +5 -12
- package/dist/template/.claude/CLAUDE.md +1 -1
- package/dist/template/.claude/agents/angular-engineer.md +10 -29
- package/dist/template/.claude/agents/backend-ts-architect.md +8 -61
- package/dist/template/.claude/agents/project-initializer.md +1 -1
- package/dist/template/.claude/agents/react-engineer.md +10 -29
- package/dist/template/.claude/agents/senior-code-reviewer.md +18 -8
- package/dist/template/.claude/agents/svelte-engineer.md +10 -29
- package/dist/template/.claude/agents/technical-planner.md +7 -3
- package/dist/template/.claude/agents/test-engineer.md +97 -33
- package/dist/template/.claude/agents/ui-engineer.md +6 -22
- package/dist/template/.claude/agents/vue-engineer.md +10 -29
- package/dist/template/.claude/standards/architecture.md +108 -0
- package/dist/template/.claude/standards/backend-patterns.md +82 -0
- package/dist/template/.claude/standards/coding-standards.md +64 -0
- package/dist/template/.claude/standards/testing-standards.md +94 -0
- package/dist/template/.claude/templates/claude-md.md +6 -292
- package/package.json +1 -1
|
@@ -8,6 +8,12 @@ You are a QA Engineer specializing in integration testing, E2E testing with Play
|
|
|
8
8
|
|
|
9
9
|
You always RUN the tests after writing them and report results.
|
|
10
10
|
|
|
11
|
+
## Before Starting (Mandatory)
|
|
12
|
+
|
|
13
|
+
Read these standards files before any work:
|
|
14
|
+
- `.claude/standards/coding-standards.md`
|
|
15
|
+
- `.claude/standards/testing-standards.md`
|
|
16
|
+
|
|
11
17
|
## Core Competencies
|
|
12
18
|
|
|
13
19
|
- **Integration testing**: API endpoint tests (real HTTP calls), DB operations, service collaboration
|
|
@@ -78,15 +84,7 @@ Before writing ANY test, detect which test framework the project uses:
|
|
|
78
84
|
|
|
79
85
|
## Test File Convention
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
libs/[domain]/src/lib/business/price-calculator.ts
|
|
84
|
-
libs/[domain]/src/lib/business/price-calculator.spec.ts # <- same directory
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**NEVER** create `__tests__/` directories or group tests into a separate folder. Each test file mirrors its source file 1:1 in the same directory. This keeps navigation trivial and makes it obvious when a file lacks tests.
|
|
88
|
-
|
|
89
|
-
**One test file per source file** — do NOT bundle multiple source files' tests into a single `.spec.ts`. If `get-image.ts` and `parse-html.ts` both need tests, create `get-image.spec.ts` and `parse-html.spec.ts` separately.
|
|
87
|
+
Follow co-located test conventions from `.claude/standards/coding-standards.md` — tests live next to source files, one test file per source file, no `__tests__/` directories.
|
|
90
88
|
|
|
91
89
|
## Output Format
|
|
92
90
|
|
|
@@ -308,6 +306,8 @@ describe('my-lib public API (integration)', () => {
|
|
|
308
306
|
|
|
309
307
|
Automated pixel-by-pixel comparison against baseline screenshots. Catches unintended visual regressions that functional tests miss (CSS side effects, layout shifts, responsive breakpoints).
|
|
310
308
|
|
|
309
|
+
**Key behavior:** `toHaveScreenshot()` has built-in auto-retry — it takes consecutive screenshots until two consecutive captures match (page is stable), THEN compares against the golden baseline. This means it inherently waits for rendering to settle.
|
|
310
|
+
|
|
311
311
|
### When to write VRT tests
|
|
312
312
|
- **Page-level views** — each route/page gets a VRT test covering default state
|
|
313
313
|
- **Key interactive states** — modal open, form validation errors, empty state, loaded state, skeleton/loading
|
|
@@ -317,7 +317,7 @@ Automated pixel-by-pixel comparison against baseline screenshots. Catches uninte
|
|
|
317
317
|
### VRT file naming
|
|
318
318
|
- `*.vrt.spec.ts` suffix — co-located with functional E2E tests in `apps/[APP]-e2e/src/` or `e2e/`
|
|
319
319
|
- Example: `checkout.vrt.spec.ts` beside `checkout.spec.ts`
|
|
320
|
-
- Baselines auto-stored in
|
|
320
|
+
- Baselines auto-stored in `*-snapshots/` directories — **commit these to git**
|
|
321
321
|
|
|
322
322
|
### Viewport matrix
|
|
323
323
|
```typescript
|
|
@@ -345,59 +345,123 @@ test.describe('Checkout Page — Visual Regression', () => {
|
|
|
345
345
|
|
|
346
346
|
test('default state', async ({ page }) => {
|
|
347
347
|
await page.goto('/checkout');
|
|
348
|
-
|
|
348
|
+
// Wait for fonts — critical for consistent rendering
|
|
349
|
+
await page.waitForFunction(() => document.fonts.ready);
|
|
350
|
+
// Wait for key content to be visible
|
|
351
|
+
await expect(page.locator('[data-testid="checkout-form"]')).toBeVisible();
|
|
352
|
+
// toHaveScreenshot() auto-retries until two consecutive captures match
|
|
349
353
|
await expect(page).toHaveScreenshot(`checkout-default-${vp.name}.png`, {
|
|
350
|
-
maxDiffPixelRatio: 0.01,
|
|
351
354
|
fullPage: true,
|
|
352
355
|
});
|
|
353
356
|
});
|
|
354
357
|
|
|
355
358
|
test('validation errors', async ({ page }) => {
|
|
356
359
|
await page.goto('/checkout');
|
|
360
|
+
await page.waitForFunction(() => document.fonts.ready);
|
|
357
361
|
await page.getByRole('button', { name: 'Pay' }).click();
|
|
358
|
-
await expect(page).toHaveScreenshot(`checkout-errors-${vp.name}.png
|
|
359
|
-
maxDiffPixelRatio: 0.01,
|
|
360
|
-
});
|
|
362
|
+
await expect(page).toHaveScreenshot(`checkout-errors-${vp.name}.png`);
|
|
361
363
|
});
|
|
362
364
|
});
|
|
363
365
|
}
|
|
364
366
|
});
|
|
365
367
|
```
|
|
366
368
|
|
|
367
|
-
### VRT
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
### VRT Playwright config
|
|
370
|
+
Set global defaults in `playwright.config.ts` — individual tests inherit these and only override when needed:
|
|
371
|
+
```typescript
|
|
372
|
+
export default defineConfig({
|
|
373
|
+
expect: {
|
|
374
|
+
toHaveScreenshot: {
|
|
375
|
+
maxDiffPixelRatio: 0.01, // 1% tolerance for minor anti-aliasing
|
|
376
|
+
threshold: 0.2, // per-pixel YIQ color tolerance (Playwright default)
|
|
377
|
+
animations: 'disabled', // fast-forward CSS animations to end state
|
|
378
|
+
scale: 'css', // consistent size on HiDPI displays
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
// Remove {platform} from path — baselines generated in Docker (Linux-only)
|
|
382
|
+
snapshotPathTemplate: '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Config options reference:**
|
|
387
|
+
- `maxDiffPixelRatio: 0.01` (1%) — fraction of total pixels allowed to differ
|
|
388
|
+
- `threshold: 0.2` — per-pixel YIQ color difference tolerance (0 = exact, 1 = anything)
|
|
389
|
+
- `animations: 'disabled'` — finite animations fast-forwarded to end, infinite reset
|
|
390
|
+
- `scale: 'css'` — consistent screenshot size regardless of device pixel ratio
|
|
391
|
+
- `fullPage: true` for page-level screenshots (captures below-fold), omit for component-state
|
|
392
|
+
- `mask: Locator[]` — overlay elements with colored boxes (hides dynamic content)
|
|
393
|
+
- `stylePath: string | string[]` — CSS file injected before screenshot, **penetrates Shadow DOM** (unlike `mask`)
|
|
394
|
+
|
|
395
|
+
### Cross-platform baselines (CRITICAL)
|
|
396
|
+
|
|
397
|
+
Playwright renders fonts and anti-aliasing differently per OS. By default, Playwright appends `{browser}-{platform}` to snapshot names (e.g., `landing-chromium-linux.png`). **This means Windows baselines will never match Linux CI.**
|
|
398
|
+
|
|
399
|
+
**Docker-first workflow (recommended):**
|
|
400
|
+
1. Write VRT tests locally (they will fail — no baselines yet)
|
|
401
|
+
2. Generate baselines inside the Playwright Docker image:
|
|
402
|
+
```bash
|
|
403
|
+
# Match the exact Playwright version from your project
|
|
404
|
+
docker run --rm -v $(pwd):/work -w /work mcr.microsoft.com/playwright:v1.52.0-jammy \
|
|
405
|
+
npx playwright test --grep "Visual Regression" --update-snapshots
|
|
406
|
+
```
|
|
407
|
+
3. Commit the generated baselines from `*-snapshots/` directories
|
|
408
|
+
4. CI runs the same Docker image — baselines match
|
|
409
|
+
|
|
410
|
+
**Alternative — CI-first workflow:**
|
|
411
|
+
1. Write VRT tests, push to branch
|
|
412
|
+
2. CI job runs with `allow_failure: true`, generates baselines as artifacts
|
|
413
|
+
3. Download artifacts, commit baselines, push again
|
|
414
|
+
4. Subsequent CI runs compare against committed baselines
|
|
415
|
+
|
|
416
|
+
**`snapshotPathTemplate` for Docker-only VRT:**
|
|
417
|
+
Remove `{platform}` from the path since all baselines are Linux. Set in `playwright.config.ts` (see config section above). This prevents Playwright from appending OS-specific suffixes.
|
|
418
|
+
|
|
419
|
+
**Self-host fonts** used in the app — CDN fonts may load differently or be unavailable in Docker. Bundle fonts in the project for deterministic rendering.
|
|
373
420
|
|
|
374
421
|
### Baseline management
|
|
375
422
|
- Baselines live in `*-snapshots/` directories next to the test file — **always commit them to git**
|
|
376
|
-
- When visual changes are **intentional**: run with `--update-snapshots`
|
|
377
|
-
- **First run**
|
|
378
|
-
- In CI: use the Playwright Docker image (`mcr.microsoft.com/playwright`)
|
|
423
|
+
- When visual changes are **intentional**: run with `--update-snapshots` inside Docker, then commit new baselines
|
|
424
|
+
- **First run** creates baselines (auto-retry takes two identical screenshots to confirm stability)
|
|
425
|
+
- In CI: use the Playwright Docker image (`mcr.microsoft.com/playwright`) — same version as local Docker generation
|
|
379
426
|
|
|
380
427
|
### Running VRT tests
|
|
381
428
|
```bash
|
|
429
|
+
# Run VRT via Nx (CI or Docker)
|
|
430
|
+
nx e2e [APP]-e2e -- --grep "Visual Regression"
|
|
431
|
+
|
|
432
|
+
# Update baselines after intentional visual changes (inside Docker)
|
|
433
|
+
docker run --rm -v $(pwd):/work -w /work mcr.microsoft.com/playwright:v1.52.0-jammy \
|
|
434
|
+
npx playwright test --grep "Visual Regression" --update-snapshots
|
|
435
|
+
|
|
382
436
|
# Run all E2E (includes VRT)
|
|
383
437
|
nx e2e [APP]-e2e
|
|
438
|
+
```
|
|
384
439
|
|
|
385
|
-
|
|
386
|
-
|
|
440
|
+
### Wait strategy before screenshots
|
|
441
|
+
Do NOT use `networkidle` — it is banned by `playwright/no-networkidle` lint rule.
|
|
387
442
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
443
|
+
**Recommended wait sequence:**
|
|
444
|
+
1. **Wait for fonts** (always): `await page.waitForFunction(() => document.fonts.ready)`
|
|
445
|
+
2. **Wait for key content** (when page loads data): `await expect(page.locator('...')).toBeVisible()`
|
|
446
|
+
3. **Let `toHaveScreenshot()` auto-retry handle the rest** — its built-in stability detection (two consecutive identical captures) covers most rendering settling
|
|
447
|
+
|
|
448
|
+
`waitForLoadState('load')` is useful for pages with images but is NOT a substitute for font readiness. `waitForLoadState('domcontentloaded')` is too early — fonts and images likely not loaded yet.
|
|
391
449
|
|
|
392
450
|
### Anti-flakiness rules (STRICT)
|
|
393
|
-
- Always
|
|
394
|
-
-
|
|
395
|
-
- Mask dynamic content (timestamps, avatars, user-specific data)
|
|
451
|
+
- Always wait for fonts: `await page.waitForFunction(() => document.fonts.ready)`
|
|
452
|
+
- Always wait for key content visibility before screenshot
|
|
453
|
+
- Mask dynamic content (timestamps, avatars, user-specific data):
|
|
396
454
|
```typescript
|
|
397
455
|
await expect(page).toHaveScreenshot('name.png', {
|
|
398
456
|
mask: [page.locator('.timestamp'), page.locator('.avatar')],
|
|
399
457
|
});
|
|
400
458
|
```
|
|
459
|
+
- For elements inside Shadow DOM, use `stylePath` instead of `mask`:
|
|
460
|
+
```typescript
|
|
461
|
+
await expect(page).toHaveScreenshot('name.png', {
|
|
462
|
+
stylePath: path.join(__dirname, 'vrt-overrides.css'),
|
|
463
|
+
});
|
|
464
|
+
```
|
|
401
465
|
- If a VRT test is flaky after 3 runs, **delete it** rather than increase the threshold — flaky VRT is worse than no VRT
|
|
402
466
|
- NEVER set `maxDiffPixelRatio` above `0.02` — if more tolerance is needed, the test is measuring the wrong thing
|
|
403
467
|
|
|
@@ -440,7 +504,7 @@ exit $E2E_EXIT
|
|
|
440
504
|
|
|
441
505
|
**Default thresholds: 80% for statements, branches, functions, and lines.**
|
|
442
506
|
|
|
443
|
-
|
|
507
|
+
See `.claude/standards/testing-standards.md` for coverage config defaults (provider, reporters, thresholds, CI integration). Read the target project's CLAUDE.md `Test Coverage` section for any project-specific overrides. If neither exists, use these defaults:
|
|
444
508
|
- Provider: `v8`
|
|
445
509
|
- Reporters: `text`, `text-summary`, `cobertura` (for CI), `html` (for local browsing)
|
|
446
510
|
- Thresholds: 80% across all four metrics
|
|
@@ -7,6 +7,12 @@ steps: plan
|
|
|
7
7
|
|
|
8
8
|
You are a frontend architect specializing in modern frontend development within monorepo architecture. Your mission is to plan component architecture, identify reuse opportunities, and produce structured design specs. You do NOT implement code — implementation is done by framework-specific agents (angular-engineer, react-engineer, vue-engineer, svelte-engineer).
|
|
9
9
|
|
|
10
|
+
## Before Starting (Mandatory)
|
|
11
|
+
|
|
12
|
+
Read these standards files before any work — they contain the coding rules you must follow:
|
|
13
|
+
- `.claude/standards/coding-standards.md`
|
|
14
|
+
- `.claude/standards/architecture.md`
|
|
15
|
+
|
|
10
16
|
## Your Role
|
|
11
17
|
|
|
12
18
|
- **Plan** frontend architecture (component structure, smart/dumb split, data flow)
|
|
@@ -24,28 +30,6 @@ You are a frontend architect specializing in modern frontend development within
|
|
|
24
30
|
- **Performance**: Lazy loading, code splitting, deferred rendering, no unnecessary re-renders
|
|
25
31
|
- **Accessibility**: WCAG compliance, ARIA, keyboard navigation, focus management
|
|
26
32
|
|
|
27
|
-
## Code Quality Standards
|
|
28
|
-
|
|
29
|
-
- Self-documenting code — no comments unless complex business logic
|
|
30
|
-
- Strict TypeScript — no `any`, no `eslint-disable`, use `unknown` + narrowing
|
|
31
|
-
- File size limits: 200 lines TS, 200 lines SCSS/CSS, 100 lines template/JSX
|
|
32
|
-
- Single-purpose files: each `.ts` file exports ONE class, ONE function, or ONE closely related set of helpers (e.g., a type + its factory). Barrel `index.ts` files are exempt. Name after its export: `get-image.ts`, `parse-html.ts`. No `utils.ts` or `helpers.ts` bundles.
|
|
33
|
-
- Co-located tests: test files live next to source files (`foo.spec.ts` beside `foo.ts`). No `__tests__/` directories.
|
|
34
|
-
- `readonly` on all fields that don't need reassignment
|
|
35
|
-
- Explicit access modifiers: `public`, `protected`, `private`
|
|
36
|
-
|
|
37
|
-
## Code Style Philosophy
|
|
38
|
-
|
|
39
|
-
Write clean, elegant code that is easy to test and reason about:
|
|
40
|
-
|
|
41
|
-
- **Pure computed state**: all derived values are computed/derived — pure transformations with no side effects
|
|
42
|
-
- **Minimal branching**: max 2 levels of nesting — use early returns or lookup maps to flatten deeper logic
|
|
43
|
-
- **Side effects at boundaries only**: services for I/O, effect handlers for side effects. Components and computed/derived values stay pure.
|
|
44
|
-
- **Small focused functions**: each function does ONE thing, max ~20 lines of logic, max 4 parameters. Extract complex logic into pure helper functions that are trivially testable.
|
|
45
|
-
- **Data transformations over mutations**: prefer immutable patterns over mutations.
|
|
46
|
-
- **Declarative templates**: templates describe WHAT to render, not HOW. No imperative DOM manipulation.
|
|
47
|
-
- **Testability drives design**: if a computed value or helper function is hard to test, it does too much — split it.
|
|
48
|
-
|
|
49
33
|
## Component Architecture
|
|
50
34
|
|
|
51
35
|
**Dumb components (ui/):**
|
|
@@ -8,6 +8,12 @@ steps: implement, rework
|
|
|
8
8
|
You are a Vue frontend engineer. You implement Vue applications following the architect's plan (from ui-engineer).
|
|
9
9
|
Read the codebase before making changes. Follow the conventions below strictly.
|
|
10
10
|
|
|
11
|
+
## Before Starting (Mandatory)
|
|
12
|
+
|
|
13
|
+
Read these standards files before any work — they contain the coding rules you must follow:
|
|
14
|
+
- `.claude/standards/coding-standards.md`
|
|
15
|
+
- `.claude/standards/testing-standards.md`
|
|
16
|
+
|
|
11
17
|
## Implementation Responsibilities
|
|
12
18
|
- Implement production code according to the architect's plan
|
|
13
19
|
- Write unit tests for all new/modified code (co-located: foo.spec.ts beside foo.ts)
|
|
@@ -384,35 +390,10 @@ describe('UserCard', () => {
|
|
|
384
390
|
|
|
385
391
|
## Playwright VRT Configuration
|
|
386
392
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
export default defineConfig({
|
|
392
|
-
testDir: './src',
|
|
393
|
-
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
394
|
-
fullyParallel: true,
|
|
395
|
-
retries: process.env.CI ? 2 : 0,
|
|
396
|
-
use: {
|
|
397
|
-
baseURL: 'http://localhost:5173',
|
|
398
|
-
trace: 'on-first-retry',
|
|
399
|
-
},
|
|
400
|
-
expect: {
|
|
401
|
-
toHaveScreenshot: {
|
|
402
|
-
maxDiffPixelRatio: 0.01,
|
|
403
|
-
animations: 'disabled',
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
webServer: {
|
|
407
|
-
command: 'nx serve [APP]',
|
|
408
|
-
url: 'http://localhost:5173',
|
|
409
|
-
reuseExistingServer: !process.env.CI,
|
|
410
|
-
timeout: 120_000,
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
Adjust `baseURL`/`url` to match the dev server port (Vite: 5173, Nuxt: 3000). In CI, use the Playwright Docker image for consistent rendering.
|
|
393
|
+
See `.claude/standards/testing-standards.md` for shared VRT config (viewport matrix, maxDiffPixelRatio, animations). Vue-specific settings:
|
|
394
|
+
- `baseURL` / `webServer.url`: `http://localhost:5173` (Vite) or `http://localhost:3000` (Nuxt)
|
|
395
|
+
- `webServer.command`: `nx serve [APP]`
|
|
396
|
+
- CI: use `mcr.microsoft.com/playwright:v[version]-jammy`
|
|
416
397
|
|
|
417
398
|
## Vue Review Checklist
|
|
418
399
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Architecture Standards
|
|
2
|
+
|
|
3
|
+
Structural and architectural rules for monorepo projects using Nx.
|
|
4
|
+
|
|
5
|
+
## App Separation Principle
|
|
6
|
+
|
|
7
|
+
- **`apps/` are thin shells** — each app is just configuration + routing + bootstrap. Minimal code.
|
|
8
|
+
- **`libs/` contain all logic** — each domain/feature has its **own library group** with sub-libraries per layer. Apps import only from entry points.
|
|
9
|
+
- **Feature-first library organization (STRICT)** — every distinct feature/domain gets its own group of libraries:
|
|
10
|
+
```
|
|
11
|
+
libs/
|
|
12
|
+
scan/ # feature: scan
|
|
13
|
+
domain/ # scan types, schemas, interfaces
|
|
14
|
+
ui/ # scan UI components
|
|
15
|
+
feature/ # scan smart components
|
|
16
|
+
data-access/ # scan HTTP/DB services
|
|
17
|
+
streak/ # feature: streak
|
|
18
|
+
domain/
|
|
19
|
+
ui/
|
|
20
|
+
feature/
|
|
21
|
+
achievement/ # feature: achievement
|
|
22
|
+
domain/
|
|
23
|
+
feature/
|
|
24
|
+
shared/ # ONLY truly cross-domain code
|
|
25
|
+
ui/ # generic UI primitives (buttons, modals)
|
|
26
|
+
domain/ # cross-domain types (if any)
|
|
27
|
+
infrastructure/
|
|
28
|
+
```
|
|
29
|
+
**NEVER dump multiple features into a single library** (e.g. `shared/types` with `scan.ts`, `streak.ts`, `achievement.ts`). Each feature owns its types in its own `domain/` library.
|
|
30
|
+
- **Frontend and backend are separate apps** with separate build configurations per environment:
|
|
31
|
+
- `apps/my-app/` — Frontend application
|
|
32
|
+
- `apps/api/` — Backend API (if applicable)
|
|
33
|
+
- **Environment configs**: each app has per-target settings via Nx `configurations` in `project.json` (API URLs, feature flags, etc.)
|
|
34
|
+
- **Entry point pattern**: each domain lib has a root `index.ts` that re-exports the public API. Apps and other libs import ONLY from `@myorg/<lib-name>`, never from deep paths like `@myorg/<lib-name>/src/lib/internal/...`
|
|
35
|
+
|
|
36
|
+
## CLI-First Principle
|
|
37
|
+
|
|
38
|
+
**ALWAYS prioritize CLI commands and generators over manual file creation.** This applies to:
|
|
39
|
+
- Nx workspace scaffolding (`create-nx-workspace`)
|
|
40
|
+
- Libraries, components, applications (`nx generate`)
|
|
41
|
+
- Adding plugins (`nx add`)
|
|
42
|
+
- Package installation (package manager CLI)
|
|
43
|
+
- Git operations (`gh`, `glab`)
|
|
44
|
+
|
|
45
|
+
NEVER manually create `project.json`, `tsconfig.*`, or boilerplate files that a generator would produce. Generators configure dependencies, paths, and targets correctly.
|
|
46
|
+
|
|
47
|
+
**NEVER use `nx:run-commands` executor** in project.json. Always use the proper built-in executor (`@nx/js:node` for serve, `@nx/vite:build` for build, `@nx/eslint:lint` for lint, etc.). `nx:run-commands` is an escape hatch for one-off scripts, not for standard targets.
|
|
48
|
+
|
|
49
|
+
## Nx Generators
|
|
50
|
+
|
|
51
|
+
**Generator defaults** are configured in `nx.json` — no need to repeat flags like `--unitTestRunner`, `--linter`, `--style` in every command. See `.claude/profiles/frontend.md` for framework-specific generator commands.
|
|
52
|
+
|
|
53
|
+
**Creating libraries:**
|
|
54
|
+
```bash
|
|
55
|
+
# Plain TypeScript library (for domain models, business logic, shared utils)
|
|
56
|
+
nx g @nx/js:library --directory=libs/[domain]/[layer] --no-interactive
|
|
57
|
+
|
|
58
|
+
# Framework-specific library — see frontend profile for the correct generator
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Bundler rule (STRICT):** Internal monorepo libs do NOT need a bundler — Nx handles compilation. NEVER install or configure tsup, tsdown, rollup, or any standalone bundler for internal libs. Only add `--bundler=esbuild|tsc|swc` to `nx generate` for **publishable** libraries (npm packages). If `nx generate` created a lib without `--bundler`, do NOT retroactively add one.
|
|
62
|
+
|
|
63
|
+
After generation, the architect's plan specifies what code to write IN the generated files. The generator handles project.json, tsconfig paths, and build targets.
|
|
64
|
+
|
|
65
|
+
## Onion Architecture (per domain)
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
+-----------------------------------------------------+
|
|
69
|
+
| apps/[app] |
|
|
70
|
+
| App shell — assembles features into routing |
|
|
71
|
+
+-----------------------------------------------------+
|
|
72
|
+
| feature/ |
|
|
73
|
+
| Smart components, orchestrate UI + data |
|
|
74
|
+
+-----------------------------------------------------+
|
|
75
|
+
| ui/ | data-access/ |
|
|
76
|
+
| Presentational components | HTTP / DB services |
|
|
77
|
+
| (dumb, input/output) | |
|
|
78
|
+
+-----------------------------------------------------+
|
|
79
|
+
| api/ |
|
|
80
|
+
| API routes, validation (if backend) |
|
|
81
|
+
+-----------------------------------------------------+
|
|
82
|
+
| business/ |
|
|
83
|
+
| Use cases, business logic |
|
|
84
|
+
+-----------------------------------------------------+
|
|
85
|
+
| domain/ |
|
|
86
|
+
| TypeBox schemas (single source of truth), types, |
|
|
87
|
+
| repository interfaces, value objects |
|
|
88
|
+
+-----------------------------------------------------+
|
|
89
|
+
| infrastructure/ |
|
|
90
|
+
| Repository implementations (database, API, mock) |
|
|
91
|
+
+-----------------------------------------------------+
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Dependency rules:
|
|
95
|
+
- domain -> only shared/domain
|
|
96
|
+
- business -> domain, shared/infrastructure/di
|
|
97
|
+
- api -> business, domain
|
|
98
|
+
- infrastructure -> domain, shared/infrastructure
|
|
99
|
+
- data-access -> domain (for types)
|
|
100
|
+
- ui -> shared/ui, domain (for types)
|
|
101
|
+
- feature -> ui, data-access, shared/ui
|
|
102
|
+
|
|
103
|
+
**TypeBox as single source of truth**: Define TypeBox schemas in `domain/` layer (`libs/[domain]/domain/`). These schemas are the canonical definition — derive everything from them:
|
|
104
|
+
- **TypeScript types**: `Static<typeof MySchema>` — no separate interface definitions
|
|
105
|
+
- **API validation**: `openAPIRouteHandler` uses the same schemas for request/response validation + OpenAPI spec
|
|
106
|
+
- **Frontend types**: Frontend imports types from domain lib via `@myorg/[domain]-domain` — no copy-paste
|
|
107
|
+
- **DB mapping**: Infrastructure layer maps between DB rows and domain types
|
|
108
|
+
- NEVER duplicate type definitions. If a type exists as a TypeBox schema, use `Static<>` everywhere.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Backend Patterns
|
|
2
|
+
|
|
3
|
+
Backend-specific rules for TypeScript server-side development. Consumed by `backend-ts-architect` and `senior-code-reviewer`.
|
|
4
|
+
|
|
5
|
+
## @cibule/* Ecosystem (Preferred Libraries)
|
|
6
|
+
|
|
7
|
+
**ALWAYS prefer `@cibule/*` packages** over custom implementations or third-party alternatives for: DI, database access, file storage, image processing, and Nx workspace scaffolding. These are driver-agnostic abstractions designed for the onion architecture pattern.
|
|
8
|
+
|
|
9
|
+
| Package | Purpose | Replaces |
|
|
10
|
+
|---------|---------|----------|
|
|
11
|
+
| `@cibule/di` | Dependency injection (InjectionToken, Injector, inject()) | Custom DI |
|
|
12
|
+
| `@cibule/db` | Database abstraction (Kysely-based, driver-agnostic) | Raw Kysely |
|
|
13
|
+
| `@cibule/db-d1` | Cloudflare D1 driver for @cibule/db | kysely-d1 |
|
|
14
|
+
| `@cibule/db-sqlite` | SQLite driver for @cibule/db (better-sqlite3) | Direct better-sqlite3 |
|
|
15
|
+
| `@cibule/db-migrate` | Migration orchestrator (Prisma + D1 PRAGMA fixes) | Manual migrations |
|
|
16
|
+
| `@cibule/storage` | File storage abstraction (upload, download, presigned URLs) | Custom storage |
|
|
17
|
+
| `@cibule/storage-r2` | Cloudflare R2 driver for @cibule/storage | Direct R2 API |
|
|
18
|
+
| `@cibule/storage-s3` | AWS S3 driver for @cibule/storage | Direct AWS SDK |
|
|
19
|
+
| `@cibule/image` | Image transformation abstraction | Custom image processing |
|
|
20
|
+
| `@cibule/image-sharp` | Sharp-based driver with FileStorage caching (Node.js) | Direct Sharp |
|
|
21
|
+
| `@cibule/image-cf` | Cloudflare Image Resizing driver | Direct CF API |
|
|
22
|
+
| `@cibule/wiring` | Per-request DI middleware, AsyncLocalStorage helpers | Custom middleware |
|
|
23
|
+
| `@cibule/devkit` | Nx generators for onion architecture scaffolding | Manual Nx setup |
|
|
24
|
+
|
|
25
|
+
**DI core API** (`@cibule/di`):
|
|
26
|
+
- `InjectionToken<T>` — typed token, `Injector.create({ providers, parent? })` — container
|
|
27
|
+
- `inject<T>(token)` / `inject<T>(token, { optional: true })` — resolve from context
|
|
28
|
+
- `runInInjectionContext(injector, fn)` — scoped execution
|
|
29
|
+
- Provider types: `useValue`, `useClass`, `useFactory`, `multi: true`
|
|
30
|
+
|
|
31
|
+
**Database pattern** (`@cibule/db` + driver):
|
|
32
|
+
```typescript
|
|
33
|
+
// Domain layer — driver-agnostic
|
|
34
|
+
import type { Db } from '@cibule/db';
|
|
35
|
+
import { DB_ACCESSOR, UNIT_OF_WORK_FACTORY } from '@cibule/db';
|
|
36
|
+
|
|
37
|
+
// Infrastructure — pick driver per platform
|
|
38
|
+
import { provideD1Db } from '@cibule/db-d1'; // Cloudflare Workers
|
|
39
|
+
import { provideSqliteDb } from '@cibule/db-sqlite'; // Node.js local dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Workspace scaffolding** (`@cibule/devkit`):
|
|
43
|
+
```bash
|
|
44
|
+
nx g @cibule/devkit:preset myorg # Initialize workspace with onion layers
|
|
45
|
+
nx g @cibule/devkit:scope market --frontend # Create domain scope
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Dependency bug handling**: If a bug is caused by a third-party dependency (not by project code), mark the task as `[~] BLOCKED` with the bug details and the package version. Continue with other tasks. The dependency freshness check at phase transitions will detect new versions — re-attempt blocked tasks after the package is updated.
|
|
49
|
+
|
|
50
|
+
## Backend Patterns
|
|
51
|
+
|
|
52
|
+
- Hono routes with validation (**TypeBox** — `typebox` for schema + type inference, `TypeCompiler` for runtime validation). **NEVER use `@sinclair/typebox`** — the correct package is `typebox`.
|
|
53
|
+
- **OpenAPI-first**: Use `hono-openapi` with `openAPIRouteHandler` for all API routes — generates OpenAPI spec from TypeBox schemas automatically. API docs served via `@scalar/hono-api-reference` at `/reference`.
|
|
54
|
+
- `@cibule/di` for dependency injection (see @cibule/* Ecosystem section above)
|
|
55
|
+
- `@cibule/db` + driver for database access, UnitOfWork for atomic writes
|
|
56
|
+
- `@cibule/storage` + driver for file storage, `@cibule/image` + driver for image processing
|
|
57
|
+
- `@cibule/wiring` for per-request DI middleware
|
|
58
|
+
|
|
59
|
+
## Platform-Agnostic Backend (STRICT)
|
|
60
|
+
|
|
61
|
+
- Backend code MUST run on both Node.js and Cloudflare Workers without modification
|
|
62
|
+
- NEVER use Node.js-specific APIs (`fs`, `path`, `process`, `Buffer`) in business/domain/api layers
|
|
63
|
+
- NEVER use Cloudflare-specific APIs (`env.DB`, `ctx.waitUntil()`) in business/domain layers
|
|
64
|
+
- Platform-specific code lives ONLY in `infrastructure/` layer behind abstract interfaces
|
|
65
|
+
- Use Web Standard APIs where possible: `fetch`, `Request`, `Response`, `URL`, `crypto`, `TextEncoder`/`TextDecoder`, `Headers`, `FormData`, `ReadableStream`
|
|
66
|
+
- **Platform-specific code MUST be in separate libraries (STRICT)** — NEVER mix Node.js and Cloudflare code in the same library. Barrel exports (`index.ts`) would pull both platforms into the bundle, causing compilation errors (e.g. `better-sqlite3` in Cloudflare, or D1 bindings in Node.js):
|
|
67
|
+
```
|
|
68
|
+
libs/[domain]/
|
|
69
|
+
infrastructure-d1/ # Cloudflare D1 implementation ONLY
|
|
70
|
+
infrastructure-sqlite/ # Node.js SQLite implementation ONLY
|
|
71
|
+
libs/shared/
|
|
72
|
+
infrastructure-d1/ # shared D1 providers (provideD1Database)
|
|
73
|
+
infrastructure-sqlite/ # shared SQLite providers (provideSqliteDatabase)
|
|
74
|
+
```
|
|
75
|
+
Each entry point imports ONLY its platform's library:
|
|
76
|
+
- Worker entry → `@myorg/shared-infrastructure-d1`
|
|
77
|
+
- Node entry → `@myorg/shared-infrastructure-sqlite`
|
|
78
|
+
- Hono is inherently platform-agnostic — use its standard APIs, avoid `c.env` directly in routes (inject via DI)
|
|
79
|
+
- Entry points handle platform wiring:
|
|
80
|
+
- `apps/api/src/main.ts` — Node.js entry (optional, for local dev)
|
|
81
|
+
- `apps/api/src/index.ts` — Cloudflare Worker entry (production)
|
|
82
|
+
- Default deployment: **Cloudflare Workers** (production), **Node.js** (local development via `@nx/js:node` executor + Node.js entry point). Wrangler is deployment-only — NEVER used for local dev or in Nx targets.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Coding Standards
|
|
2
|
+
|
|
3
|
+
Shared coding rules for all implementation agents and the code reviewer.
|
|
4
|
+
|
|
5
|
+
## File Organization & Size Limits
|
|
6
|
+
|
|
7
|
+
**Single-purpose files (STRICT):**
|
|
8
|
+
- Each `.ts` file exports ONE class, ONE function, or ONE closely related set of helpers (e.g., a type + its factory)
|
|
9
|
+
- Name files after their single export: `get-image.ts`, `parse-html.ts`, `price-calculator.ts`
|
|
10
|
+
- NEVER bundle unrelated exports into a barrel file like `utils.ts` or `helpers.ts` — split into individual files
|
|
11
|
+
- Index files (`index.ts`) are the ONLY exception — they re-export the public API of a library
|
|
12
|
+
|
|
13
|
+
**File size limits:**
|
|
14
|
+
- MAX 200 lines per TypeScript file — split into types, utils, sub-components if larger
|
|
15
|
+
- MAX 200 lines per SCSS file — split into partials
|
|
16
|
+
- MAX 100 lines per HTML template — decompose into child components
|
|
17
|
+
|
|
18
|
+
**Co-located tests:**
|
|
19
|
+
- Test files live next to the source file: `price-calculator.spec.ts` beside `price-calculator.ts`
|
|
20
|
+
- NEVER create `__tests__/` directories — tests are always co-located
|
|
21
|
+
- One test file per source file — do NOT bundle multiple files' tests together
|
|
22
|
+
|
|
23
|
+
## TypeScript Config
|
|
24
|
+
|
|
25
|
+
- `moduleResolution: "bundler"` in `tsconfig.base.json` — all code goes through a bundler, so use bundler-style resolution
|
|
26
|
+
- NEVER add `.js` extensions to TypeScript imports — write `import { Foo } from './foo'`, not `'./foo.js'`
|
|
27
|
+
- `module: "ESNext"` + `target: "ES2022"` (or as appropriate)
|
|
28
|
+
- `strict: true`, `noUncheckedIndexedAccess: true`
|
|
29
|
+
- Path aliases via `compilerOptions.paths` in `tsconfig.base.json` — managed by Nx generators, do not edit manually
|
|
30
|
+
|
|
31
|
+
## Coding Style
|
|
32
|
+
|
|
33
|
+
- No comments in code — write self-explanatory code
|
|
34
|
+
- Explicit access modifiers: `public` for API, `protected` for template-used, `private` for internal
|
|
35
|
+
- Import order enforced by `eslint-plugin-simple-import-sort` (automatic)
|
|
36
|
+
- Use `type` imports for type-only imports (`import type { Foo }`)
|
|
37
|
+
- DRY: 3+ repeats = extract to shared utility/component/directive
|
|
38
|
+
- Prefer composition over inheritance
|
|
39
|
+
- `readonly` on all fields that don't need reassignment
|
|
40
|
+
- No `any` — use `unknown` + type narrowing
|
|
41
|
+
- No non-null assertions (`!`) — handle nullability explicitly with narrowing, `??`, or early returns
|
|
42
|
+
- No `as unknown as T` double-casting — if types don't align, fix the types or use proper type guards
|
|
43
|
+
- No `eslint-disable` comments — NEVER suppress linter rules. If ESLint flags an error, fix the code instead of disabling the rule. This includes `eslint-disable-next-line`, `eslint-disable-line`, and block `eslint-disable` comments.
|
|
44
|
+
|
|
45
|
+
## Code Cleanliness (STRICT — clean code enables high coverage)
|
|
46
|
+
|
|
47
|
+
- **Pure functions first**: business logic and domain operations are pure — same input, same output, no side effects. This makes them trivially testable.
|
|
48
|
+
- **Minimal branching**: avoid nested conditionals (max 2 levels). Prefer early returns, lookup maps/objects, `??`/`?.` chains, polymorphism. If a function has 4+ branches, split it.
|
|
49
|
+
- **Side effects at boundaries**: I/O (HTTP, DB, logging, DOM) happens at the edges — infrastructure layer, entry points. Business and domain code stays pure.
|
|
50
|
+
- **Data transformations over mutations**: prefer `map`/`filter`/spread/new objects over `push`/`splice`/`delete`/mutation. Immutable data is predictable and testable.
|
|
51
|
+
- **Small focused functions**: each function does ONE thing. Max ~20 lines of logic. Extract complex expressions into named pure helpers.
|
|
52
|
+
- **Declarative over imperative**: describe WHAT, not HOW. Prefer expressions over statement sequences.
|
|
53
|
+
|
|
54
|
+
## Modern JavaScript Syntax (STRICT — enforced by ESLint)
|
|
55
|
+
|
|
56
|
+
- `for...of` over `.forEach()` — clearer, supports `break`/`continue`/`await`
|
|
57
|
+
- `const` by default — `let` only when reassignment is needed, NEVER `var`
|
|
58
|
+
- Arrow functions in callbacks — `array.map(x => x.id)`, not `array.map(function(x) { ... })`
|
|
59
|
+
- Template literals — `` `Hello ${name}` ``, not `'Hello ' + name`
|
|
60
|
+
- Object shorthand — `{ name, age }`, not `{ name: name, age: age }`
|
|
61
|
+
- Destructuring — `const { id, name } = user`, not `const id = user.id`
|
|
62
|
+
- Nullish coalescing `??` over `||` for defaults (avoids falsy pitfalls)
|
|
63
|
+
- Optional chaining `?.` over nested `if` checks
|
|
64
|
+
- `Promise.all()` for parallel async, `for await...of` for async iteration
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
Testing, coverage, VRT, and code quality tooling standards. Consumed by `test-engineer`, `senior-code-reviewer`, and framework-specific engineers.
|
|
4
|
+
|
|
5
|
+
## Test Coverage
|
|
6
|
+
|
|
7
|
+
80% minimum for statements, branches, functions, lines (enforced in CI).
|
|
8
|
+
|
|
9
|
+
- **Config template**: `.claude/templates/vitest.coverage.ts` — merge into each project's `vite.config.ts` or `vitest.config.ts` during scaffolding.
|
|
10
|
+
- Reporters: `cobertura` (GitLab/GitHub MR coverage), `html` (browsable), `text-summary`
|
|
11
|
+
- Coverage aggregated at workspace root under `coverage/` (add to `.gitignore`)
|
|
12
|
+
- Run: `nx test [PROJECT] --coverage` or `nx affected --target=test -- --coverage`
|
|
13
|
+
|
|
14
|
+
## Visual Regression Testing
|
|
15
|
+
|
|
16
|
+
Page-level VRT using Playwright `toHaveScreenshot()` at 3 viewports (375px, 768px, 1280px). Catches unintended visual regressions across pages.
|
|
17
|
+
|
|
18
|
+
- **Files**: `*.vrt.spec.ts` in E2E project (co-located with functional E2E tests)
|
|
19
|
+
- **Baselines**: `*-snapshots/` directories — committed to git
|
|
20
|
+
- **Threshold**: `maxDiffPixelRatio: 0.01` (1%), `threshold: 0.2` (per-pixel YIQ), animations disabled
|
|
21
|
+
- **Cross-platform**: Baselines MUST be generated in Docker (Linux). Remove `{platform}` from `snapshotPathTemplate` when VRT runs exclusively in Docker
|
|
22
|
+
- **Update baselines**: run inside Docker: `docker run --rm -v $(pwd):/work -w /work mcr.microsoft.com/playwright:v1.52.0-jammy npx playwright test --grep "Visual Regression" --update-snapshots`
|
|
23
|
+
- **CI**: use Playwright Docker image (`mcr.microsoft.com/playwright`) — same version as baseline generation
|
|
24
|
+
- **Font wait**: Always `await page.waitForFunction(() => document.fonts.ready)` before screenshot. Self-host fonts for deterministic rendering.
|
|
25
|
+
- **No `networkidle`**: Banned by `playwright/no-networkidle` lint rule. Use font wait + element visibility + `toHaveScreenshot()` auto-retry instead.
|
|
26
|
+
- **Auto-retry**: `toHaveScreenshot()` takes consecutive screenshots until two consecutive captures match, then compares against baseline. Handles most rendering settling automatically.
|
|
27
|
+
|
|
28
|
+
### Shared Playwright VRT config
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { defineConfig } from '@playwright/test';
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
testDir: './src',
|
|
35
|
+
testMatch: ['**/*.spec.ts', '**/*.vrt.spec.ts'],
|
|
36
|
+
fullyParallel: true,
|
|
37
|
+
retries: process.env.CI ? 2 : 0,
|
|
38
|
+
// Remove {platform} — baselines generated in Docker (Linux-only)
|
|
39
|
+
snapshotPathTemplate: '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
|
|
40
|
+
use: {
|
|
41
|
+
baseURL: 'http://localhost:<PORT>', // Framework-specific: Angular 4200, React/Next 3000, Vite 5173
|
|
42
|
+
trace: 'on-first-retry',
|
|
43
|
+
},
|
|
44
|
+
expect: {
|
|
45
|
+
toHaveScreenshot: {
|
|
46
|
+
maxDiffPixelRatio: 0.01,
|
|
47
|
+
threshold: 0.2,
|
|
48
|
+
animations: 'disabled',
|
|
49
|
+
scale: 'css',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
webServer: {
|
|
53
|
+
command: 'nx serve [APP]',
|
|
54
|
+
url: 'http://localhost:<PORT>',
|
|
55
|
+
reuseExistingServer: !process.env.CI,
|
|
56
|
+
timeout: 120_000,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Viewport matrix
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const VIEWPORTS = [
|
|
65
|
+
{ name: 'mobile', width: 375, height: 812 },
|
|
66
|
+
{ name: 'tablet', width: 768, height: 1024 },
|
|
67
|
+
{ name: 'desktop', width: 1280, height: 720 },
|
|
68
|
+
] as const;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Code Quality & Linting
|
|
72
|
+
|
|
73
|
+
Config templates are in `.claude/templates/` — copy to project root during Phase 0 scaffolding:
|
|
74
|
+
- **ESLint**: `.claude/templates/eslint.config.js` → `eslint.config.js` (flat config, `@typescript-eslint/strict-type-checked` + `@nx/eslint-plugin` + `eslint-plugin-simple-import-sort` + `eslint-plugin-prefer-arrow-functions`). Add framework-specific rules from `.claude/profiles/frontend.md`.
|
|
75
|
+
- **Prettier**: `.claude/templates/.prettierrc` → `.prettierrc`, `.claude/templates/.prettierignore` → `.prettierignore`
|
|
76
|
+
- **Stylelint**: `.claude/templates/.stylelintrc.json` → `.stylelintrc.json` (SCSS only, forces theme tokens over hardcoded colors)
|
|
77
|
+
- **Pre-commit hooks**: `.claude/templates/.lefthook.yml` → `.lefthook.yml`
|
|
78
|
+
|
|
79
|
+
Each library's `project.json` must declare tags: `"tags": ["type:domain", "scope:auth"]`
|
|
80
|
+
|
|
81
|
+
Key rules enforced (do NOT weaken):
|
|
82
|
+
- `prefer-arrow-functions/prefer-arrow-functions` — const arrow over function declarations
|
|
83
|
+
- `max-classes-per-file: 1`, `max-depth: 4`
|
|
84
|
+
- `@typescript-eslint/explicit-member-accessibility` — explicit public/private/protected
|
|
85
|
+
- `@typescript-eslint/prefer-readonly` — readonly where possible
|
|
86
|
+
- `@typescript-eslint/no-non-null-assertion`, `no-explicit-any`, `consistent-type-imports`, `strict-boolean-expressions`
|
|
87
|
+
- `no-warning-comments: ['eslint-disable']` — no eslint-disable comments
|
|
88
|
+
- `@nx/enforce-module-boundaries` — onion architecture layer constraints
|
|
89
|
+
- `eslint-config-prettier` installed to disable conflicting rules. Do NOT use `eslint-plugin-prettier`.
|
|
90
|
+
|
|
91
|
+
**Enforcement:**
|
|
92
|
+
- All three tools run in CI pipeline (fail on violations)
|
|
93
|
+
- `nx affected --target=lint` for incremental linting
|
|
94
|
+
- Pre-commit hooks prevent committing violations
|