create-claude-workspace 2.3.21 → 2.3.22

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.
@@ -5,7 +5,7 @@ import { resolve } from 'node:path';
5
5
  import { existsSync, mkdirSync } from 'node:fs';
6
6
  import { config as dotenvConfig } from '@dotenvx/dotenvx';
7
7
  import { SCHEDULER_DEFAULTS } from './types.mjs';
8
- import { detectCIPlatform } from './git/ci-watcher.mjs';
8
+ import { detectCIPlatform, checkPlatformCLI, installPlatformCLI } from './git/ci-watcher.mjs';
9
9
  import { fetchOpenIssues } from './tasks/issue-source.mjs';
10
10
  import { emptyState, readState, writeState, appendEvent, createEvent } from './state/state.mjs';
11
11
  import { WorkerPool } from './agents/worker-pool.mjs';
@@ -256,6 +256,26 @@ export async function runScheduler(opts) {
256
256
  else {
257
257
  state = emptyState(opts.concurrency);
258
258
  }
259
+ // Ensure platform CLI is available before task mode detection
260
+ const detectedPlatform = detectCIPlatform(opts.projectDir);
261
+ if (detectedPlatform !== 'none') {
262
+ const cliError = checkPlatformCLI(detectedPlatform);
263
+ if (cliError) {
264
+ const cli = detectedPlatform === 'github' ? 'gh' : 'glab';
265
+ logger.warn(`${cliError} — attempting auto-install of ${cli}`);
266
+ const installResult = installPlatformCLI(detectedPlatform);
267
+ if (installResult.success) {
268
+ logger.info(`Installed ${cli} via ${installResult.method}`);
269
+ const authError = checkPlatformCLI(detectedPlatform);
270
+ if (authError?.includes('not authenticated')) {
271
+ logger.warn(`${cli} installed but needs authentication. Run: ${cli} auth login`);
272
+ }
273
+ }
274
+ else {
275
+ logger.error(`Failed to install ${cli}: ${installResult.error}`);
276
+ }
277
+ }
278
+ }
259
279
  // Task mode detection
260
280
  if (opts.taskMode) {
261
281
  state.taskMode = opts.taskMode;
@@ -11,7 +11,7 @@ import { recordSession, getSession, clearSession } from './state/session.mjs';
11
11
  import { createWorktree, commitInWorktree, getChangedFiles, cleanupWorktree, mergeToMain, syncMain, pushWorktree, forcePushWorktree, rebaseOnMain, cleanMainForMerge, popStash, getMainBranch, listWorktrees, listOrphanedWorktrees, isBranchMerged, getCurrentBranch, hasUncommittedChanges, deleteBranchRemote, cleanMergedBranches, pruneRemoteBranches, findStaleUnmergedBranches, } from './git/manager.mjs';
12
12
  import { createPR, getPRStatus, getPRComments, mergePR, closePR } from './git/pr-manager.mjs';
13
13
  import { scanAgents } from './agents/health-checker.mjs';
14
- import { detectCIPlatform, checkPlatformCLI, installPlatformCLI, fetchFailureLogs } from './git/ci-watcher.mjs';
14
+ import { detectCIPlatform, fetchFailureLogs } from './git/ci-watcher.mjs';
15
15
  import { createRelease } from './git/release.mjs';
16
16
  import { processInbox, addTaskMessageToTask } from './tasks/inbox.mjs';
17
17
  import { buildPlanPrompt, buildImplementPrompt, buildQAPrompt, buildReviewPrompt, buildReworkPrompt, buildCIFixPrompt, buildPRCommentPrompt, } from './agents/prompt-builder.mjs';
@@ -62,29 +62,6 @@ export async function runIteration(deps) {
62
62
  // Caller (scheduler.mts) handles pause state
63
63
  }
64
64
  }
65
- // Pre-flight: ensure platform CLI is available
66
- if (!state._recoveryDone) {
67
- const ciPlatformPreflight = detectCIPlatform(projectDir);
68
- if (ciPlatformPreflight !== 'none') {
69
- const cliError = checkPlatformCLI(ciPlatformPreflight);
70
- if (cliError) {
71
- const cli = ciPlatformPreflight === 'github' ? 'gh' : 'glab';
72
- logger.warn(`${cliError} — attempting auto-install of ${cli}`);
73
- const installResult = installPlatformCLI(ciPlatformPreflight);
74
- if (installResult.success) {
75
- logger.info(`Installed ${cli} via ${installResult.method}`);
76
- // Check auth after install
77
- const authError = checkPlatformCLI(ciPlatformPreflight);
78
- if (authError?.includes('not authenticated')) {
79
- logger.warn(`${cli} installed but needs authentication. Run: ${cli} auth login`);
80
- }
81
- }
82
- else {
83
- logger.error(`Failed to install ${cli}: ${installResult.error}`);
84
- }
85
- }
86
- }
87
- }
88
65
  // Pre-flight: commit uncommitted changes on main before any work
89
66
  if (!state._recoveryDone) {
90
67
  if (hasUncommittedChanges(projectDir)) {
@@ -568,7 +568,7 @@ To determine if a task is frontend, backend, or fullstack, use this heuristic:
568
568
  - Ask for: TEST PLAN per layer, then full implementation, then RUN all tests and report results
569
569
  - **E2E tests**: Include in the prompt: "Write E2E tests for the following user flows: [list flows from TESTING section]. Use Playwright. Ensure E2E project is set up (scaffold via `nx g @nx/playwright:configuration` if missing)."
570
570
  - **Integration tests**: Include in the prompt: "Write integration tests for: [list endpoints/services from TESTING section]. Test real request/response cycles (use test server or supertest), database operations against test DB, and public API contracts. Keep integration tests in `*.integration.spec.ts` files (co-located next to source, same as unit tests)."
571
- - **Visual regression tests**: Include in the prompt: "Write visual regression tests (`*.vrt.spec.ts`) for these pages/views: [list from TESTING section]. Use the VRT template with 3-viewport matrix (mobile 375px, tablet 768px, desktop 1280px). Use `maxDiffPixelRatio: 0.01` and `fullPage: true` for page screenshots. Mask dynamic content (timestamps, user avatars) with `mask` option."
571
+ - **Visual regression tests**: Include in the prompt: "Write visual regression tests (`*.vrt.spec.ts`) for these pages/views: [list from TESTING section]. Use the VRT template with 3-viewport matrix (mobile 375px, tablet 768px, desktop 1280px). Use `maxDiffPixelRatio: 0.01`. For page-level VRT use `expect(page)` with `fullPage: true`. For section/component VRT (hero, header, modal, etc.) use `expect(page.locator('...'))` to screenshot only the element — NEVER use `fullPage: true` on `page` for section-level tests. Mask dynamic content (timestamps, user avatars) with `mask` option. After generating baselines, visually verify every screenshot with the Read tool — check that each captures the correct scope (element vs full page) and shows fully rendered content."
572
572
  - **Baseline updates for intentional visual changes**: If the architect's plan describes intentional visual changes to existing pages (redesign, new theme, layout restructure), add to the test-engineer prompt: "Existing VRT baselines will need updating. After writing/updating VRT tests, run `nx e2e [APP]-e2e -- --update-snapshots` to regenerate baselines. Include the updated baseline images in the commit."
573
573
  - **Coverage requirement**: Include in the prompt: "Run ALL tests with `--coverage`. Coverage MUST stay above 80% (statements, branches, functions, lines). If coverage drops below 80%, add missing tests before returning."
574
574
  - **E2E tests**: Include in the prompt: "Start dev server, wait for readiness, run E2E tests, then kill the dev server port. Use `bunx kill-port 4200` for cleanup."
@@ -309,11 +309,17 @@ Automated pixel-by-pixel comparison against baseline screenshots. Catches uninte
309
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
310
 
311
311
  ### When to write VRT tests
312
- - **Page-level views** — each route/page gets a VRT test covering default state
312
+ - **Page-level views** — each route/page gets a VRT test covering default state. Use `expect(page).toHaveScreenshot(name, { fullPage: true })`
313
+ - **Key sections/components** — hero, header, footer, sidebar, card grid, modal, form. Use `expect(page.locator('...')).toHaveScreenshot(name)` — screenshots the **element only**, not the full page
313
314
  - **Key interactive states** — modal open, form validation errors, empty state, loaded state, skeleton/loading
314
315
  - **Multi-component compositions** — dashboards, listings, onboarding flows
315
316
  - **Do NOT write VRT for**: individual dumb components in isolation, backend-only tasks, pages with highly dynamic content (live charts, video feeds, real-time data)
316
317
 
318
+ **Choosing page vs element screenshot:**
319
+ - If the test name describes the ENTIRE page (e.g., "checkout page default") → `expect(page)` + `fullPage: true`
320
+ - If the test name describes a SECTION or COMPONENT (e.g., "hero section", "pricing cards") → `expect(locator)` without `fullPage`
321
+ - **Rule of thumb**: the screenshot name should match what's actually captured. A file named `hero-desktop.png` must show only the hero, not the whole page.
322
+
317
323
  ### VRT file naming
318
324
  - `*.vrt.spec.ts` suffix — co-located with functional E2E tests in `apps/[APP]-e2e/src/` or `e2e/`
319
325
  - Example: `checkout.vrt.spec.ts` beside `checkout.spec.ts`
@@ -338,6 +344,7 @@ const VIEWPORTS = [
338
344
  { name: 'desktop', width: 1280, height: 720 },
339
345
  ] as const;
340
346
 
347
+ // PAGE-LEVEL VRT — use fullPage: true, subject is the entire page
341
348
  test.describe('Checkout Page — Visual Regression', () => {
342
349
  for (const vp of VIEWPORTS) {
343
350
  test.describe(vp.name, () => {
@@ -345,11 +352,8 @@ test.describe('Checkout Page — Visual Regression', () => {
345
352
 
346
353
  test('default state', async ({ page }) => {
347
354
  await page.goto('/checkout');
348
- // Wait for fonts — critical for consistent rendering
349
355
  await page.waitForFunction(() => document.fonts.ready);
350
- // Wait for key content to be visible
351
356
  await expect(page.locator('[data-testid="checkout-form"]')).toBeVisible();
352
- // toHaveScreenshot() auto-retries until two consecutive captures match
353
357
  await expect(page).toHaveScreenshot(`checkout-default-${vp.name}.png`, {
354
358
  fullPage: true,
355
359
  });
@@ -359,7 +363,28 @@ test.describe('Checkout Page — Visual Regression', () => {
359
363
  await page.goto('/checkout');
360
364
  await page.waitForFunction(() => document.fonts.ready);
361
365
  await page.getByRole('button', { name: 'Pay' }).click();
362
- await expect(page).toHaveScreenshot(`checkout-errors-${vp.name}.png`);
366
+ await expect(page).toHaveScreenshot(`checkout-errors-${vp.name}.png`, {
367
+ fullPage: true,
368
+ });
369
+ });
370
+ });
371
+ }
372
+ });
373
+
374
+ // SECTION-LEVEL VRT — screenshot a specific element, NOT the full page
375
+ // Use this for hero sections, headers, modals, forms, cards, etc.
376
+ test.describe('Landing Page Hero — Visual Regression', () => {
377
+ for (const vp of VIEWPORTS) {
378
+ test.describe(vp.name, () => {
379
+ test.use({ viewport: { width: vp.width, height: vp.height } });
380
+
381
+ test('hero section', async ({ page }) => {
382
+ await page.goto('/');
383
+ await page.waitForFunction(() => document.fonts.ready);
384
+ const hero = page.locator('[data-testid="hero-section"]');
385
+ await expect(hero).toBeVisible();
386
+ // Screenshot the ELEMENT, not the page — captures only the hero
387
+ await expect(hero).toHaveScreenshot(`hero-${vp.name}.png`);
363
388
  });
364
389
  });
365
390
  }
@@ -388,7 +413,7 @@ export default defineConfig({
388
413
  - `threshold: 0.2` — per-pixel YIQ color difference tolerance (0 = exact, 1 = anything)
389
414
  - `animations: 'disabled'` — finite animations fast-forwarded to end, infinite reset
390
415
  - `scale: 'css'` — consistent screenshot size regardless of device pixel ratio
391
- - `fullPage: true` for page-level screenshots (captures below-fold), omit for component-state
416
+ - `fullPage: true` for page-level screenshots (captures below-fold content). **Only use for page-level VRT** where the entire page is the subject. For section/component-state VRT (hero, header, modal, form), use **element-level screenshots** instead — see below
392
417
  - `mask: Locator[]` — overlay elements with colored boxes (hides dynamic content)
393
418
  - `stylePath: string | string[]` — CSS file injected before screenshot, **penetrates Shadow DOM** (unlike `mask`)
394
419
 
@@ -424,6 +449,25 @@ Remove `{platform}` from the path since all baselines are Linux. Set in `playwri
424
449
  - **First run** creates baselines (auto-retry takes two identical screenshots to confirm stability)
425
450
  - In CI: use the Playwright Docker image (`mcr.microsoft.com/playwright`) — same version as local Docker generation
426
451
 
452
+ ### Baseline validation (MANDATORY after first run)
453
+
454
+ After generating baselines (first run or `--update-snapshots`), you MUST visually verify every generated screenshot before committing:
455
+
456
+ 1. **Read each baseline image** using the Read tool — you can view images directly
457
+ 2. **Verify correct scope**: Does the screenshot capture ONLY the intended subject?
458
+ - Page-level VRT (`fullPage: true` on `page`) → should show the entire page with all sections
459
+ - Section/component VRT (on `locator`) → should show ONLY that section/component, not the whole page
460
+ - **Common mistake**: using `expect(page).toHaveScreenshot()` with `fullPage: true` for a section-level test (e.g., "hero") — this captures the entire page instead of just the hero. Fix: screenshot the element locator, not the page
461
+ 3. **Verify content is rendered**: Screenshot must show actual content, not blank/loading/skeleton state
462
+ 4. **Verify no visual artifacts**: No overlapping elements, no broken layout, no missing images
463
+ 5. **If any screenshot is wrong**: fix the test (wrong locator, wrong wait, page vs element), delete the bad baseline, re-run to generate correct one, and re-validate
464
+
465
+ **Quick validation checklist per screenshot:**
466
+ - [ ] Shows the correct scope (element vs full page)
467
+ - [ ] Content is fully rendered (not loading/skeleton)
468
+ - [ ] No visual artifacts or layout breaks
469
+ - [ ] Responsive variant looks correct for its viewport size
470
+
427
471
  ### Running VRT tests
428
472
  ```bash
429
473
  # Run VRT via Nx (CI or Docker)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-workspace",
3
- "version": "2.3.21",
3
+ "version": "2.3.22",
4
4
  "author": "",
5
5
  "repository": {
6
6
  "type": "git",