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.
@@ -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
- **Co-located tests** every `.spec.ts` file lives next to the source file it tests:
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 `*.vrt.spec.ts-snapshots/` directories — **commit these to git**
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
- await page.waitForLoadState('networkidle');
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 configuration
368
- - `maxDiffPixelRatio: 0.01` (1%) tolerates minor anti-aliasing differences across environments
369
- - `fullPage: true` for page-level screenshots (captures below-fold content)
370
- - Omit `fullPage` for component-state screenshots (captures only the viewport)
371
- - Screenshot name includes viewport name — ensures unique baselines per breakpoint
372
- - `animations: 'disabled'` in Playwright config `expect.toHaveScreenshot` — prevents flaky diffs from CSS transitions
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` flag, then commit new baselines
377
- - **First run** always fails (no baselines exist) run twice: first creates baselines, second verifies
378
- - In CI: use the Playwright Docker image (`mcr.microsoft.com/playwright`) for consistent rendering
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
- # Update baselines after intentional visual changes
386
- nx e2e [APP]-e2e -- --update-snapshots
440
+ ### Wait strategy before screenshots
441
+ Do NOT use `networkidle` — it is banned by `playwright/no-networkidle` lint rule.
387
442
 
388
- # Run only VRT tests
389
- nx e2e [APP]-e2e -- --grep "Visual Regression"
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 `await` content visibility before screenshot: `await expect(page.getByText('...')).toBeVisible()`
394
- - Use `page.waitForLoadState('networkidle')` before page screenshots if the page fetches data
395
- - Mask dynamic content (timestamps, avatars, user-specific data) using `mask` option:
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
- Read the target project's CLAUDE.md `Test Coverage` section for the canonical Vitest coverage configuration (provider, reporters, thresholds, include/exclude patterns, reportsDirectory). If no CLAUDE.md exists or it lacks a Test Coverage section, use these defaults:
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
- Add to `playwright.config.ts` in the E2E project:
388
- ```typescript
389
- import { defineConfig } from '@playwright/test';
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