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.
package/dist/scheduler/index.mjs
CHANGED
|
@@ -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;
|
package/dist/scheduler/loop.mjs
CHANGED
|
@@ -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,
|
|
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`
|
|
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)
|
|
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)
|