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.
- package/lib/init-generator.js +163 -20
- package/lib/scanner.js +4 -1
- package/package.json +7 -3
package/lib/init-generator.js
CHANGED
|
@@ -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
|
-
|
|
182
|
-
|
|
183
|
-
path.join(
|
|
184
|
-
|
|
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: ["${
|
|
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
|
|
309
|
-
const db = scan.backend.db
|
|
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
|
|
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
|
|
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 `${
|
|
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 = [`${
|
|
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
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
470
|
-
|
|
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.
|
|
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": "
|
|
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
|
}
|