create-sdd-project 0.2.4 → 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/README.md +10 -10
- package/lib/config.js +1 -1
- package/lib/generator.js +144 -27
- package/lib/init-generator.js +321 -41
- package/lib/init-wizard.js +1 -1
- package/package.json +1 -1
- package/template/.claude/agents/backend-developer.md +1 -1
- package/template/.claude/agents/frontend-developer.md +1 -1
- package/template/.claude/settings.json +1 -1
- package/template/.claude/skills/bug-workflow/SKILL.md +2 -2
- package/template/.claude/skills/development-workflow/SKILL.md +18 -18
- package/template/.claude/skills/development-workflow/references/add-feature-template.md +16 -0
- package/template/.claude/skills/development-workflow/references/branching-strategy.md +1 -1
- package/template/.claude/skills/development-workflow/references/complexity-guide.md +6 -6
- package/template/.claude/skills/development-workflow/references/failure-handling.md +3 -3
- package/template/.claude/skills/development-workflow/references/pr-template.md +3 -3
- package/template/.claude/skills/development-workflow/references/ticket-template.md +3 -3
- package/template/.claude/skills/development-workflow/references/workflow-example.md +7 -7
- package/template/.claude/skills/project-memory/SKILL.md +9 -9
- package/template/.gemini/agents/backend-developer.md +1 -1
- package/template/.gemini/agents/frontend-developer.md +1 -1
- package/template/.gemini/commands/add-feature.toml +2 -0
- package/template/.gemini/commands/next-task.toml +2 -2
- package/template/.gemini/commands/show-progress.toml +2 -2
- package/template/.gemini/commands/start-task.toml +1 -1
- package/template/.gemini/skills/bug-workflow/SKILL.md +4 -4
- package/template/.gemini/skills/development-workflow/SKILL.md +18 -18
- package/template/.gemini/skills/development-workflow/references/add-feature-template.md +16 -0
- package/template/.gemini/skills/development-workflow/references/branching-strategy.md +1 -1
- package/template/.gemini/skills/development-workflow/references/complexity-guide.md +6 -6
- package/template/.gemini/skills/development-workflow/references/failure-handling.md +3 -3
- package/template/.gemini/skills/development-workflow/references/pr-template.md +3 -3
- package/template/.gemini/skills/development-workflow/references/ticket-template.md +3 -3
- package/template/.gemini/skills/development-workflow/references/workflow-example.md +7 -7
- package/template/.gemini/skills/project-memory/SKILL.md +8 -8
- package/template/AGENTS.md +6 -6
- package/template/CLAUDE.md +2 -2
- package/template/ai-specs/specs/base-standards.mdc +8 -8
- package/template/docs/project_notes/product-tracker.md +56 -0
- package/template/.claude/skills/development-workflow/references/sprint-init-template.md +0 -82
- package/template/.gemini/commands/init-sprint.toml +0 -2
- package/template/.gemini/skills/development-workflow/references/sprint-init-template.md +0 -82
- package/template/docs/project_notes/sprint-0-tracker.md +0 -66
package/README.md
CHANGED
|
@@ -36,8 +36,8 @@ Scans your project, detects your stack and architecture, and installs SDD files
|
|
|
36
36
|
Open in your AI coding tool and run:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
-
|
|
40
|
-
start task
|
|
39
|
+
add feature "your first feature"
|
|
40
|
+
start task F001
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
The workflow skill guides you through each step with checkpoints based on your autonomy level.
|
|
@@ -55,7 +55,7 @@ SDD DevFlow combines three proven practices:
|
|
|
55
55
|
### Why use SDD DevFlow?
|
|
56
56
|
|
|
57
57
|
- **AI agents work better with structure.** Without guardrails, AI coding assistants produce inconsistent results. SDD provides the methodology, standards, and workflow that make AI output predictable and high-quality.
|
|
58
|
-
- **Institutional memory across sessions.**
|
|
58
|
+
- **Institutional memory across sessions.** Product tracker, bug logs, and decision records survive context compaction and session boundaries.
|
|
59
59
|
- **Scales from solo to team.** Start at L1 (full control) while learning, scale to L4 (full auto) for repetitive tasks.
|
|
60
60
|
- **Works with your stack.** Not opinionated about frameworks — detects and adapts to Express, Fastify, NestJS, Next.js, Nuxt, Vue, Angular, and many more.
|
|
61
61
|
|
|
@@ -79,7 +79,7 @@ SDD DevFlow combines three proven practices:
|
|
|
79
79
|
|
|
80
80
|
| Skill | Trigger | What it does |
|
|
81
81
|
|-------|---------|-------------|
|
|
82
|
-
| `development-workflow` | `start task
|
|
82
|
+
| `development-workflow` | `start task F001`, `next task`, `add feature` | Orchestrates the complete 7-step workflow |
|
|
83
83
|
| `bug-workflow` | `report bug`, `fix bug`, `hotfix needed` | Bug triage, investigation, and resolution |
|
|
84
84
|
| `project-memory` | `set up project memory`, `log a bug fix` | Maintains institutional knowledge |
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ SDD DevFlow combines three proven practices:
|
|
|
87
87
|
|
|
88
88
|
```
|
|
89
89
|
0. SPEC → spec-creator drafts specs → Spec Approval
|
|
90
|
-
1. SETUP → Branch, ticket,
|
|
90
|
+
1. SETUP → Branch, ticket, product tracker → Ticket Approval
|
|
91
91
|
2. PLAN → Planner creates implementation plan → Plan Approval
|
|
92
92
|
3. IMPLEMENT → Developer agent, TDD
|
|
93
93
|
4. FINALIZE → Tests/lint/build, validator → Commit Approval
|
|
@@ -104,7 +104,7 @@ SDD DevFlow combines three proven practices:
|
|
|
104
104
|
|
|
105
105
|
| Level | Name | Human Checkpoints | Best For |
|
|
106
106
|
|-------|------|-------------------|----------|
|
|
107
|
-
| L1 | Full Control | All 5 | First
|
|
107
|
+
| L1 | Full Control | All 5 | First feature, learning SDD |
|
|
108
108
|
| L2 | Trusted | Plan + Merge | Normal development **(default)** |
|
|
109
109
|
| L3 | Autopilot | Merge only | Well-defined, repetitive tasks |
|
|
110
110
|
| L4 | Full Auto | None (CI/CD gates only) | Bulk simple tasks |
|
|
@@ -115,7 +115,7 @@ Quality gates (tests, lint, build, validators) **always run** regardless of leve
|
|
|
115
115
|
|
|
116
116
|
Tracks institutional knowledge across sessions in `docs/project_notes/`:
|
|
117
117
|
|
|
118
|
-
- **
|
|
118
|
+
- **product-tracker.md** — Feature backlog + Active Session (context recovery after compaction)
|
|
119
119
|
- **bugs.md** — Bug log with solutions and prevention notes
|
|
120
120
|
- **decisions.md** — Architectural Decision Records (ADRs)
|
|
121
121
|
- **key_facts.md** — Project configuration, ports, URLs, branching strategy
|
|
@@ -123,7 +123,7 @@ Tracks institutional knowledge across sessions in `docs/project_notes/`:
|
|
|
123
123
|
### Automated Hooks (Claude Code)
|
|
124
124
|
|
|
125
125
|
- **Quick Scan** — After developer agents finish, a fast grep-based scan (~2s, no API calls) checks for debug code, secrets, and TODOs
|
|
126
|
-
- **Compaction Recovery** — After context compaction, injects a reminder to read the
|
|
126
|
+
- **Compaction Recovery** — After context compaction, injects a reminder to read the product tracker for context recovery
|
|
127
127
|
|
|
128
128
|
### Multi-Tool Support
|
|
129
129
|
|
|
@@ -189,7 +189,7 @@ project/
|
|
|
189
189
|
│
|
|
190
190
|
└── docs/
|
|
191
191
|
├── project_notes/ # Project memory
|
|
192
|
-
│ ├──
|
|
192
|
+
│ ├── product-tracker.md
|
|
193
193
|
│ ├── key_facts.md
|
|
194
194
|
│ ├── bugs.md
|
|
195
195
|
│ └── decisions.md
|
|
@@ -237,7 +237,7 @@ Then look for `<!-- CONFIG: ... -->` comments in the files to customize.
|
|
|
237
237
|
## Roadmap
|
|
238
238
|
|
|
239
239
|
- **Agent Teams**: Parallel execution of independent tasks (waiting for Claude Code Agent Teams to stabilize)
|
|
240
|
-
- **PM Agent + L5 Autonomous**: AI-driven
|
|
240
|
+
- **PM Agent + L5 Autonomous**: AI-driven feature orchestration with human review at milestone boundaries
|
|
241
241
|
- **Retrofit Testing**: Automated test generation for existing projects with low coverage
|
|
242
242
|
|
|
243
243
|
## License
|
package/lib/config.js
CHANGED
|
@@ -78,7 +78,7 @@ const AI_TOOLS = [
|
|
|
78
78
|
];
|
|
79
79
|
|
|
80
80
|
const AUTONOMY_LEVELS = [
|
|
81
|
-
{ level: 1, name: 'Full Control', desc: 'Human approves every checkpoint (first
|
|
81
|
+
{ level: 1, name: 'Full Control', desc: 'Human approves every checkpoint (first feature, learning SDD)' },
|
|
82
82
|
{ level: 2, name: 'Trusted', desc: 'Human reviews plans + merges only (normal development)', default: true },
|
|
83
83
|
{ level: 3, name: 'Autopilot', desc: 'Human only approves merges (well-defined, repetitive tasks)' },
|
|
84
84
|
{ level: 4, name: 'Full Auto', desc: 'No human checkpoints, CI/CD gates only (bulk simple tasks)' },
|
package/lib/generator.js
CHANGED
|
@@ -41,11 +41,7 @@ function generate(config) {
|
|
|
41
41
|
step(`Setting branching: ${config.branching}`);
|
|
42
42
|
updateBranching(dest, config);
|
|
43
43
|
|
|
44
|
-
// 6.
|
|
45
|
-
step('Setting sprint dates to today');
|
|
46
|
-
updateSprintDates(dest);
|
|
47
|
-
|
|
48
|
-
// 7. Remove agents/specs based on project type
|
|
44
|
+
// 6. Remove agents/specs based on project type
|
|
49
45
|
if (config.projectType === 'backend') {
|
|
50
46
|
step('Removing frontend agents (backend only)');
|
|
51
47
|
removeFrontendFiles(dest, config);
|
|
@@ -54,6 +50,21 @@ function generate(config) {
|
|
|
54
50
|
removeBackendFiles(dest, config);
|
|
55
51
|
}
|
|
56
52
|
|
|
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
|
+
|
|
57
68
|
// 8. Remove AI tool config if single tool selected
|
|
58
69
|
if (config.aiTools === 'claude') {
|
|
59
70
|
step('Removing Gemini config (Claude only)');
|
|
@@ -76,7 +87,7 @@ function generate(config) {
|
|
|
76
87
|
console.log(`\nDone! Next steps:`);
|
|
77
88
|
console.log(` cd ${path.relative(process.cwd(), dest)}`);
|
|
78
89
|
console.log(` git init && git add -A && git commit -m "chore: initialize SDD DevFlow project"`);
|
|
79
|
-
console.log(` # Open in your AI coding tool and run:
|
|
90
|
+
console.log(` # Open in your AI coding tool and run: add feature "your first feature"\n`);
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
// --- Helpers ---
|
|
@@ -230,19 +241,6 @@ function updateBranching(dest, config) {
|
|
|
230
241
|
// Nothing else to update — branching is read from key_facts.md at runtime
|
|
231
242
|
}
|
|
232
243
|
|
|
233
|
-
function updateSprintDates(dest) {
|
|
234
|
-
const file = path.join(dest, 'docs', 'project_notes', 'sprint-0-tracker.md');
|
|
235
|
-
const today = new Date().toISOString().split('T')[0];
|
|
236
|
-
// Calculate 2-week sprint end
|
|
237
|
-
const endDate = new Date();
|
|
238
|
-
endDate.setDate(endDate.getDate() + 14);
|
|
239
|
-
const end = endDate.toISOString().split('T')[0];
|
|
240
|
-
|
|
241
|
-
replaceInFile(file, [
|
|
242
|
-
[/\[YYYY-MM-DD\] to \[YYYY-MM-DD\]/, `${today} to ${end}`],
|
|
243
|
-
]);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
244
|
function removeFrontendFiles(dest, config) {
|
|
247
245
|
// Remove frontend agents
|
|
248
246
|
for (const agent of FRONTEND_AGENTS) {
|
|
@@ -262,11 +260,7 @@ function removeFrontendFiles(dest, config) {
|
|
|
262
260
|
['├── frontend/ ← Frontend (has its own package.json)\n', ''],
|
|
263
261
|
]);
|
|
264
262
|
|
|
265
|
-
//
|
|
266
|
-
const trackerFile = path.join(dest, 'docs', 'project_notes', 'sprint-0-tracker.md');
|
|
267
|
-
replaceInFile(trackerFile, [
|
|
268
|
-
[/\n### Frontend\n\n\|.*\n\|.*\n\|.*\n/, '\n'],
|
|
269
|
-
]);
|
|
263
|
+
// Product tracker already defaults to backend — no change needed
|
|
270
264
|
}
|
|
271
265
|
|
|
272
266
|
function removeBackendFiles(dest, config) {
|
|
@@ -288,10 +282,10 @@ function removeBackendFiles(dest, config) {
|
|
|
288
282
|
['├── backend/ ← Backend (has its own package.json)\n', ''],
|
|
289
283
|
]);
|
|
290
284
|
|
|
291
|
-
//
|
|
292
|
-
const trackerFile = path.join(dest, 'docs', 'project_notes', '
|
|
285
|
+
// Update product tracker default feature type to frontend
|
|
286
|
+
const trackerFile = path.join(dest, 'docs', 'project_notes', 'product-tracker.md');
|
|
293
287
|
replaceInFile(trackerFile, [
|
|
294
|
-
[
|
|
288
|
+
['| backend | pending', '| frontend | pending'],
|
|
295
289
|
]);
|
|
296
290
|
}
|
|
297
291
|
|
|
@@ -310,4 +304,127 @@ function collectNotes(config) {
|
|
|
310
304
|
return notes;
|
|
311
305
|
}
|
|
312
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
|
+
|
|
313
430
|
module.exports = { generate };
|