create-sdd-project 0.2.0 → 0.2.1

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.
@@ -177,12 +177,15 @@ function generateInit(config) {
177
177
  // 8. Append to .gitignore
178
178
  appendGitignore(dest, skipped);
179
179
 
180
- // 9. Copy .env.example if not present
181
- copyFileIfNotExists(
182
- path.join(templateDir, '.env.example'),
183
- path.join(dest, '.env.example'),
184
- skipped
185
- );
180
+ // 9. Copy and adapt .env.example if not present
181
+ const envExamplePath = path.join(dest, '.env.example');
182
+ if (!fs.existsSync(envExamplePath)) {
183
+ let envContent = fs.readFileSync(path.join(templateDir, '.env.example'), 'utf8');
184
+ envContent = adaptEnvExample(envContent, config, scan);
185
+ fs.writeFileSync(envExamplePath, envContent, 'utf8');
186
+ } else {
187
+ skipped.push('.env.example');
188
+ }
186
189
 
187
190
  // Show skipped files
188
191
  if (skipped.length > 0) {
@@ -290,6 +293,12 @@ function safeDelete(filePath) {
290
293
  function adaptBackendStandards(template, scan) {
291
294
  let content = template;
292
295
 
296
+ // Update frontmatter description to be generic (not "Covers DDD architecture")
297
+ content = content.replace(
298
+ /description: Backend development standards.*?\n/,
299
+ 'description: Backend development standards, best practices, and conventions.\n'
300
+ );
301
+
293
302
  // Add TODO marker after frontmatter
294
303
  content = content.replace(
295
304
  '<!-- CONFIG: This file defaults to Node.js/Express/Prisma/PostgreSQL. Adjust for your stack. -->',
@@ -298,25 +307,42 @@ function adaptBackendStandards(template, scan) {
298
307
 
299
308
  // Update globs in frontmatter
300
309
  const srcRoot = findSrcRootName(scan);
310
+ const globPattern = srcRoot ? `${srcRoot}/**/*.{ts,js,tsx,jsx}` : '**/*.{ts,js,tsx,jsx}';
301
311
  content = content.replace(
302
312
  /globs: \[.*?\]/,
303
- `globs: ["${srcRoot}/**/*.{ts,js,tsx,jsx}"]`
313
+ `globs: ["${globPattern}"]`
304
314
  );
305
315
 
306
316
  // Update Technology Stack
307
317
  const framework = scan.backend.framework || 'Unknown';
308
- const orm = scan.backend.orm || 'Unknown';
309
- const db = scan.backend.db || 'Unknown';
318
+ const orm = scan.backend.orm;
319
+ const db = scan.backend.db;
310
320
  const lang = scan.language === 'typescript' ? 'TypeScript' : 'JavaScript';
311
321
 
322
+ const testFramework = scan.tests.framework !== 'none'
323
+ ? capitalizeFramework(scan.tests.framework)
324
+ : 'Not configured';
325
+
326
+ let stackLines = [
327
+ `- **Runtime**: Node.js with ${lang}`,
328
+ `- **Framework**: ${framework}`,
329
+ ];
330
+ if (orm) {
331
+ stackLines.push(`- **ORM**: ${orm}${db ? ` (${db})` : ''}`);
332
+ } else if (db) {
333
+ stackLines.push(`- **Database**: ${db}`);
334
+ }
335
+ stackLines.push(`- **Testing**: ${testFramework}`);
336
+
312
337
  content = content.replace(
313
338
  /## Technology Stack\n\n[\s\S]*?(?=\n## Architecture)/,
314
- `## Technology Stack\n\n- **Runtime**: Node.js with ${lang}\n- **Framework**: ${framework}\n- **ORM**: ${orm} (${db})\n- **Testing**: ${scan.tests.framework !== 'none' ? scan.tests.framework : 'Not configured'}\n\n`
339
+ `## Technology Stack\n\n${stackLines.join('\n')}\n\n`
315
340
  );
316
341
 
317
342
  // Update Architecture section
318
343
  const patternLabels = {
319
344
  mvc: 'MVC',
345
+ layered: 'Layered',
320
346
  ddd: 'DDD Layered',
321
347
  'feature-based': 'Feature-Based',
322
348
  'handler-based': 'Handler-Based',
@@ -332,13 +358,30 @@ function adaptBackendStandards(template, scan) {
332
358
  );
333
359
 
334
360
  // Update Database Patterns section if not Prisma
335
- if (scan.backend.orm && scan.backend.orm !== 'Prisma') {
361
+ if (!scan.backend.orm) {
362
+ content = content.replace(
363
+ /## Database Patterns\n\n### Prisma Best Practices[\s\S]*?### Repository Pattern[\s\S]*?```\n/,
364
+ `## Database Patterns\n\n<!-- TODO: Add database access patterns for your project. -->\n\n`
365
+ );
366
+ } else if (scan.backend.orm !== 'Prisma') {
336
367
  content = content.replace(
337
368
  /## Database Patterns\n\n### Prisma Best Practices[\s\S]*?### Repository Pattern[\s\S]*?```\n/,
338
369
  `## Database Patterns\n\n<!-- TODO: Add ${scan.backend.orm} best practices and patterns for your project. -->\n\n`
339
370
  );
340
371
  }
341
372
 
373
+ // Replace Prisma-specific references in Security and Performance sections
374
+ if (scan.backend.orm !== 'Prisma') {
375
+ content = content.replace(
376
+ '- Use parameterized queries (Prisma handles this)',
377
+ '- Use parameterized queries to prevent injection attacks'
378
+ );
379
+ content = content.replace(
380
+ '- Use Prisma `include` instead of N+1 queries',
381
+ '- Avoid N+1 queries — use eager loading or batch fetching'
382
+ );
383
+ }
384
+
342
385
  return content;
343
386
  }
344
387
 
@@ -366,7 +409,7 @@ function adaptFrontendStandards(template, scan) {
366
409
 
367
410
  content = content.replace(
368
411
  /## Technology Stack\n\n[\s\S]*?(?=\n## Project Structure)/,
369
- `## Technology Stack\n\n- **Framework**: ${framework}\n- **Language**: ${lang}\n- **Styling**: ${styling}${components ? `\n- **Components**: ${components.slice(2)}` : ''}${state ? `\n- **State Management**: ${state.slice(2)}` : ''}\n- **Testing**: ${scan.tests.framework !== 'none' ? scan.tests.framework : 'Not configured'}\n\n`
412
+ `## Technology Stack\n\n- **Framework**: ${framework}\n- **Language**: ${lang}\n- **Styling**: ${styling}${components ? `\n- **Components**: ${components.slice(2)}` : ''}${state ? `\n- **State Management**: ${state.slice(2)}` : ''}\n- **Testing**: ${scan.tests.framework !== 'none' ? capitalizeFramework(scan.tests.framework) : 'Not configured'}\n\n`
370
413
  );
371
414
 
372
415
  // Update Project Structure
@@ -383,14 +426,15 @@ function adaptFrontendStandards(template, scan) {
383
426
  function buildArchitectureTree(scan) {
384
427
  const srcRoot = findSrcRootName(scan);
385
428
  const dirs = scan.srcStructure.dirs;
429
+ const rootLabel = srcRoot || 'project';
386
430
 
387
431
  if (dirs.length === 0) {
388
- return `${srcRoot}/\n└── <!-- TODO: Map your project structure here -->\n`;
432
+ return `${rootLabel}/\n└── <!-- TODO: Map your project structure here -->\n`;
389
433
  }
390
434
 
391
435
  // Build a simple tree from detected directories (depth 1 only)
392
436
  const topLevel = dirs.filter((d) => !d.includes('/'));
393
- const lines = [`${srcRoot}/\n`];
437
+ const lines = [`${rootLabel}/\n`];
394
438
  topLevel.forEach((dir, i) => {
395
439
  const prefix = i === topLevel.length - 1 ? '└── ' : '├── ';
396
440
  lines.push(`${prefix}${dir}/\n`);
@@ -399,13 +443,18 @@ function buildArchitectureTree(scan) {
399
443
  return lines.join('');
400
444
  }
401
445
 
446
+ function capitalizeFramework(name) {
447
+ const map = { jest: 'Jest', vitest: 'Vitest', mocha: 'Mocha' };
448
+ return map[name] || name;
449
+ }
450
+
402
451
  function findSrcRootName(scan) {
403
452
  // Determine the source root directory name from scan
404
453
  if (scan.rootDirs.includes('src/')) return 'src';
405
454
  if (scan.rootDirs.includes('app/')) return 'app';
406
455
  if (scan.rootDirs.includes('server/')) return 'server';
407
456
  if (scan.rootDirs.includes('lib/')) return 'lib';
408
- return 'src';
457
+ return null; // No conventional source root — code is at project root
409
458
  }
410
459
 
411
460
  // --- AGENTS.md Adaptation ---
@@ -431,9 +480,30 @@ function adaptAgentsMd(template, config, scan) {
431
480
  );
432
481
  }
433
482
 
483
+ // Adapt Standards References descriptions
484
+ if (scan.backend.detected) {
485
+ const parts = [scan.srcStructure.pattern ? patternLabelFor(scan.srcStructure.pattern) : null, scan.backend.framework, scan.backend.orm].filter(Boolean);
486
+ content = content.replace(
487
+ 'Backend patterns (DDD, Express, Prisma)',
488
+ `Backend patterns (${parts.join(', ')})`
489
+ );
490
+ }
491
+ if (scan.frontend.detected) {
492
+ const parts = [scan.frontend.framework, scan.frontend.styling, scan.frontend.components].filter(Boolean);
493
+ content = content.replace(
494
+ 'Frontend patterns (Next.js, Tailwind, Radix)',
495
+ `Frontend patterns (${parts.join(', ')})`
496
+ );
497
+ }
498
+
434
499
  return content;
435
500
  }
436
501
 
502
+ function patternLabelFor(pattern) {
503
+ const map = { mvc: 'MVC', layered: 'Layered', ddd: 'DDD', 'feature-based': 'Feature-Based', 'handler-based': 'Handler-Based', flat: 'Flat', unknown: null };
504
+ return map[pattern] || null;
505
+ }
506
+
437
507
  // --- key_facts.md Configuration ---
438
508
 
439
509
  function configureKeyFacts(template, config, scan) {
@@ -462,12 +532,17 @@ function configureKeyFacts(template, config, scan) {
462
532
  const runtime = scan.language === 'typescript' ? 'Node.js (TypeScript)' : 'Node.js';
463
533
  content = content.replace('[Framework, runtime, version]', `${fw}, ${runtime}`);
464
534
 
465
- const db = scan.backend.db || 'Unknown';
466
- const port = scan.backend.port || config.backendPort;
467
- content = content.replace('[Type, host, port]', `${db}, localhost, ${port}`);
535
+ if (scan.backend.db) {
536
+ content = content.replace('[Type, host, port]', scan.backend.db);
537
+ } else {
538
+ content = content.replace('- **Database**: [Type, host, port]\n', '');
539
+ }
468
540
 
469
- const orm = scan.backend.orm || 'Unknown';
470
- content = content.replace('[Name, version]', orm);
541
+ if (scan.backend.orm) {
542
+ content = content.replace('[Name, version]', scan.backend.orm);
543
+ } else {
544
+ content = content.replace('- **ORM**: [Name, version]\n', '');
545
+ }
471
546
  }
472
547
 
473
548
  if (scan.frontend.detected) {
@@ -509,6 +584,28 @@ function configureKeyFacts(template, config, scan) {
509
584
  content = content.replace('## Technology Stack', `${contextSection}\n## Technology Stack`);
510
585
  }
511
586
 
587
+ // Remove irrelevant Infrastructure lines based on project type
588
+ if (!scan.frontend.detected) {
589
+ content = content.replace('- **Frontend Hosting**: [e.g., Vercel]\n', '');
590
+ }
591
+ if (!scan.backend.detected) {
592
+ content = content.replace('- **Backend Hosting**: [e.g., Render]\n', '');
593
+ content = content.replace('- **Database Hosting**: [e.g., Neon, Supabase, RDS]\n', '');
594
+ }
595
+
596
+ // Adapt DB hosting examples based on detected database
597
+ if (scan.backend.db === 'MongoDB') {
598
+ content = content.replace('[e.g., Neon, Supabase, RDS]', '[e.g., MongoDB Atlas, Cosmos DB]');
599
+ }
600
+
601
+ // Remove irrelevant Reusable Components subsections
602
+ if (!scan.frontend.detected) {
603
+ content = content.replace('\n### Frontend\n- [List key components, hooks, stores as you build them]\n', '');
604
+ }
605
+ if (!scan.backend.detected) {
606
+ content = content.replace('\n### Backend\n- [List key services, middleware, validators as you build them]\n', '');
607
+ }
608
+
512
609
  // Prisma schema reference
513
610
  if (scan.existingDocs.hasPrismaSchema) {
514
611
  content = content.replace(
@@ -532,6 +629,20 @@ function configureSprintTracker(template, scan) {
532
629
  const end = endDate.toISOString().split('T')[0];
533
630
  content = content.replace(/\[YYYY-MM-DD\] to \[YYYY-MM-DD\]/, `${today} to ${end}`);
534
631
 
632
+ // Remove irrelevant task tables based on detected stack
633
+ if (!scan.frontend.detected) {
634
+ content = content.replace(
635
+ /\n### Frontend\n\n\| # \| Task \| Status \| Notes \|\n\|---\|------\|--------\|-------\|\n\| F0\.1 \| \[Task description\] \| ⬚ \| \|\n/,
636
+ ''
637
+ );
638
+ }
639
+ if (!scan.backend.detected) {
640
+ content = content.replace(
641
+ /\n### Backend\n\n\| # \| Task \| Status \| Notes \|\n\|---\|------\|--------\|-------\|\n\| B0\.1 \| \[Task description\] \| ⬚ \| \|\n/,
642
+ ''
643
+ );
644
+ }
645
+
535
646
  // Add retrofit testing tasks if coverage is low
536
647
  if (scan.tests.estimatedCoverage === 'none' || scan.tests.estimatedCoverage === 'low') {
537
648
  const retrofitSection = `\n### Retrofit Testing (recommended)\n\n| # | Task | Status | Notes |\n|---|------|--------|-------|\n| R0.1 | Audit existing test coverage | ⬚ | |\n| R0.2 | Add missing unit tests for critical paths | ⬚ | |\n| R0.3 | Set up CI test pipeline | ⬚ | |\n`;
@@ -566,6 +677,38 @@ function updateAutonomy(dest, config) {
566
677
  }
567
678
  }
568
679
 
680
+ // --- .env.example Adaptation ---
681
+
682
+ function adaptEnvExample(template, config, scan) {
683
+ let content = template;
684
+ const port = scan.backend.port || config.backendPort || 3010;
685
+
686
+ // Adapt port
687
+ content = content.replace('PORT=3010', `PORT=${port}`);
688
+ content = content.replace('localhost:3010', `localhost:${port}`);
689
+
690
+ // Adapt DATABASE_URL based on detected DB
691
+ if (scan.backend.db === 'MongoDB') {
692
+ content = content.replace(
693
+ 'DATABASE_URL=postgresql://user:password@localhost:5432/dbname',
694
+ 'MONGODB_URI=mongodb://localhost:27017/dbname'
695
+ );
696
+ }
697
+
698
+ // Remove frontend section if backend-only
699
+ if (!scan.frontend.detected) {
700
+ content = content.replace(/\n# Frontend\nNEXT_PUBLIC_API_URL=.*\n/, '\n');
701
+ }
702
+
703
+ // Remove backend section if frontend-only
704
+ if (!scan.backend.detected) {
705
+ content = content.replace(/# Backend\nNODE_ENV=.*\nPORT=.*\nDATABASE_URL=.*\n/, '');
706
+ content = content.replace(/# Backend\nNODE_ENV=.*\nPORT=.*\nMONGODB_URI=.*\n/, '');
707
+ }
708
+
709
+ return content;
710
+ }
711
+
569
712
  // --- .gitignore ---
570
713
 
571
714
  function appendGitignore(dest, skipped) {
package/lib/scanner.js CHANGED
@@ -231,8 +231,11 @@ function detectArchitecture(dir, pkg) {
231
231
  result.hasHandlers = dirNames.has('handlers') || dirNames.has('handler');
232
232
 
233
233
  // Determine pattern
234
+ const hasManagers = dirNames.has('managers') || dirNames.has('manager');
234
235
  if (result.hasDomain && (dirNames.has('application') || dirNames.has('infrastructure'))) {
235
236
  result.pattern = 'ddd';
237
+ } else if (result.hasHandlers && result.hasControllers && hasManagers) {
238
+ result.pattern = 'layered';
236
239
  } else if (result.hasControllers && result.hasModels) {
237
240
  result.pattern = 'mvc';
238
241
  } else if (result.hasFeatures) {
@@ -284,7 +287,7 @@ function detectTests(dir, pkg) {
284
287
  };
285
288
 
286
289
  // Framework detection
287
- if (deps['jest'] || deps['@jest/core']) {
290
+ if (deps['jest'] || deps['@jest/core'] || deps['ts-jest'] || deps['@types/jest']) {
288
291
  result.framework = 'jest';
289
292
  } else if (deps['vitest']) {
290
293
  result.framework = 'vitest';
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "create-sdd-project",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Create a new SDD DevFlow project with AI-assisted development workflow",
5
5
  "bin": {
6
- "create-sdd-project": "./bin/cli.js"
6
+ "create-sdd-project": "bin/cli.js"
7
7
  },
8
8
  "scripts": {
9
9
  "test": "node test/smoke.js",
@@ -36,6 +36,10 @@
36
36
  "license": "MIT",
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "https://github.com/pbojeda/sdd-devflow.git"
39
+ "url": "git+https://github.com/pbojeda/sdd-devflow.git"
40
+ },
41
+ "homepage": "https://github.com/pbojeda/sdd-devflow#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/pbojeda/sdd-devflow/issues"
40
44
  }
41
45
  }