prjct-cli 1.19.0 → 1.21.0

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/CHANGELOG.md CHANGED
@@ -1,11 +1,135 @@
1
1
  # Changelog
2
2
 
3
- ## [1.19.0] - 2026-02-09
3
+ ## [1.21.0] - 2026-02-10
4
+
5
+ ### Features
6
+
7
+ - add semantic verification for analysis results (PRJ-270) (#163)
8
+
9
+
10
+ ## [1.20.0] - 2026-02-10
4
11
 
5
12
  ### Features
6
13
 
7
- - implement aggressive archival of stale storage data (PRJ-267) (#161)
14
+ - add retry with exponential backoff for agent and tool operations (#162)
15
+
16
+
17
+ ## [1.20.0] - 2026-02-09
18
+
19
+ ### Features
20
+
21
+ - **Semantic verification for analysis results** (PRJ-270): Validate analysis consistency before sealing
22
+ - Framework verification: checks frameworks exist in package.json dependencies (case-insensitive matching)
23
+ - Language verification: validates languages match actual file extensions (.ts → TypeScript)
24
+ - Pattern location verification: confirms pattern files exist in project
25
+ - File count verification: validates count accuracy (within 10% tolerance)
26
+ - Anti-pattern file verification: ensures anti-pattern files exist when referenced
27
+ - CLI command: `prjct verify --semantic` with human-readable and JSON output
28
+ - Parallel execution of all 5 checks using Promise.all() (~100-200ms for typical projects)
29
+ - Comprehensive test suite with 10 new test cases covering all scenarios (28 tests total, 63 assertions)
30
+
31
+ - **Retry with exponential backoff for agent and tool operations** (PRJ-271): Comprehensive retry infrastructure with error classification and circuit breaker
32
+ - RetryPolicy utility with configurable attempts, delays, and exponential backoff (1s→2s→4s)
33
+ - Automatic error classification: transient (EBUSY, EAGAIN, ETIMEDOUT) vs permanent (ENOENT, EPERM)
34
+ - Circuit breaker protection: opens after 5 consecutive failures, auto-closes after 60s
35
+ - Agent initialization retries (3 attempts with 1s base delay)
36
+ - Tool operations retry (Read/Write/Bash with 2 attempts)
37
+ - Resilient parallel agent generation using Promise.allSettled()
38
+
39
+ ### Implementation Details
40
+
41
+ #### Semantic Verification (PRJ-270)
42
+
43
+ Added semantic verification functions to validate analysis results match actual project state. The verification system runs 5 parallel checks to detect logical inconsistencies before sealing analysis data.
44
+
45
+ **Modified modules:**
46
+ - `core/schemas/analysis.ts` — Added 5 verification functions, 1 orchestrator, 2 helpers, and 2 interfaces (SemanticCheckResult, SemanticVerificationReport)
47
+ - `core/storage/analysis-storage.ts` — Added semanticVerify() method to AnalysisStorage class
48
+ - `core/commands/analysis.ts` — Extended verify() with --semantic flag and added semanticVerify() helper
49
+ - `core/commands/commands.ts` — Updated verify() signature to include semantic?: boolean
50
+ - `core/index.ts` — Updated verify command handler to pass semantic flag
51
+ - `core/__tests__/storage/analysis-storage.test.ts` — Added 10 comprehensive test cases (+345 lines)
52
+ - `README.md` — Added analysis verification documentation section
53
+ - `CHANGELOG.md` — Documented semantic verification feature
54
+
55
+ **Verification functions:**
56
+ 1. `verifyFrameworks()` — Checks frameworks exist in package.json (case-insensitive partial matching)
57
+ 2. `verifyLanguages()` — Validates languages match file extensions (.ts → TypeScript)
58
+ 3. `verifyPatternLocations()` — Confirms pattern files exist in project
59
+ 4. `verifyFileCount()` — Validates count accuracy (10% tolerance for temporary files/caches)
60
+ 5. `verifyAntiPatternFiles()` — Ensures anti-pattern files exist when referenced
61
+
62
+ **Helper functions:**
63
+ - `getProjectExtensions()` — Recursively scans project for file extensions (ignores node_modules, .git, dist, build, .next, .turbo, coverage)
64
+ - `countProjectFiles()` — Counts all files in project with same ignore patterns
65
+
66
+ **Key features:**
67
+ - Parallel execution using Promise.all() for performance (~100-200ms typical)
68
+ - Skip conditions for null/undefined/empty arrays (prevents false failures)
69
+ - 10% tolerance for file count (accounts for temporary files, caches)
70
+ - Case-insensitive partial matching for frameworks
71
+ - Returns SemanticVerificationReport following VerificationReport pattern from sync-verifier
72
+ - Zero breaking changes: all 18 existing tests pass
73
+
74
+ #### Retry Infrastructure (PRJ-271)
75
+
76
+ Built RetryPolicy utility with exponential backoff, error classification, and circuit breaker. Integrated across agent initialization, tool operations, and parallel agent generation. The system now automatically retries transient failures while failing fast on permanent errors.
77
+
78
+ **New modules:**
79
+ - `core/utils/retry.ts` (320 lines) — Core retry infrastructure with RetryPolicy class, error classification, circuit breaker
80
+ - `core/__tests__/utils/retry.test.ts` (380 lines) — 21 comprehensive tests with 53 assertions
81
+ - `ACCEPTANCE-PRJ-271.md` — Full acceptance criteria verification (22 criteria verified)
82
+
83
+ **Modified modules:**
84
+ - `core/services/agent-service.ts` — Wrapped initialize() with retry policy (3 attempts)
85
+ - `core/agentic/tool-registry.ts` — Added retry to Read/Write/Bash tools (2 attempts each)
86
+ - `core/services/agent-generator.ts` — Changed to Promise.allSettled() with per-agent retry
87
+
88
+ **Key features:**
89
+ - Exponential backoff: 1s, 2s, 4s (configurable base/max)
90
+ - Error classification: automatic transient vs permanent detection
91
+ - Circuit breaker: per-operation tracking, 5 failure threshold, 60s cooldown
92
+ - Two default policies: defaultAgentRetryPolicy (3 attempts), defaultToolRetryPolicy (2 attempts)
93
+ - Zero breaking changes: all 968 existing tests pass
94
+
95
+ ### Learnings
96
+
97
+ - **RetryPolicy pattern:** Wrapping operations with retry execution provides clean separation of retry logic from business logic
98
+ - **Error classification strategies:** Using error code sets (EBUSY, EAGAIN) for transient vs (ENOENT, EPERM) for permanent enables automatic decision-making
99
+ - **Promise.allSettled() for resilient parallel operations:** Prevents one failure from blocking other operations, enables partial success
100
+ - **Circuit breaker implementation:** Per-operation state tracking prevents cascading failures while allowing recovery
101
+
102
+ ### Test Plan
103
+
104
+ #### For QA
105
+
106
+ 1. **Agent Initialization Retry**
107
+ - Temporarily make file system busy during agent initialization
108
+ - Verify agent initialization retries up to 3 times
109
+ - Confirm permanent errors (unsupported agent) fail immediately
110
+
111
+ 2. **Tool Operations Retry**
112
+ - Test Read/Write/Bash with transient errors (EBUSY, ETIMEDOUT)
113
+ - Verify operations retry automatically (2 attempts)
114
+ - Confirm permanent errors (ENOENT, EPERM) return null/false without retry
115
+
116
+ 3. **Circuit Breaker**
117
+ - Trigger 5 consecutive failures on same operation
118
+ - Verify circuit breaker opens and blocks further attempts
119
+ - Wait 60 seconds and verify circuit closes automatically
120
+
121
+ 4. **Parallel Agent Generation**
122
+ - Simulate one agent generation failure during sync
123
+ - Verify other agents generate successfully (Promise.allSettled behavior)
124
+ - Check logs for failure warnings
125
+
126
+ #### For Users
127
+
128
+ **What changed:** The system is now more resilient against transient failures. Operations like agent initialization, file reads/writes, and command execution will automatically retry when they encounter temporary errors (disk busy, timeouts, etc).
129
+
130
+ **How to use:** No action required - retry logic works automatically. Users will experience fewer random failures during normal operations.
8
131
 
132
+ **Breaking changes:** None. All changes are backward compatible. Existing tests (968 total) all pass.
9
133
 
10
134
  ## [1.19.0] - 2026-02-09
11
135
 
package/README.md CHANGED
@@ -128,10 +128,59 @@ All agents share the same project storage, so you can switch between them freely
128
128
  ```bash
129
129
  prjct start # First-time setup (Claude/Gemini)
130
130
  prjct init # Initialize project (+ Cursor setup)
131
+ prjct sync # Analyze project and generate context
132
+ prjct verify # Verify analysis integrity (cryptographic)
133
+ prjct verify --semantic # Verify analysis consistency (semantic)
131
134
  prjct --version # Show version + provider status
132
135
  prjct --help # Show help
133
136
  ```
134
137
 
138
+ ### Analysis Verification
139
+
140
+ prjct provides two types of analysis verification to ensure data integrity and logical consistency:
141
+
142
+ #### Cryptographic Verification (Default)
143
+ ```bash
144
+ prjct verify [--json]
145
+ ```
146
+
147
+ Verifies the integrity of sealed analysis results using cryptographic signatures. This ensures:
148
+ - Analysis data hasn't been tampered with
149
+ - Sealed analysis matches the original analysis
150
+ - Hash signatures are valid
151
+
152
+ **When to use:** After sealing an analysis (`prjct seal`) to confirm data integrity.
153
+
154
+ #### Semantic Verification (PRJ-270)
155
+ ```bash
156
+ prjct verify --semantic [--json]
157
+ ```
158
+
159
+ Validates that analysis results match the actual project state. This checks:
160
+ - ✓ **Frameworks** exist in `package.json` dependencies
161
+ - ✓ **Languages** match actual file extensions (.ts → TypeScript)
162
+ - ✓ **Pattern locations** reference real files in the project
163
+ - ✓ **File count** is accurate (within 10% tolerance)
164
+ - ✓ **Anti-pattern files** exist when referenced
165
+
166
+ **When to use:** Before sealing an analysis to catch logical inconsistencies or after project changes to validate analysis accuracy.
167
+
168
+ **Example output:**
169
+ ```
170
+ Semantic Verification Report
171
+ ────────────────────────────
172
+ ✓ Framework verification: passed (2 frameworks validated)
173
+ ✓ Language verification: passed (1 language validated)
174
+ ✓ Pattern locations: passed (12 patterns verified)
175
+ ✓ File count verification: passed (324 files, within tolerance)
176
+ ✓ Anti-pattern files: passed (3 anti-patterns verified)
177
+
178
+ Result: PASSED (5/5 checks)
179
+ Total time: 145ms
180
+ ```
181
+
182
+ Both verification modes support `--json` flag for programmatic use.
183
+
135
184
  ## Environment Variables
136
185
 
137
186
  ### Configuration
@@ -14,6 +14,7 @@ import {
14
14
  AnalysisItemSchema,
15
15
  AnalysisStatusSchema,
16
16
  parseAnalysis,
17
+ type SemanticCheckResult,
17
18
  safeParseAnalysis,
18
19
  } from '../../schemas/analysis'
19
20
 
@@ -275,3 +276,366 @@ describe('backward compatibility', () => {
275
276
  expect(result.modelMetadata?.provider).toBe('claude')
276
277
  })
277
278
  })
279
+
280
+ // =============================================================================
281
+ // Semantic Verification (PRJ-270)
282
+ // =============================================================================
283
+
284
+ describe('semantic verification', () => {
285
+ const { semanticVerify } = require('../../schemas/analysis')
286
+ const fs = require('node:fs/promises')
287
+ const path = require('node:path')
288
+ const os = require('node:os')
289
+
290
+ // Helper to create a temporary test project
291
+ async function createTestProject(options: {
292
+ hasPackageJson?: boolean
293
+ packageJsonDeps?: Record<string, string>
294
+ files?: { path: string; content: string }[]
295
+ fileCount?: number
296
+ }) {
297
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-test-'))
298
+
299
+ // Create package.json if requested
300
+ if (options.hasPackageJson) {
301
+ const pkg = {
302
+ name: 'test-project',
303
+ dependencies: options.packageJsonDeps || {},
304
+ }
305
+ await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2))
306
+ }
307
+
308
+ // Create additional files
309
+ if (options.files) {
310
+ for (const file of options.files) {
311
+ const filePath = path.join(tmpDir, file.path)
312
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
313
+ await fs.writeFile(filePath, file.content)
314
+ }
315
+ }
316
+
317
+ return tmpDir
318
+ }
319
+
320
+ // Helper to cleanup test project
321
+ async function cleanupTestProject(tmpDir: string) {
322
+ try {
323
+ await fs.rm(tmpDir, { recursive: true, force: true })
324
+ } catch {
325
+ // Ignore cleanup errors
326
+ }
327
+ }
328
+
329
+ it('should pass all checks for valid analysis', async () => {
330
+ const projectPath = await createTestProject({
331
+ hasPackageJson: true,
332
+ packageJsonDeps: { hono: '^3.0.0', zod: '^3.0.0' },
333
+ files: [
334
+ { path: 'src/index.ts', content: 'export const app = {}' },
335
+ { path: 'src/server.ts', content: 'import { serve } from "bun"' },
336
+ { path: 'patterns/service.ts', content: 'export class UserService {}' },
337
+ ],
338
+ })
339
+
340
+ const analysis = {
341
+ projectId: 'test',
342
+ languages: ['TypeScript'],
343
+ frameworks: ['Hono'],
344
+ configFiles: [],
345
+ fileCount: 4, // package.json + 3 TypeScript files
346
+ patterns: [
347
+ { name: 'Service pattern', description: 'DI pattern', location: 'patterns/service.ts' },
348
+ ],
349
+ antiPatterns: [],
350
+ analyzedAt: '2026-02-10T00:00:00.000Z',
351
+ status: 'draft' as const,
352
+ }
353
+
354
+ const result = await semanticVerify(analysis, projectPath)
355
+
356
+ expect(result.passed).toBe(true)
357
+ expect(result.failedCount).toBe(0)
358
+ expect(result.passedCount).toBeGreaterThan(0)
359
+ expect(result.checks.every((c: SemanticCheckResult) => c.passed)).toBe(true)
360
+
361
+ await cleanupTestProject(projectPath)
362
+ })
363
+
364
+ it('should fail when frameworks are not in package.json', async () => {
365
+ const projectPath = await createTestProject({
366
+ hasPackageJson: true,
367
+ packageJsonDeps: { express: '^4.0.0' }, // Different framework
368
+ files: [{ path: 'src/index.ts', content: '' }],
369
+ })
370
+
371
+ const analysis = {
372
+ projectId: 'test',
373
+ languages: ['TypeScript'],
374
+ frameworks: ['Hono', 'Zod'], // These are not in dependencies
375
+ configFiles: [],
376
+ fileCount: 1,
377
+ patterns: [],
378
+ antiPatterns: [],
379
+ analyzedAt: '2026-02-10T00:00:00.000Z',
380
+ status: 'draft' as const,
381
+ }
382
+
383
+ const result = await semanticVerify(analysis, projectPath)
384
+
385
+ expect(result.passed).toBe(false)
386
+ expect(result.failedCount).toBeGreaterThan(0)
387
+
388
+ const frameworkCheck = result.checks.find(
389
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
390
+ )
391
+ expect(frameworkCheck?.passed).toBe(false)
392
+ expect(frameworkCheck?.error).toContain('not found in dependencies')
393
+
394
+ await cleanupTestProject(projectPath)
395
+ })
396
+
397
+ it('should fail when package.json is missing', async () => {
398
+ const projectPath = await createTestProject({
399
+ hasPackageJson: false, // No package.json
400
+ files: [{ path: 'src/index.ts', content: '' }],
401
+ })
402
+
403
+ const analysis = {
404
+ projectId: 'test',
405
+ languages: ['TypeScript'],
406
+ frameworks: ['Hono'],
407
+ configFiles: [],
408
+ fileCount: 1,
409
+ patterns: [],
410
+ antiPatterns: [],
411
+ analyzedAt: '2026-02-10T00:00:00.000Z',
412
+ status: 'draft' as const,
413
+ }
414
+
415
+ const result = await semanticVerify(analysis, projectPath)
416
+
417
+ const frameworkCheck = result.checks.find(
418
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
419
+ )
420
+ expect(frameworkCheck?.passed).toBe(false)
421
+ expect(frameworkCheck?.error).toContain('package.json not found')
422
+
423
+ await cleanupTestProject(projectPath)
424
+ })
425
+
426
+ it('should fail when declared languages have no matching files', async () => {
427
+ const projectPath = await createTestProject({
428
+ hasPackageJson: true,
429
+ files: [
430
+ { path: 'src/index.js', content: '' }, // Only JS files
431
+ ],
432
+ })
433
+
434
+ const analysis = {
435
+ projectId: 'test',
436
+ languages: ['TypeScript', 'Go'], // Declared but no .ts or .go files
437
+ frameworks: [],
438
+ configFiles: [],
439
+ fileCount: 1,
440
+ patterns: [],
441
+ antiPatterns: [],
442
+ analyzedAt: '2026-02-10T00:00:00.000Z',
443
+ status: 'draft' as const,
444
+ }
445
+
446
+ const result = await semanticVerify(analysis, projectPath)
447
+
448
+ const languageCheck = result.checks.find(
449
+ (c: SemanticCheckResult) => c.name === 'Language verification'
450
+ )
451
+ expect(languageCheck?.passed).toBe(false)
452
+ expect(languageCheck?.error).toContain('without matching files')
453
+
454
+ await cleanupTestProject(projectPath)
455
+ })
456
+
457
+ it('should fail when pattern locations reference missing files', async () => {
458
+ const projectPath = await createTestProject({
459
+ hasPackageJson: true,
460
+ files: [{ path: 'src/index.ts', content: '' }],
461
+ })
462
+
463
+ const analysis = {
464
+ projectId: 'test',
465
+ languages: ['TypeScript'],
466
+ frameworks: [],
467
+ configFiles: [],
468
+ fileCount: 1,
469
+ patterns: [
470
+ { name: 'Service pattern', description: 'DI', location: 'src/service.ts' }, // Doesn't exist
471
+ { name: 'Repository', description: 'Data access', location: 'src/repo.ts' }, // Doesn't exist
472
+ ],
473
+ antiPatterns: [],
474
+ analyzedAt: '2026-02-10T00:00:00.000Z',
475
+ status: 'draft' as const,
476
+ }
477
+
478
+ const result = await semanticVerify(analysis, projectPath)
479
+
480
+ const patternCheck = result.checks.find(
481
+ (c: SemanticCheckResult) => c.name === 'Pattern location verification'
482
+ )
483
+ expect(patternCheck?.passed).toBe(false)
484
+ expect(patternCheck?.error).toContain('not found')
485
+
486
+ await cleanupTestProject(projectPath)
487
+ })
488
+
489
+ it('should fail when file count is inaccurate', async () => {
490
+ const projectPath = await createTestProject({
491
+ hasPackageJson: true,
492
+ files: [
493
+ { path: 'src/a.ts', content: '' },
494
+ { path: 'src/b.ts', content: '' },
495
+ { path: 'src/c.ts', content: '' },
496
+ ], // 4 files total (package.json + 3 .ts files)
497
+ })
498
+
499
+ const analysis = {
500
+ projectId: 'test',
501
+ languages: ['TypeScript'],
502
+ frameworks: [],
503
+ configFiles: [],
504
+ fileCount: 100, // Way off (actual is ~4)
505
+ patterns: [],
506
+ antiPatterns: [],
507
+ analyzedAt: '2026-02-10T00:00:00.000Z',
508
+ status: 'draft' as const,
509
+ }
510
+
511
+ const result = await semanticVerify(analysis, projectPath)
512
+
513
+ const fileCountCheck = result.checks.find(
514
+ (c: SemanticCheckResult) => c.name === 'File count verification'
515
+ )
516
+ expect(fileCountCheck?.passed).toBe(false)
517
+ expect(fileCountCheck?.error).toContain('mismatch')
518
+
519
+ await cleanupTestProject(projectPath)
520
+ })
521
+
522
+ it('should fail when anti-pattern files are missing', async () => {
523
+ const projectPath = await createTestProject({
524
+ hasPackageJson: true,
525
+ files: [{ path: 'src/index.ts', content: '' }],
526
+ })
527
+
528
+ const analysis = {
529
+ projectId: 'test',
530
+ languages: ['TypeScript'],
531
+ frameworks: [],
532
+ configFiles: [],
533
+ fileCount: 1,
534
+ patterns: [],
535
+ antiPatterns: [
536
+ { issue: 'Missing types', file: 'src/bad.ts', suggestion: 'Add types' }, // File doesn't exist
537
+ ],
538
+ analyzedAt: '2026-02-10T00:00:00.000Z',
539
+ status: 'draft' as const,
540
+ }
541
+
542
+ const result = await semanticVerify(analysis, projectPath)
543
+
544
+ const antiPatternCheck = result.checks.find(
545
+ (c: SemanticCheckResult) => c.name === 'Anti-pattern file verification'
546
+ )
547
+ expect(antiPatternCheck?.passed).toBe(false)
548
+ expect(antiPatternCheck?.error).toContain('not found')
549
+
550
+ await cleanupTestProject(projectPath)
551
+ })
552
+
553
+ it('should skip checks when no data to verify', async () => {
554
+ const projectPath = await createTestProject({
555
+ hasPackageJson: true,
556
+ })
557
+
558
+ const analysis = {
559
+ projectId: 'test',
560
+ languages: [], // No languages declared
561
+ frameworks: [], // No frameworks declared
562
+ configFiles: [],
563
+ fileCount: 1,
564
+ patterns: [], // No patterns with locations
565
+ antiPatterns: [], // No anti-patterns
566
+ analyzedAt: '2026-02-10T00:00:00.000Z',
567
+ status: 'draft' as const,
568
+ }
569
+
570
+ const result = await semanticVerify(analysis, projectPath)
571
+
572
+ expect(result.passed).toBe(true) // All checks skipped, so passes
573
+ expect(
574
+ result.checks.every((c: SemanticCheckResult) => c.output?.includes('skipped') || c.passed)
575
+ ).toBe(true)
576
+
577
+ await cleanupTestProject(projectPath)
578
+ })
579
+
580
+ it('should accept file count within tolerance (10%)', async () => {
581
+ const projectPath = await createTestProject({
582
+ hasPackageJson: true,
583
+ files: [
584
+ { path: 'src/a.ts', content: '' },
585
+ { path: 'src/b.ts', content: '' },
586
+ { path: 'src/c.ts', content: '' },
587
+ { path: 'src/d.ts', content: '' },
588
+ { path: 'src/e.ts', content: '' },
589
+ ], // 6 files total
590
+ })
591
+
592
+ const analysis = {
593
+ projectId: 'test',
594
+ languages: ['TypeScript'],
595
+ frameworks: [],
596
+ configFiles: [],
597
+ fileCount: 6, // Within 10% tolerance
598
+ patterns: [],
599
+ antiPatterns: [],
600
+ analyzedAt: '2026-02-10T00:00:00.000Z',
601
+ status: 'draft' as const,
602
+ }
603
+
604
+ const result = await semanticVerify(analysis, projectPath)
605
+
606
+ const fileCountCheck = result.checks.find(
607
+ (c: SemanticCheckResult) => c.name === 'File count verification'
608
+ )
609
+ expect(fileCountCheck?.passed).toBe(true)
610
+
611
+ await cleanupTestProject(projectPath)
612
+ })
613
+
614
+ it('should handle partial framework matches (case insensitive)', async () => {
615
+ const projectPath = await createTestProject({
616
+ hasPackageJson: true,
617
+ packageJsonDeps: { '@hono/node-server': '^1.0.0' }, // Hono as part of package name
618
+ })
619
+
620
+ const analysis = {
621
+ projectId: 'test',
622
+ languages: [],
623
+ frameworks: ['Hono'], // Should match @hono/node-server
624
+ configFiles: [],
625
+ fileCount: 1,
626
+ patterns: [],
627
+ antiPatterns: [],
628
+ analyzedAt: '2026-02-10T00:00:00.000Z',
629
+ status: 'draft' as const,
630
+ }
631
+
632
+ const result = await semanticVerify(analysis, projectPath)
633
+
634
+ const frameworkCheck = result.checks.find(
635
+ (c: SemanticCheckResult) => c.name === 'Framework verification'
636
+ )
637
+ expect(frameworkCheck?.passed).toBe(true)
638
+
639
+ await cleanupTestProject(projectPath)
640
+ })
641
+ })