create-sdd-project 0.3.0 → 0.3.3
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/generator.js +139 -1
- package/lib/init-generator.js +297 -6
- package/package.json +1 -1
- package/template/AGENTS.md +1 -1
package/lib/generator.js
CHANGED
|
@@ -50,7 +50,22 @@ function generate(config) {
|
|
|
50
50
|
removeBackendFiles(dest, config);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// 7.
|
|
53
|
+
// 7. Adapt agent/skill content for non-default backend stacks
|
|
54
|
+
if (config.projectType !== 'frontend') {
|
|
55
|
+
const bp = config.backendPreset || BACKEND_STACKS[0];
|
|
56
|
+
if (bp.orm && bp.orm !== 'Prisma') {
|
|
57
|
+
step(`Adapting agents for ${bp.orm}`);
|
|
58
|
+
adaptAgentsForStack(dest, bp, config);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 7b. Clean documentation-standards and agent content based on project type
|
|
63
|
+
adaptDocStandards(dest, config);
|
|
64
|
+
if (config.projectType !== 'fullstack') {
|
|
65
|
+
adaptAgentContent(dest, config);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 8. Remove AI tool config if single tool selected
|
|
54
69
|
if (config.aiTools === 'claude') {
|
|
55
70
|
step('Removing Gemini config (Claude only)');
|
|
56
71
|
fs.rmSync(path.join(dest, '.gemini'), { recursive: true, force: true });
|
|
@@ -289,4 +304,127 @@ function collectNotes(config) {
|
|
|
289
304
|
return notes;
|
|
290
305
|
}
|
|
291
306
|
|
|
307
|
+
function adaptAgentsForStack(dest, bPreset, config) {
|
|
308
|
+
const ormReplacements = [
|
|
309
|
+
['Prisma ORM, and PostgreSQL', `${bPreset.orm}${bPreset.db ? `, and ${bPreset.db}` : ''}`],
|
|
310
|
+
['Repository implementations (Prisma)', `Repository implementations (${bPreset.orm})`],
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
const toolDirs = [];
|
|
314
|
+
if (config.aiTools !== 'gemini') toolDirs.push('.claude');
|
|
315
|
+
if (config.aiTools !== 'claude') toolDirs.push('.gemini');
|
|
316
|
+
|
|
317
|
+
for (const dir of toolDirs) {
|
|
318
|
+
replaceInFile(path.join(dest, dir, 'agents', 'backend-developer.md'), ormReplacements);
|
|
319
|
+
replaceInFile(path.join(dest, dir, 'agents', 'backend-planner.md'), ormReplacements);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function adaptDocStandards(dest, config) {
|
|
324
|
+
const docStdPath = path.join(dest, 'ai-specs', 'specs', 'documentation-standards.mdc');
|
|
325
|
+
if (!fs.existsSync(docStdPath)) return;
|
|
326
|
+
|
|
327
|
+
const replacements = [];
|
|
328
|
+
if (config.projectType === 'backend') {
|
|
329
|
+
replacements.push([/\| `ai-specs\/specs\/frontend-standards\.mdc` \|[^\n]*\n/, '']);
|
|
330
|
+
replacements.push([/\| `docs\/specs\/ui-components\.md` \|[^\n]*\n/, '']);
|
|
331
|
+
replacements.push([/ - UI component changes → `docs\/specs\/ui-components\.md`\n/, '']);
|
|
332
|
+
} else if (config.projectType === 'frontend') {
|
|
333
|
+
replacements.push([/\| `ai-specs\/specs\/backend-standards\.mdc` \|[^\n]*\n/, '']);
|
|
334
|
+
replacements.push([/\| `docs\/specs\/api-spec\.yaml` \|[^\n]*\n/, '']);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (replacements.length > 0) {
|
|
338
|
+
replaceInFile(docStdPath, replacements);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function adaptAgentContent(dest, config) {
|
|
343
|
+
const toolDirs = [];
|
|
344
|
+
if (config.aiTools !== 'gemini') toolDirs.push('.claude');
|
|
345
|
+
if (config.aiTools !== 'claude') toolDirs.push('.gemini');
|
|
346
|
+
|
|
347
|
+
if (config.projectType === 'backend') {
|
|
348
|
+
for (const dir of toolDirs) {
|
|
349
|
+
// spec-creator: remove Frontend Specifications section and UI output format
|
|
350
|
+
replaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
351
|
+
[/### Frontend Specifications\n(?:- [^\n]*\n)+\n/, ''],
|
|
352
|
+
[/### For UI Changes\n```markdown\n(?:[^\n]*\n)*?```\n\n/, ''],
|
|
353
|
+
['Data Model Changes, UI Changes, Edge Cases', 'Data Model Changes, Edge Cases'],
|
|
354
|
+
]);
|
|
355
|
+
// production-code-validator: remove ui-components line
|
|
356
|
+
replaceInFile(path.join(dest, dir, 'agents', 'production-code-validator.md'), [
|
|
357
|
+
[/- Components exported\/used that are NOT listed in `docs\/specs\/ui-components\.md`\n/, ''],
|
|
358
|
+
]);
|
|
359
|
+
// code-review-specialist: backend-only refs
|
|
360
|
+
replaceInFile(path.join(dest, dir, 'agents', 'code-review-specialist.md'), [
|
|
361
|
+
['(`backend-standards.mdc` / `frontend-standards.mdc`)', '(`backend-standards.mdc`)'],
|
|
362
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`api-spec.yaml`)'],
|
|
363
|
+
]);
|
|
364
|
+
// qa-engineer: remove frontend refs
|
|
365
|
+
replaceInFile(path.join(dest, dir, 'agents', 'qa-engineer.md'), [
|
|
366
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`api-spec.yaml`)'],
|
|
367
|
+
[/- Frontend: `cd frontend && npm test`\n/, ''],
|
|
368
|
+
[/- \*\*Frontend\*\*: Write tests for error states[^\n]*\n/, ''],
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
} else if (config.projectType === 'frontend') {
|
|
372
|
+
for (const dir of toolDirs) {
|
|
373
|
+
replaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
374
|
+
[/### Backend Specifications\n(?:- [^\n]*\n)+\n/, ''],
|
|
375
|
+
[/### For API Changes\n```yaml\n(?:[^\n]*\n)*?```\n\n/, ''],
|
|
376
|
+
]);
|
|
377
|
+
replaceInFile(path.join(dest, dir, 'agents', 'code-review-specialist.md'), [
|
|
378
|
+
['(`backend-standards.mdc` / `frontend-standards.mdc`)', '(`frontend-standards.mdc`)'],
|
|
379
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`ui-components.md`)'],
|
|
380
|
+
]);
|
|
381
|
+
replaceInFile(path.join(dest, dir, 'agents', 'qa-engineer.md'), [
|
|
382
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`ui-components.md`)'],
|
|
383
|
+
[/- Backend: `cd backend && npm test`\n/, ''],
|
|
384
|
+
[/- \*\*Backend\*\*: Write tests for error paths[^\n]*\n/, ''],
|
|
385
|
+
]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Skills and templates: same cleanup
|
|
390
|
+
if (config.projectType === 'backend') {
|
|
391
|
+
for (const dir of toolDirs) {
|
|
392
|
+
replaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
393
|
+
[/\(api-spec\.yaml, ui-components\.md\)/, '(api-spec.yaml)'],
|
|
394
|
+
]);
|
|
395
|
+
replaceInFile(path.join(dest, dir, 'agents', 'production-code-validator.md'), [
|
|
396
|
+
[/,? components not in ui-components\.md/, ''],
|
|
397
|
+
]);
|
|
398
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
399
|
+
[/,? `ui-components\.md`\)/, ')'],
|
|
400
|
+
[/- UI components → `docs\/specs\/ui-components\.md` \(MANDATORY\)\n/, ''],
|
|
401
|
+
]);
|
|
402
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
403
|
+
[/### UI Changes \(if applicable\)\n\n\[Components to add\/modify\. Reference `docs\/specs\/ui-components\.md`\.\]\n\n/, ''],
|
|
404
|
+
[' / `ui-components.md`', ''],
|
|
405
|
+
]);
|
|
406
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
407
|
+
[' / ui-components.md', ''],
|
|
408
|
+
]);
|
|
409
|
+
}
|
|
410
|
+
} else if (config.projectType === 'frontend') {
|
|
411
|
+
for (const dir of toolDirs) {
|
|
412
|
+
replaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
413
|
+
[/\(api-spec\.yaml, ui-components\.md\)/, '(ui-components.md)'],
|
|
414
|
+
]);
|
|
415
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
416
|
+
[/`api-spec\.yaml`,? /, ''],
|
|
417
|
+
[/- API endpoints → `docs\/specs\/api-spec\.yaml` \(MANDATORY\)\n/, ''],
|
|
418
|
+
]);
|
|
419
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
420
|
+
[/### API Changes \(if applicable\)\n\n\[Endpoints to add\/modify\. Reference[^\]]*\]\n\n/, ''],
|
|
421
|
+
['`api-spec.yaml` / ', ''],
|
|
422
|
+
]);
|
|
423
|
+
replaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
424
|
+
['api-spec.yaml / ', ''],
|
|
425
|
+
]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
292
430
|
module.exports = { generate };
|
package/lib/init-generator.js
CHANGED
|
@@ -35,12 +35,15 @@ function generateInit(config) {
|
|
|
35
35
|
step('Creating ai-specs/specs/ (4 standards files)');
|
|
36
36
|
ensureDir(path.join(dest, 'ai-specs', 'specs'));
|
|
37
37
|
|
|
38
|
-
// base-standards
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
path.join(
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
// base-standards: adapt validation references based on detected stack
|
|
39
|
+
const baseStdPath = path.join(dest, 'ai-specs', 'specs', 'base-standards.mdc');
|
|
40
|
+
if (!fs.existsSync(baseStdPath)) {
|
|
41
|
+
const baseTemplate = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'base-standards.mdc'), 'utf8');
|
|
42
|
+
const adaptedBase = adaptBaseStandards(baseTemplate, scan, config);
|
|
43
|
+
fs.writeFileSync(baseStdPath, adaptedBase, 'utf8');
|
|
44
|
+
} else {
|
|
45
|
+
skipped.push('ai-specs/specs/base-standards.mdc');
|
|
46
|
+
}
|
|
44
47
|
copyFileIfNotExists(
|
|
45
48
|
path.join(templateDir, 'ai-specs', 'specs', 'documentation-standards.mdc'),
|
|
46
49
|
path.join(dest, 'ai-specs', 'specs', 'documentation-standards.mdc'),
|
|
@@ -174,6 +177,9 @@ function generateInit(config) {
|
|
|
174
177
|
removeAgentFiles(dest, BACKEND_AGENTS, config);
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
// 7b. Adapt agent/skill content to match detected stack
|
|
181
|
+
adaptCopiedFiles(dest, scan, config);
|
|
182
|
+
|
|
177
183
|
// 8. Append to .gitignore
|
|
178
184
|
appendGitignore(dest, skipped);
|
|
179
185
|
|
|
@@ -288,8 +294,288 @@ function safeDelete(filePath) {
|
|
|
288
294
|
try { fs.unlinkSync(filePath); } catch { /* ignore */ }
|
|
289
295
|
}
|
|
290
296
|
|
|
297
|
+
// --- Adapt Copied Agent/Skill Files ---
|
|
298
|
+
|
|
299
|
+
function replaceInCopiedFile(dest, relativePath, replacements) {
|
|
300
|
+
const filePath = path.join(dest, relativePath);
|
|
301
|
+
if (!fs.existsSync(filePath)) return;
|
|
302
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
303
|
+
for (const [from, to] of replacements) {
|
|
304
|
+
content = content.replaceAll(from, to);
|
|
305
|
+
}
|
|
306
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function regexReplaceInFile(filePath, replacements) {
|
|
310
|
+
if (!fs.existsSync(filePath)) return;
|
|
311
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
312
|
+
for (const [from, to] of replacements) {
|
|
313
|
+
if (from instanceof RegExp) {
|
|
314
|
+
content = content.replace(from, to);
|
|
315
|
+
} else {
|
|
316
|
+
content = content.replaceAll(from, to);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function adaptCopiedFiles(dest, scan, config) {
|
|
323
|
+
const orm = scan.backend.orm || 'your ORM';
|
|
324
|
+
const db = scan.backend.db || 'your database';
|
|
325
|
+
|
|
326
|
+
// Common Zod → generic validation replacements (all agents + skills)
|
|
327
|
+
// Order: most specific first, catch-all last
|
|
328
|
+
const zodReplacements = [
|
|
329
|
+
['Zod data schemas', 'validation schemas'],
|
|
330
|
+
['Zod schemas', 'validation schemas'],
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
// ORM/DB replacements for backend agents
|
|
334
|
+
const ormReplacements = scan.backend.orm && scan.backend.orm !== 'Prisma'
|
|
335
|
+
? [
|
|
336
|
+
['Prisma ORM, and PostgreSQL', `${orm}${db !== 'your database' ? `, and ${db}` : ''}`],
|
|
337
|
+
['Repository implementations (Prisma)', `Repository implementations (${orm})`],
|
|
338
|
+
]
|
|
339
|
+
: [];
|
|
340
|
+
|
|
341
|
+
// Apply to all AI tool directories
|
|
342
|
+
const toolDirs = [];
|
|
343
|
+
if (config.aiTools !== 'gemini') toolDirs.push('.claude');
|
|
344
|
+
if (config.aiTools !== 'claude') toolDirs.push('.gemini');
|
|
345
|
+
|
|
346
|
+
for (const dir of toolDirs) {
|
|
347
|
+
// Backend agents: Zod + ORM replacements
|
|
348
|
+
if (scan.backend.validation !== 'Zod') {
|
|
349
|
+
const backendAgentReplacements = [...zodReplacements, ...ormReplacements];
|
|
350
|
+
replaceInCopiedFile(dest, `${dir}/agents/backend-developer.md`, backendAgentReplacements);
|
|
351
|
+
replaceInCopiedFile(dest, `${dir}/agents/backend-planner.md`, backendAgentReplacements);
|
|
352
|
+
} else if (ormReplacements.length > 0) {
|
|
353
|
+
// Zod detected but different ORM — only ORM replacements
|
|
354
|
+
replaceInCopiedFile(dest, `${dir}/agents/backend-developer.md`, ormReplacements);
|
|
355
|
+
replaceInCopiedFile(dest, `${dir}/agents/backend-planner.md`, ormReplacements);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Multi-purpose agents: Zod replacements only
|
|
359
|
+
if (scan.backend.validation !== 'Zod') {
|
|
360
|
+
replaceInCopiedFile(dest, `${dir}/agents/spec-creator.md`, zodReplacements);
|
|
361
|
+
replaceInCopiedFile(dest, `${dir}/agents/production-code-validator.md`, zodReplacements);
|
|
362
|
+
replaceInCopiedFile(dest, `${dir}/agents/database-architect.md`, zodReplacements);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Skills: Zod replacements
|
|
366
|
+
if (scan.backend.validation !== 'Zod') {
|
|
367
|
+
replaceInCopiedFile(dest, `${dir}/skills/development-workflow/SKILL.md`, zodReplacements);
|
|
368
|
+
replaceInCopiedFile(dest, `${dir}/skills/development-workflow/references/ticket-template.md`, zodReplacements);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Architecture adaptation: DDD-specific content in backend agents
|
|
373
|
+
const arch = scan.srcStructure ? scan.srcStructure.pattern : 'ddd';
|
|
374
|
+
if (arch !== 'ddd') {
|
|
375
|
+
for (const dir of toolDirs) {
|
|
376
|
+
// backend-planner: adapt header, exploration paths, implementation order, rules
|
|
377
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'backend-planner.md'), [
|
|
378
|
+
// Header: remove DDD reference, use generic
|
|
379
|
+
['specializing in Domain-Driven Design (DDD) layered architecture with deep knowledge of',
|
|
380
|
+
'specializing in layered architecture with deep knowledge of'],
|
|
381
|
+
// Exploration paths: replace DDD-specific with generic
|
|
382
|
+
[/4\. Read `shared\/src\/schemas\/` \(if exists\) for current .* data schemas\n/, ''],
|
|
383
|
+
[/5\. Explore `backend\/src\/domain\/` for existing entities and errors\n/,
|
|
384
|
+
'5. Explore the codebase for existing patterns, layer structure, and reusable code\n'],
|
|
385
|
+
[/6\. Explore `backend\/src\/application\/services\/` for existing services\n/, ''],
|
|
386
|
+
[/7\. Explore `backend\/src\/application\/validators\/` for existing validators\n/, ''],
|
|
387
|
+
[/8\. Explore `backend\/src\/infrastructure\/` for existing repositories\n/, ''],
|
|
388
|
+
// Implementation Order
|
|
389
|
+
['following DDD layer order: Domain > Application > Infrastructure > Presentation > Tests',
|
|
390
|
+
'following the layer order defined in backend-standards.mdc'],
|
|
391
|
+
// Rules
|
|
392
|
+
['Follow DDD layer separation: Domain > Application > Infrastructure > Presentation',
|
|
393
|
+
'Follow the layer separation defined in backend-standards.mdc'],
|
|
394
|
+
]);
|
|
395
|
+
// backend-developer: adapt frontmatter, header, exploration, implementation order, rules
|
|
396
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'backend-developer.md'), [
|
|
397
|
+
['follows DDD layered architecture',
|
|
398
|
+
'follows layered architecture'],
|
|
399
|
+
['specializing in Domain-Driven Design (DDD) with',
|
|
400
|
+
'specializing in layered architecture with'],
|
|
401
|
+
[/4\. Read `shared\/src\/schemas\/` \(if exists\) for current .* data schemas\n/, ''],
|
|
402
|
+
// Implementation Order section: replace DDD layers with generic guidance
|
|
403
|
+
['Follow the DDD layer order from the plan:',
|
|
404
|
+
'Follow the layer order from the plan (see backend-standards.mdc for project layers):'],
|
|
405
|
+
[/1\. \*\*Domain Layer\*\*: Entities, value objects, repository interfaces, domain errors\n/,
|
|
406
|
+
'1. **Data Layer**: Models, database operations, data access\n'],
|
|
407
|
+
[/2\. \*\*Application Layer\*\*: Services, validators, DTOs\n/,
|
|
408
|
+
'2. **Business Logic Layer**: Controllers, services, external integrations\n'],
|
|
409
|
+
[/3\. \*\*Infrastructure Layer\*\*: Repository implementations \([^)]*\), external integrations\n/,
|
|
410
|
+
'3. **Presentation Layer**: Routes, handlers, middleware\n'],
|
|
411
|
+
[/4\. \*\*Presentation Layer\*\*: Controllers, routes, middleware\n/,
|
|
412
|
+
'4. **Integration Layer**: Wiring, configuration, server registration\n'],
|
|
413
|
+
// Rules
|
|
414
|
+
['**ALWAYS** follow DDD layer separation',
|
|
415
|
+
'**ALWAYS** follow the layer separation defined in backend-standards.mdc'],
|
|
416
|
+
['**ALWAYS** handle errors with custom domain error classes',
|
|
417
|
+
'**ALWAYS** handle errors following the patterns in backend-standards.mdc'],
|
|
418
|
+
// Documentation
|
|
419
|
+
[/- \*\*MANDATORY\*\*: If modifying a DB schema → update .* schemas in `shared\/src\/schemas\/` BEFORE continuing\n/, ''],
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Agent content: remove frontend/backend-specific references for single-stack projects
|
|
425
|
+
if (config.projectType === 'backend') {
|
|
426
|
+
for (const dir of toolDirs) {
|
|
427
|
+
// spec-creator: remove Frontend Specifications section and UI output format
|
|
428
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
429
|
+
[/### Frontend Specifications\n(?:- [^\n]*\n)+\n/, ''],
|
|
430
|
+
[/### For UI Changes\n```markdown\n(?:[^\n]*\n)*?```\n\n/, ''],
|
|
431
|
+
['Data Model Changes, UI Changes, Edge Cases', 'Data Model Changes, Edge Cases'],
|
|
432
|
+
]);
|
|
433
|
+
// production-code-validator: remove ui-components line from Spec Drift
|
|
434
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'production-code-validator.md'), [
|
|
435
|
+
[/- Components exported\/used that are NOT listed in `docs\/specs\/ui-components\.md`\n/, ''],
|
|
436
|
+
]);
|
|
437
|
+
// code-review-specialist: backend-only standards ref, remove ui-components
|
|
438
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'code-review-specialist.md'), [
|
|
439
|
+
['(`backend-standards.mdc` / `frontend-standards.mdc`)', '(`backend-standards.mdc`)'],
|
|
440
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`api-spec.yaml`)'],
|
|
441
|
+
]);
|
|
442
|
+
// qa-engineer: remove ui-components from specs, remove frontend test command
|
|
443
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'qa-engineer.md'), [
|
|
444
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`api-spec.yaml`)'],
|
|
445
|
+
[/- Frontend: `cd frontend && npm test`\n/, ''],
|
|
446
|
+
[/- \*\*Frontend\*\*: Write tests for error states[^\n]*\n/, ''],
|
|
447
|
+
]);
|
|
448
|
+
}
|
|
449
|
+
} else if (config.projectType === 'frontend') {
|
|
450
|
+
for (const dir of toolDirs) {
|
|
451
|
+
// spec-creator: remove Backend Specifications section
|
|
452
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
453
|
+
[/### Backend Specifications\n(?:- [^\n]*\n)+\n/, ''],
|
|
454
|
+
[/### For API Changes\n```yaml\n(?:[^\n]*\n)*?```\n\n/, ''],
|
|
455
|
+
]);
|
|
456
|
+
// code-review-specialist: frontend-only standards ref
|
|
457
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'code-review-specialist.md'), [
|
|
458
|
+
['(`backend-standards.mdc` / `frontend-standards.mdc`)', '(`frontend-standards.mdc`)'],
|
|
459
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`ui-components.md`)'],
|
|
460
|
+
]);
|
|
461
|
+
// qa-engineer: remove api-spec from specs, remove backend test command
|
|
462
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'qa-engineer.md'), [
|
|
463
|
+
['(`api-spec.yaml`, `ui-components.md`)', '(`ui-components.md`)'],
|
|
464
|
+
[/- Backend: `cd backend && npm test`\n/, ''],
|
|
465
|
+
[/- \*\*Backend\*\*: Write tests for error paths[^\n]*\n/, ''],
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Skills and templates: remove frontend/backend-specific references
|
|
471
|
+
if (config.projectType === 'backend') {
|
|
472
|
+
for (const dir of toolDirs) {
|
|
473
|
+
// Gemini agents have different text patterns
|
|
474
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
475
|
+
[/\(api-spec\.yaml, ui-components\.md\)/, '(api-spec.yaml)'],
|
|
476
|
+
['Data Model Changes, UI Changes, Edge Cases', 'Data Model Changes, Edge Cases'],
|
|
477
|
+
]);
|
|
478
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'production-code-validator.md'), [
|
|
479
|
+
[/,? components not in ui-components\.md/, ''],
|
|
480
|
+
]);
|
|
481
|
+
// SKILL.md: remove ui-components references
|
|
482
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
483
|
+
[/,? `ui-components\.md`\)/, ')'],
|
|
484
|
+
[/- UI components → `docs\/specs\/ui-components\.md` \(MANDATORY\)\n/, ''],
|
|
485
|
+
]);
|
|
486
|
+
// ticket-template: remove UI Changes section, ui-components from checklists
|
|
487
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
488
|
+
[/### UI Changes \(if applicable\)\n\n\[Components to add\/modify\. Reference `docs\/specs\/ui-components\.md`\.\]\n\n/, ''],
|
|
489
|
+
[' / `ui-components.md`', ''],
|
|
490
|
+
]);
|
|
491
|
+
// pr-template: remove ui-components from checklist
|
|
492
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
493
|
+
[' / ui-components.md', ''],
|
|
494
|
+
]);
|
|
495
|
+
}
|
|
496
|
+
} else if (config.projectType === 'frontend') {
|
|
497
|
+
for (const dir of toolDirs) {
|
|
498
|
+
regexReplaceInFile(path.join(dest, dir, 'agents', 'spec-creator.md'), [
|
|
499
|
+
[/\(api-spec\.yaml, ui-components\.md\)/, '(ui-components.md)'],
|
|
500
|
+
]);
|
|
501
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
502
|
+
[/`api-spec\.yaml`,? /, ''],
|
|
503
|
+
[/- API endpoints → `docs\/specs\/api-spec\.yaml` \(MANDATORY\)\n/, ''],
|
|
504
|
+
]);
|
|
505
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
506
|
+
[/### API Changes \(if applicable\)\n\n\[Endpoints to add\/modify\. Reference[^\]]*\]\n\n/, ''],
|
|
507
|
+
['`api-spec.yaml` / ', ''],
|
|
508
|
+
]);
|
|
509
|
+
regexReplaceInFile(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
510
|
+
['api-spec.yaml / ', ''],
|
|
511
|
+
]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// documentation-standards.mdc: remove irrelevant rows based on project type
|
|
516
|
+
const docStdPath = path.join(dest, 'ai-specs', 'specs', 'documentation-standards.mdc');
|
|
517
|
+
if (fs.existsSync(docStdPath)) {
|
|
518
|
+
let content = fs.readFileSync(docStdPath, 'utf8');
|
|
519
|
+
if (config.projectType === 'backend') {
|
|
520
|
+
content = content.replace(/\| `ai-specs\/specs\/frontend-standards\.mdc` \|[^\n]*\n/, '');
|
|
521
|
+
content = content.replace(/\| `docs\/specs\/ui-components\.md` \|[^\n]*\n/, '');
|
|
522
|
+
content = content.replace(/ - UI component changes → `docs\/specs\/ui-components\.md`\n/, '');
|
|
523
|
+
} else if (config.projectType === 'frontend') {
|
|
524
|
+
content = content.replace(/\| `ai-specs\/specs\/backend-standards\.mdc` \|[^\n]*\n/, '');
|
|
525
|
+
content = content.replace(/\| `docs\/specs\/api-spec\.yaml` \|[^\n]*\n/, '');
|
|
526
|
+
}
|
|
527
|
+
fs.writeFileSync(docStdPath, content, 'utf8');
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
291
531
|
// --- Standards Adaptation ---
|
|
292
532
|
|
|
533
|
+
function adaptBaseStandards(template, scan, config) {
|
|
534
|
+
let content = template;
|
|
535
|
+
|
|
536
|
+
// Adapt validation recommendation based on detected library
|
|
537
|
+
const val = scan.backend.validation;
|
|
538
|
+
if (val && val !== 'Zod') {
|
|
539
|
+
content = content.replace(
|
|
540
|
+
'Use runtime validation (Zod recommended).',
|
|
541
|
+
`Use runtime validation (${val}).`
|
|
542
|
+
);
|
|
543
|
+
} else if (!val) {
|
|
544
|
+
content = content.replace(
|
|
545
|
+
'Use runtime validation (Zod recommended).',
|
|
546
|
+
'Use runtime validation at system boundaries.'
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Adapt Shared Types Strategy section based on project structure
|
|
551
|
+
const hasSharedTypes = scan.frontend.detected && scan.backend.detected;
|
|
552
|
+
if (!hasSharedTypes) {
|
|
553
|
+
// Backend-only or frontend-only: remove entire Shared Types section
|
|
554
|
+
content = content.replace(
|
|
555
|
+
/## 5\. Shared Types Strategy\n\n<!-- CONFIG:.*?-->\n\n[\s\S]*?(?=\n## 6\.)/,
|
|
556
|
+
'## 5. Shared Types Strategy\n\n_Not applicable — single-stack project._\n\n'
|
|
557
|
+
);
|
|
558
|
+
} else if (val !== 'Zod') {
|
|
559
|
+
// Fullstack but not using Zod (or no validation detected): generalize the section
|
|
560
|
+
content = content.replace(
|
|
561
|
+
/## 5\. Shared Types Strategy\n\n<!-- CONFIG:.*?-->\n\nFor projects with backend \+ frontend, use a shared workspace for Zod schemas:\n\n```\n[\s\S]*?```\n\n\*\*Rules:\*\*\n[\s\S]*?(?=\n## 6\.)/,
|
|
562
|
+
`## 5. Shared Types Strategy\n\n<!-- CONFIG: Remove this section if your project has no shared types -->\n\nFor projects with backend + frontend, use a shared workspace for types:\n\n\`\`\`\nproject/\n├── shared/ ← @project/shared (npm workspace)\n│ └── src/types/ ← Shared TypeScript types\n├── backend/ ← imports @project/shared\n└── frontend/ ← imports @project/shared\n\`\`\`\n\n**Rules:**\n- Define shared types in the shared workspace\n- Both apps import from shared — never duplicate type definitions\n- Update shared types FIRST — both apps get changes automatically\n- Wire with npm workspaces + \`tsconfig.json\` paths\n\n`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
// If Zod detected + fullstack: keep as-is (template default is correct)
|
|
566
|
+
|
|
567
|
+
// Remove frontend/backend references for single-stack projects
|
|
568
|
+
if (config.projectType === 'backend') {
|
|
569
|
+
content = content.replace(/ - \*\*Frontend\*\*: Update `docs\/specs\/ui-components\.md` first\.\n/, '');
|
|
570
|
+
content = content.replace('`backend-standards.mdc` / `frontend-standards.mdc`', '`backend-standards.mdc`');
|
|
571
|
+
} else if (config.projectType === 'frontend') {
|
|
572
|
+
content = content.replace(/ - \*\*Backend\*\*: Update `docs\/specs\/api-spec\.yaml` first\.\n/, '');
|
|
573
|
+
content = content.replace('`backend-standards.mdc` / `frontend-standards.mdc`', '`frontend-standards.mdc`');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return content;
|
|
577
|
+
}
|
|
578
|
+
|
|
293
579
|
function adaptBackendStandards(template, scan) {
|
|
294
580
|
let content = template;
|
|
295
581
|
|
|
@@ -367,6 +653,11 @@ function adaptBackendStandards(template, scan) {
|
|
|
367
653
|
/## Database Patterns\n\n[\s\S]*?(?=\n## )/,
|
|
368
654
|
`## Database Patterns\n\n<!-- TODO: Add database access patterns for your project. -->\n`
|
|
369
655
|
);
|
|
656
|
+
} else if (scan.backend.orm === 'Mongoose') {
|
|
657
|
+
content = content.replace(
|
|
658
|
+
/## Database Patterns\n\n[\s\S]*?(?=\n## )/,
|
|
659
|
+
`## Database Patterns\n\n### Mongoose Best Practices\n- Define schemas in dedicated model files\n- Use TypeScript interfaces alongside Mongoose schemas for type safety\n- Use \`.lean()\` for read-only queries (returns plain objects, better performance)\n- Use \`.select()\` to limit returned fields on large documents\n- Add indexes for frequently queried fields (\`{ index: true }\` or compound \`schema.index()\`)\n- Use \`.populate()\` sparingly — avoid deep nesting, prefer manual joins for complex queries\n- Use middleware (pre/post hooks) for cross-cutting concerns (timestamps, audit logs)\n- Use transactions (\`session\`) for multi-document operations that must be atomic\n- Always handle \`CastError\` (invalid ObjectId) at the controller/middleware level\n\n### Schema Pattern\n\`\`\`typescript\nimport mongoose, { Schema, Document } from 'mongoose';\n\nexport interface IUser extends Document {\n email: string;\n name: string;\n role: 'user' | 'admin';\n}\n\nconst UserSchema = new Schema<IUser>({\n email: { type: String, required: true, unique: true, index: true },\n name: { type: String, required: true },\n role: { type: String, enum: ['user', 'admin'], default: 'user' },\n}, { timestamps: true });\n\nexport const User = mongoose.model<IUser>('User', UserSchema);\n\`\`\`\n\n`
|
|
660
|
+
);
|
|
370
661
|
} else if (scan.backend.orm !== 'Prisma') {
|
|
371
662
|
content = content.replace(
|
|
372
663
|
/## Database Patterns\n\n[\s\S]*?(?=\n## )/,
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
project/
|
|
12
12
|
├── backend/ ← Backend (has its own package.json)
|
|
13
13
|
├── frontend/ ← Frontend (has its own package.json)
|
|
14
|
-
├── shared/ ← Shared
|
|
14
|
+
├── shared/ ← Shared type schemas (optional — see base-standards.mdc § Shared Types)
|
|
15
15
|
└── docs/ ← Documentation (no package.json)
|
|
16
16
|
```
|
|
17
17
|
|