popeye-cli 1.4.3 → 1.4.5

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.
@@ -29,7 +29,7 @@ import { runComprehensiveVerification, autoFixIssues, resolveProjectPaths } from
29
29
  import { setupUI } from './ui-setup.js';
30
30
  import { designUI, saveUISpecification, loadUISpecification } from './ui-designer.js';
31
31
  import { iterateUntilConsensus, runOptimizedConsensusProcess, type ConsensusProcessResult } from './consensus.js';
32
- import { isWorkspace } from '../types/project.js';
32
+ import { isWorkspace, type OutputLanguage } from '../types/project.js';
33
33
  import { getProjectStructureSummary } from './project-structure.js';
34
34
 
35
35
  /**
@@ -167,6 +167,7 @@ async function generateProjectReadme(
167
167
  ): Promise<{ success: boolean; path?: string; error?: string }> {
168
168
  try {
169
169
  const readmePath = path.join(projectDir, 'README.md');
170
+ const workspace = isWorkspace(state.language);
170
171
 
171
172
  // Extract features from completed milestones
172
173
  const features = state.milestones
@@ -177,121 +178,562 @@ async function generateProjectReadme(
177
178
  tasks: m.tasks.filter(t => t.status === 'complete').map(t => t.name),
178
179
  }));
179
180
 
180
- // Determine run commands based on language
181
- const isTypeScript = state.language === 'typescript';
182
- const installCmd = isTypeScript ? 'npm install' : 'pip install -r requirements.txt';
183
- const devCmd = isTypeScript ? 'npm run dev' : 'python src/main.py';
184
- const testCmd = isTypeScript ? 'npm test' : 'pytest tests/ -v';
185
- const buildCmd = isTypeScript ? 'npm run build' : 'python -m py_compile src/**/*.py';
181
+ // Load workspace.json for workspace projects
182
+ let wsConfig: Record<string, unknown> | null = null;
183
+ if (workspace) {
184
+ try {
185
+ const wsContent = await fs.readFile(path.join(projectDir, '.popeye', 'workspace.json'), 'utf-8');
186
+ wsConfig = JSON.parse(wsContent);
187
+ } catch {
188
+ // workspace.json not available, use defaults
189
+ }
190
+ }
186
191
 
187
- // Build README content
188
- const readmeContent = `# ${state.name}
192
+ const description = state.specification
193
+ ? extractDescriptionFromSpec(state.specification)
194
+ : 'A project generated by Popeye CLI.';
189
195
 
190
- ${state.specification ? extractDescriptionFromSpec(state.specification) : 'A project generated by Popeye CLI.'}
196
+ const sections: string[] = [];
191
197
 
192
- ## Features
198
+ // Title and description
199
+ sections.push(`# ${state.name}\n\n${description}`);
193
200
 
194
- ${features.map(f => `### ${f.name}
201
+ // Features
202
+ const featuresContent = features.map(f => `### ${f.name}
195
203
  ${f.description || ''}
196
- ${f.tasks.length > 0 ? f.tasks.map(t => `- ${t}`).join('\n') : ''}`).join('\n\n')}
197
-
198
- ## Prerequisites
204
+ ${f.tasks.length > 0 ? f.tasks.map(t => `- ${t}`).join('\n') : ''}`).join('\n\n');
205
+ sections.push(`## Features\n\n${featuresContent}`);
199
206
 
200
- ${isTypeScript ? `- Node.js 18.0 or higher
201
- - npm 8.0 or higher` : `- Python 3.9 or higher
202
- - pip (Python package manager)`}
207
+ // Prerequisites
208
+ sections.push(`## Prerequisites\n\n${generatePrerequisites(state.language)}`);
203
209
 
204
- ## Installation
205
-
206
- \`\`\`bash
207
- # Clone the repository (if applicable)
208
- cd ${state.name}
209
-
210
- # Install dependencies
211
- ${installCmd}
212
- \`\`\`
210
+ // Installation
211
+ sections.push(`## Installation\n\n${generateInstallation(state.name, state.language)}`);
213
212
 
214
- ## Environment Setup
213
+ // Environment Setup
214
+ sections.push(`## Environment Setup
215
215
 
216
216
  1. Copy the example environment file:
217
217
  \`\`\`bash
218
218
  cp .env.example .env
219
219
  \`\`\`
220
220
 
221
- 2. Edit \`.env\` and fill in the required values.
221
+ 2. Edit \`.env\` and fill in the required values.`);
222
222
 
223
- ## Running the Application
223
+ // Running the Application
224
+ sections.push(`## Running the Application\n\n${generateRunSection(state.language, wsConfig)}`);
224
225
 
225
- ### Development Mode
226
+ // Project Structure
227
+ sections.push(`## Project Structure\n\n${generateStructureSection(state.name, state.language)}`);
228
+
229
+ // Deployment - per-app for workspace projects
230
+ if (workspace) {
231
+ sections.push(`## Deployment\n\n${generateDeploymentSection(state.name, state.language, wsConfig)}`);
232
+ }
233
+
234
+ // Development footer
235
+ sections.push(`## Development
236
+
237
+ This project was generated using [Popeye CLI](https://github.com/your-org/popeye-cli), an autonomous code generation tool.
238
+
239
+ ### Development Plan
240
+
241
+ See [docs/PLAN.md](docs/PLAN.md) for the complete development plan used to build this project.
242
+
243
+ ### Workflow Log
244
+
245
+ See [docs/WORKFLOW_LOG.md](docs/WORKFLOW_LOG.md) for detailed execution logs.
246
+
247
+ ## License
248
+
249
+ MIT`);
250
+
251
+ const readmeContent = sections.join('\n\n') + '\n';
252
+ await fs.writeFile(readmePath, readmeContent, 'utf-8');
253
+
254
+ return { success: true, path: readmePath };
255
+ } catch (error) {
256
+ return {
257
+ success: false,
258
+ error: error instanceof Error ? error.message : 'Failed to generate README',
259
+ };
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Generate prerequisites section based on language
265
+ */
266
+ function generatePrerequisites(language: OutputLanguage): string {
267
+ if (language === 'python') {
268
+ return `- Python 3.9 or higher
269
+ - pip (Python package manager)`;
270
+ }
271
+ if (language === 'typescript' || language === 'website') {
272
+ return `- Node.js 18.0 or higher
273
+ - npm 8.0 or higher`;
274
+ }
275
+ // Workspace projects (fullstack, all) need both
276
+ return `- Node.js 18.0 or higher
277
+ - npm 8.0 or higher
278
+ - Python 3.9 or higher
279
+ - pip (Python package manager)
280
+ - Docker and Docker Compose (recommended for local development)`;
281
+ }
282
+
283
+ /**
284
+ * Generate installation section based on language
285
+ */
286
+ function generateInstallation(name: string, language: OutputLanguage): string {
287
+ const lines = [`\`\`\`bash`, `cd ${name}`, ''];
288
+
289
+ if (isWorkspace(language)) {
290
+ lines.push('# Install all workspace dependencies');
291
+ lines.push('npm install');
292
+ lines.push('');
293
+ lines.push('# Install backend Python dependencies');
294
+ lines.push('cd apps/backend && pip install -r requirements.txt && cd ../..');
295
+ if (language === 'all') {
296
+ lines.push('');
297
+ lines.push('# Install website dependencies (if not covered by workspace)');
298
+ lines.push('cd apps/website && npm install && cd ../..');
299
+ }
300
+ } else if (language === 'python') {
301
+ lines.push('pip install -r requirements.txt');
302
+ } else {
303
+ lines.push('npm install');
304
+ }
305
+
306
+ lines.push('```');
307
+ return lines.join('\n');
308
+ }
309
+
310
+ /**
311
+ * Generate run commands section based on language
312
+ */
313
+ function generateRunSection(language: OutputLanguage, wsConfig: Record<string, unknown> | null): string {
314
+ if (language === 'python') {
315
+ return `### Development Mode
226
316
 
227
317
  \`\`\`bash
228
- ${devCmd}
318
+ python src/main.py
229
319
  \`\`\`
230
320
 
231
321
  ### Running Tests
232
322
 
233
323
  \`\`\`bash
234
- ${testCmd}
324
+ pytest tests/ -v
235
325
  \`\`\`
236
326
 
237
327
  ### Build for Production
238
328
 
239
329
  \`\`\`bash
240
- ${buildCmd}
330
+ python -m py_compile src/**/*.py
331
+ \`\`\``;
332
+ }
333
+
334
+ if (language === 'typescript') {
335
+ return `### Development Mode
336
+
337
+ \`\`\`bash
338
+ npm run dev
339
+ \`\`\`
340
+
341
+ ### Running Tests
342
+
343
+ \`\`\`bash
344
+ npm test
345
+ \`\`\`
346
+
347
+ ### Build for Production
348
+
349
+ \`\`\`bash
350
+ npm run build
241
351
  \`\`\`
242
352
 
243
- ${isTypeScript ? `### Start Production Server
353
+ ### Start Production Server
244
354
 
245
355
  \`\`\`bash
246
356
  npm start
357
+ \`\`\``;
358
+ }
359
+
360
+ if (language === 'website') {
361
+ return `### Development Mode
362
+
363
+ \`\`\`bash
364
+ npm run dev
365
+ \`\`\`
366
+
367
+ ### Running Tests
368
+
369
+ \`\`\`bash
370
+ npm test
371
+ \`\`\`
372
+
373
+ ### Build for Production
374
+
375
+ \`\`\`bash
376
+ npm run build
377
+ \`\`\`
378
+
379
+ ### Preview Production Build
380
+
381
+ \`\`\`bash
382
+ npm run start
383
+ \`\`\``;
384
+ }
385
+
386
+ // Workspace projects (fullstack, all)
387
+ const apps = (wsConfig as { apps?: Record<string, { path?: string; commands?: Record<string, string> }> })?.apps;
388
+
389
+ const lines: string[] = [];
390
+
391
+ lines.push(`### Run Everything (Docker Compose)
392
+
393
+ \`\`\`bash
394
+ docker-compose up
247
395
  \`\`\`
248
- ` : ''}
249
- ## Project Structure
250
396
 
397
+ ### Run Individual Apps`);
398
+
399
+ // Frontend
400
+ const fePath = apps?.frontend?.path || 'apps/frontend';
401
+ lines.push(`
402
+ #### Frontend (React/Vite)
403
+
404
+ \`\`\`bash
405
+ cd ${fePath}
406
+ npm run dev # Development server
407
+ npm run build # Production build
408
+ npm test # Run tests
409
+ \`\`\``);
410
+
411
+ // Backend
412
+ const bePath = apps?.backend?.path || 'apps/backend';
413
+ lines.push(`
414
+ #### Backend (Python/FastAPI)
415
+
416
+ \`\`\`bash
417
+ cd ${bePath}
418
+ uvicorn src.backend.main:app --reload --port 8000 # Development server
419
+ pip install -e . # Build/install
420
+ pytest tests/ -v # Run tests
421
+ \`\`\``);
422
+
423
+ // Website (for 'all' projects)
424
+ if (language === 'all') {
425
+ const webPath = apps?.website?.path || 'apps/website';
426
+ lines.push(`
427
+ #### Website (Next.js)
428
+
429
+ \`\`\`bash
430
+ cd ${webPath}
431
+ npm run dev # Development server (port 3001)
432
+ npm run build # Production build
433
+ npm test # Run tests
434
+ \`\`\``);
435
+ }
436
+
437
+ // All-at-once commands
438
+ lines.push(`
439
+ ### Run All Tests
440
+
441
+ \`\`\`bash
442
+ npm run test:all
251
443
  \`\`\`
252
- ${state.name}/
444
+
445
+ ### Build All
446
+
447
+ \`\`\`bash
448
+ npm run build:all
449
+ \`\`\``);
450
+
451
+ return lines.join('\n');
452
+ }
453
+
454
+ /**
455
+ * Generate project structure section based on language
456
+ */
457
+ function generateStructureSection(name: string, language: OutputLanguage): string {
458
+ if (language === 'python') {
459
+ return `\`\`\`
460
+ ${name}/
461
+ ├── src/ # Source code
462
+ │ ├── __init__.py
463
+ │ └── main.py # Main entry point
464
+ ├── tests/ # Test files
465
+ ├── docs/ # Documentation
466
+ │ ├── PLAN.md # Development plan
467
+ │ └── WORKFLOW_LOG.md # Execution log
468
+ ├── requirements.txt # Python dependencies
469
+ ├── pyproject.toml # Project configuration
470
+ ├── .env.example # Environment template
471
+ ├── .gitignore
472
+ ├── Dockerfile
473
+ └── README.md
474
+ \`\`\``;
475
+ }
476
+
477
+ if (language === 'typescript') {
478
+ return `\`\`\`
479
+ ${name}/
253
480
  ├── src/ # Source code
254
- ${isTypeScript ? `│ └── index.ts # Main entry point` : `│ ├── __init__.py
255
- │ └── main.py # Main entry point`}
481
+ └── index.ts # Main entry point
256
482
  ├── tests/ # Test files
257
483
  ├── docs/ # Documentation
258
484
  │ ├── PLAN.md # Development plan
259
485
  │ └── WORKFLOW_LOG.md # Execution log
260
- ${isTypeScript ? `├── package.json # Dependencies and scripts
261
- ├── tsconfig.json # TypeScript configuration` : `├── requirements.txt # Python dependencies
262
- ├── pyproject.toml # Project configuration`}
486
+ ├── package.json # Dependencies and scripts
487
+ ├── tsconfig.json # TypeScript configuration
263
488
  ├── .env.example # Environment template
264
489
  ├── .gitignore
265
490
  ├── Dockerfile
266
491
  └── README.md
492
+ \`\`\``;
493
+ }
494
+
495
+ if (language === 'website') {
496
+ return `\`\`\`
497
+ ${name}/
498
+ ├── app/ # Next.js app directory
499
+ │ ├── layout.tsx # Root layout
500
+ │ └── page.tsx # Home page
501
+ ├── components/ # React components
502
+ ├── public/ # Static assets
503
+ ├── docs/ # Documentation
504
+ │ ├── PLAN.md # Development plan
505
+ │ └── WORKFLOW_LOG.md # Execution log
506
+ ├── package.json # Dependencies and scripts
507
+ ├── next.config.js # Next.js configuration
508
+ ├── tsconfig.json # TypeScript configuration
509
+ ├── .env.example # Environment template
510
+ ├── .gitignore
511
+ ├── Dockerfile
512
+ └── README.md
513
+ \`\`\``;
514
+ }
515
+
516
+ // Workspace (fullstack / all)
517
+ const websiteTree = language === 'all' ? `│ ├── website/ # Next.js marketing/landing site
518
+ │ │ ├── app/
519
+ │ │ ├── components/
520
+ │ │ ├── package.json
521
+ │ │ └── Dockerfile
522
+ ` : '';
523
+
524
+ const packagesTree = language === 'all' ? `├── packages/ # Shared packages
525
+ │ ├── design-tokens/ # Shared design tokens
526
+ │ ├── ui/ # Shared UI components
527
+ │ └── contracts/ # API contracts (OpenAPI)
528
+ ` : '';
529
+
530
+ return `\`\`\`
531
+ ${name}/
532
+ ├── apps/
533
+ │ ├── frontend/ # React/Vite frontend
534
+ │ │ ├── src/
535
+ │ │ ├── package.json
536
+ │ │ └── Dockerfile
537
+ │ ├── backend/ # Python/FastAPI backend
538
+ │ │ ├── src/
539
+ │ │ ├── tests/
540
+ │ │ ├── requirements.txt
541
+ │ │ └── Dockerfile
542
+ ${websiteTree}${packagesTree}├── docs/ # Documentation
543
+ │ ├── PLAN.md # Development plan
544
+ │ └── WORKFLOW_LOG.md # Execution log
545
+ ├── .popeye/ # Popeye configuration
546
+ │ └── workspace.json # Workspace configuration
547
+ ├── docker-compose.yml # Local development stack
548
+ ├── package.json # Root workspace config
549
+ ├── .env.example # Environment template
550
+ ├── .gitignore
551
+ └── README.md
552
+ \`\`\``;
553
+ }
554
+
555
+ /**
556
+ * Generate deployment section for workspace projects
557
+ */
558
+ function generateDeploymentSection(
559
+ name: string,
560
+ language: OutputLanguage,
561
+ wsConfig: Record<string, unknown> | null
562
+ ): string {
563
+ const apps = (wsConfig as { apps?: Record<string, { docker?: { imageName?: string; context?: string; dockerfile?: string } }> })?.apps;
564
+ const feImage = apps?.frontend?.docker?.imageName || `${name}-frontend`;
565
+ const beImage = apps?.backend?.docker?.imageName || `${name}-backend`;
566
+
567
+ const lines: string[] = [];
568
+
569
+ lines.push(`Each app can be built and deployed independently as a Docker container.
570
+
571
+ ### Build Docker Images
572
+
573
+ \`\`\`bash
574
+ # Frontend
575
+ docker build -t ${feImage} -f apps/frontend/Dockerfile apps/frontend
576
+
577
+ # Backend
578
+ docker build -t ${beImage} -f apps/backend/Dockerfile apps/backend`);
579
+
580
+ if (language === 'all') {
581
+ const webImage = apps?.website?.docker?.imageName || `${name}-website`;
582
+ lines.push(`
583
+ # Website
584
+ docker build -t ${webImage} -f apps/website/Dockerfile apps/website`);
585
+ }
586
+
587
+ lines.push('```');
588
+
589
+ lines.push(`
590
+ ### Deploy with Docker Compose
591
+
592
+ \`\`\`bash
593
+ # Start all services
594
+ docker-compose up -d
595
+
596
+ # View logs
597
+ docker-compose logs -f
598
+
599
+ # Stop all services
600
+ docker-compose down
601
+ \`\`\``);
602
+
603
+ lines.push(`
604
+ ### Individual App Deployment
605
+
606
+ #### Frontend (Static Site)
607
+ The frontend builds to static files that can be served by any CDN or static hosting:
608
+ \`\`\`bash
609
+ cd apps/frontend
610
+ npm run build
611
+ # Deploy the dist/ folder to your hosting provider (Vercel, Netlify, S3, etc.)
267
612
  \`\`\`
268
613
 
269
- ## Development
614
+ #### Backend (API Server)
615
+ The backend runs as a Python ASGI server:
616
+ \`\`\`bash
617
+ cd apps/backend
618
+ pip install -r requirements.txt
619
+ uvicorn src.backend.main:app --host 0.0.0.0 --port 8000
620
+ \`\`\``);
621
+
622
+ if (language === 'all') {
623
+ lines.push(`
624
+ #### Website (Next.js SSR/SSG)
625
+ The website can be deployed as a Node.js server or exported as static files:
626
+ \`\`\`bash
627
+ cd apps/website
628
+ npm run build
629
+ npm start # Run as Node.js server
630
+ # Or deploy to Vercel/Netlify for automatic SSR support
631
+ \`\`\``);
632
+ }
270
633
 
271
- This project was generated using [Popeye CLI](https://github.com/your-org/popeye-cli), an autonomous code generation tool.
634
+ return lines.join('\n');
635
+ }
272
636
 
273
- ### Development Plan
637
+ /**
638
+ * Result of README validation
639
+ */
640
+ interface ReadmeValidationResult {
641
+ valid: boolean;
642
+ missingCritical: string[];
643
+ missingRecommended: string[];
644
+ }
274
645
 
275
- See [docs/PLAN.md](docs/PLAN.md) for the complete development plan used to build this project.
646
+ /**
647
+ * Validate that the generated README contains required sections for the project type.
648
+ *
649
+ * Critical sections block completion; recommended sections produce warnings.
650
+ *
651
+ * @param projectDir - Project root directory
652
+ * @param language - Project language type
653
+ * @returns Validation result with lists of missing sections
654
+ */
655
+ async function validateReadme(
656
+ projectDir: string,
657
+ language: OutputLanguage
658
+ ): Promise<ReadmeValidationResult> {
659
+ const missingCritical: string[] = [];
660
+ const missingRecommended: string[] = [];
276
661
 
277
- ### Workflow Log
662
+ let content: string;
663
+ try {
664
+ content = await fs.readFile(path.join(projectDir, 'README.md'), 'utf-8');
665
+ } catch {
666
+ return { valid: false, missingCritical: ['README.md file not found'], missingRecommended: [] };
667
+ }
278
668
 
279
- See [docs/WORKFLOW_LOG.md](docs/WORKFLOW_LOG.md) for detailed execution logs.
669
+ const contentLower = content.toLowerCase();
280
670
 
281
- ## License
671
+ // Sections required for ALL project types
672
+ const universalRequired: Array<{ label: string; patterns: string[] }> = [
673
+ { label: 'Installation', patterns: ['## installation'] },
674
+ { label: 'Running the Application', patterns: ['## running', 'development mode'] },
675
+ { label: 'Project Structure', patterns: ['## project structure'] },
676
+ { label: 'Environment Setup', patterns: ['## environment', '.env'] },
677
+ ];
282
678
 
283
- MIT
284
- `;
679
+ for (const section of universalRequired) {
680
+ if (!section.patterns.some(p => contentLower.includes(p))) {
681
+ missingCritical.push(section.label);
682
+ }
683
+ }
285
684
 
286
- await fs.writeFile(readmePath, readmeContent, 'utf-8');
685
+ // Check for build/test commands
686
+ if (!contentLower.includes('npm run build') && !contentLower.includes('pip install') && !contentLower.includes('py_compile')) {
687
+ missingCritical.push('Build command');
688
+ }
689
+ if (!contentLower.includes('npm test') && !contentLower.includes('pytest')) {
690
+ missingRecommended.push('Test command');
691
+ }
287
692
 
288
- return { success: true, path: readmePath };
289
- } catch (error) {
290
- return {
291
- success: false,
292
- error: error instanceof Error ? error.message : 'Failed to generate README',
293
- };
693
+ // Workspace-specific checks (fullstack, all)
694
+ if (isWorkspace(language)) {
695
+ // Must have per-app sections
696
+ if (!contentLower.includes('frontend') || !contentLower.includes('backend')) {
697
+ missingCritical.push('Per-app instructions (frontend/backend)');
698
+ }
699
+
700
+ // Must have deployment section
701
+ if (!contentLower.includes('## deployment') && !contentLower.includes('### deploy')) {
702
+ missingCritical.push('Deployment section');
703
+ }
704
+
705
+ // Must mention docker
706
+ if (!contentLower.includes('docker')) {
707
+ missingCritical.push('Docker instructions');
708
+ }
709
+
710
+ // 'all' projects must mention website
711
+ if (language === 'all') {
712
+ if (!contentLower.includes('website') && !contentLower.includes('next.js')) {
713
+ missingCritical.push('Website app instructions');
714
+ }
715
+ }
716
+ }
717
+
718
+ // Website-specific
719
+ if (language === 'website') {
720
+ if (!contentLower.includes('next.js') && !contentLower.includes('next')) {
721
+ missingRecommended.push('Next.js reference');
722
+ }
723
+ }
724
+
725
+ // Python-specific
726
+ if (language === 'python') {
727
+ if (!contentLower.includes('pip install') && !contentLower.includes('requirements.txt')) {
728
+ missingCritical.push('Python dependency installation');
729
+ }
294
730
  }
731
+
732
+ return {
733
+ valid: missingCritical.length === 0,
734
+ missingCritical,
735
+ missingRecommended,
736
+ };
295
737
  }
296
738
 
297
739
  /**
@@ -1283,6 +1725,39 @@ ${buildResult.structuralIssue ? buildErrors.slice(0, 1500) : buildErrors.slice(0
1283
1725
  });
1284
1726
  }
1285
1727
 
1728
+ // ============================================
1729
+ // VALIDATE README COMPLETENESS
1730
+ // ============================================
1731
+ onProgress?.('readme', 'Validating README completeness...');
1732
+ let readmeValidation = await validateReadme(projectDir, state.language);
1733
+
1734
+ if (!readmeValidation.valid) {
1735
+ onProgress?.('readme-warning', `README missing critical sections: ${readmeValidation.missingCritical.join(', ')}`);
1736
+
1737
+ // Re-generate and retry once
1738
+ onProgress?.('readme', 'Re-generating README to include missing sections...');
1739
+ await generateProjectReadme(projectDir, state);
1740
+ readmeValidation = await validateReadme(projectDir, state.language);
1741
+
1742
+ if (!readmeValidation.valid) {
1743
+ onProgress?.('readme-warning', `README still missing after re-generation: ${readmeValidation.missingCritical.join(', ')}`);
1744
+ await logger.warn('completion', 'readme_incomplete', 'README is missing critical sections', {
1745
+ missing: readmeValidation.missingCritical,
1746
+ });
1747
+ } else {
1748
+ onProgress?.('readme', 'README validated successfully after re-generation');
1749
+ }
1750
+ } else {
1751
+ onProgress?.('readme', 'README validated successfully');
1752
+ }
1753
+
1754
+ // Log recommended (non-blocking) warnings
1755
+ if (readmeValidation.missingRecommended.length > 0) {
1756
+ for (const rec of readmeValidation.missingRecommended) {
1757
+ onProgress?.('readme-info', `README recommendation: add ${rec}`);
1758
+ }
1759
+ }
1760
+
1286
1761
  // All milestones complete
1287
1762
  state = await completeProject(projectDir);
1288
1763