create-sdd-project 0.6.0 → 0.7.0

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 CHANGED
@@ -168,6 +168,29 @@ npx create-sdd-project --doctor
168
168
 
169
169
  Exit code 1 if errors found — useful for CI pipelines.
170
170
 
171
+ ### Preview Changes (--diff)
172
+
173
+ See what `--init` or `--upgrade` would do without modifying anything:
174
+
175
+ ```bash
176
+ cd your-existing-project
177
+ npx create-sdd-project --init --diff # Preview init
178
+ npx create-sdd-project --upgrade --diff # Preview upgrade
179
+ ```
180
+
181
+ Shows detected stack, files that would be created/replaced/preserved, and standards status — zero filesystem writes.
182
+
183
+ ### CI/CD (Auto-Generated)
184
+
185
+ Every project gets a GitHub Actions CI workflow at `.github/workflows/ci.yml`, adapted to your stack:
186
+
187
+ - **PostgreSQL** projects get a `postgres:16` service with health checks
188
+ - **MongoDB** projects get a `mongo:7` service with health checks
189
+ - **Frontend-only** projects get a lightweight workflow without DB services
190
+ - **GitFlow** projects trigger on both `main` and `develop` branches
191
+
192
+ Customize the generated workflow as your project grows.
193
+
171
194
  ### After Setup
172
195
 
173
196
  Open your project in Claude Code or Gemini and start building:
@@ -313,6 +336,9 @@ project/
313
336
  ├── GEMINI.md # Gemini config (autonomy)
314
337
  ├── .env.example # Environment variables template
315
338
 
339
+ ├── .github/
340
+ │ └── workflows/ci.yml # CI pipeline (adapted to your stack)
341
+
316
342
  ├── .claude/
317
343
  │ ├── agents/ # 9 specialized agents
318
344
  │ ├── skills/
package/bin/cli.js CHANGED
@@ -11,8 +11,12 @@ const isInit = args.includes('--init');
11
11
  const isUpgrade = args.includes('--upgrade');
12
12
  const isDoctor = args.includes('--doctor');
13
13
  const isForce = args.includes('--force');
14
+ const isDiff = args.includes('--diff');
14
15
 
15
16
  async function main() {
17
+ if (isDiff) {
18
+ return runDiff();
19
+ }
16
20
  if (isDoctor) {
17
21
  return runDoctorCmd();
18
22
  }
@@ -216,6 +220,100 @@ async function runUpgrade() {
216
220
  generateUpgrade(config);
217
221
  }
218
222
 
223
+ async function runDiff() {
224
+ if (!isInit && !isUpgrade) {
225
+ console.error('Error: --diff must be combined with --init or --upgrade.');
226
+ console.error('Usage: create-sdd-project --init --diff');
227
+ console.error(' create-sdd-project --upgrade --diff');
228
+ process.exit(1);
229
+ }
230
+
231
+ if (projectName) {
232
+ console.error('Error: Cannot specify a project name with --diff.');
233
+ process.exit(1);
234
+ }
235
+
236
+ const cwd = process.cwd();
237
+
238
+ if (!fs.existsSync(path.join(cwd, 'package.json'))) {
239
+ console.error('Error: No package.json found in current directory.');
240
+ process.exit(1);
241
+ }
242
+
243
+ const { scan } = require('../lib/scanner');
244
+ const { buildInitDefaultConfig } = require('../lib/init-wizard');
245
+ const { runInitDiffReport, runUpgradeDiffReport } = require('../lib/diff-generator');
246
+
247
+ if (isInit) {
248
+ // Same validation as --init
249
+ if (fs.existsSync(path.join(cwd, 'ai-specs'))) {
250
+ console.error('Error: ai-specs/ directory already exists.');
251
+ console.error('SDD DevFlow appears to already be installed. Use --upgrade --diff instead.');
252
+ process.exit(1);
253
+ }
254
+
255
+ const scanResult = scan(cwd);
256
+ const config = buildInitDefaultConfig(scanResult);
257
+ runInitDiffReport(config);
258
+ return;
259
+ }
260
+
261
+ if (isUpgrade) {
262
+ // Same validation as --upgrade
263
+ if (!fs.existsSync(path.join(cwd, 'ai-specs'))) {
264
+ console.error('Error: ai-specs/ directory not found.');
265
+ console.error('SDD DevFlow does not appear to be installed. Use --init --diff instead.');
266
+ process.exit(1);
267
+ }
268
+
269
+ const {
270
+ readInstalledVersion,
271
+ getPackageVersion,
272
+ detectAiTools,
273
+ detectProjectType,
274
+ readAutonomyLevel,
275
+ collectCustomAgents,
276
+ collectCustomCommands,
277
+ } = require('../lib/upgrade-generator');
278
+
279
+ const installedVersion = readInstalledVersion(cwd);
280
+ const packageVersion = getPackageVersion();
281
+
282
+ if (installedVersion === packageVersion && !isForce) {
283
+ console.log(`\nSDD DevFlow is already at version ${packageVersion}.`);
284
+ console.log('Use --force --diff to preview a re-install.\n');
285
+ return;
286
+ }
287
+
288
+ const scanResult = scan(cwd);
289
+ const aiTools = detectAiTools(cwd);
290
+ const projectType = detectProjectType(cwd);
291
+ const autonomy = readAutonomyLevel(cwd);
292
+ const customAgents = collectCustomAgents(cwd);
293
+ const customCommands = collectCustomCommands(cwd);
294
+ const settingsLocal = fs.existsSync(path.join(cwd, '.claude', 'settings.local.json'));
295
+
296
+ const config = buildInitDefaultConfig(scanResult);
297
+ config.aiTools = aiTools;
298
+ config.projectType = projectType;
299
+ config.autonomyLevel = autonomy.level;
300
+ config.autonomyName = autonomy.name;
301
+ config.installedVersion = installedVersion;
302
+
303
+ const state = {
304
+ installedVersion,
305
+ packageVersion,
306
+ aiTools,
307
+ projectType,
308
+ customAgents,
309
+ customCommands,
310
+ settingsLocal,
311
+ };
312
+
313
+ runUpgradeDiffReport(config, state);
314
+ }
315
+ }
316
+
219
317
  main().catch((err) => {
220
318
  console.error('\nError:', err.message);
221
319
  process.exit(1);
@@ -0,0 +1,274 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const {
6
+ FRONTEND_AGENTS,
7
+ BACKEND_AGENTS,
8
+ TEMPLATE_AGENTS,
9
+ } = require('./config');
10
+ const {
11
+ adaptBaseStandards,
12
+ adaptBackendStandards,
13
+ adaptFrontendStandards,
14
+ } = require('./init-generator');
15
+ const {
16
+ isStandardModified,
17
+ getPackageVersion,
18
+ } = require('./upgrade-generator');
19
+ const { formatScanSummary } = require('./init-wizard');
20
+
21
+ const templateDir = path.join(__dirname, '..', 'template');
22
+
23
+ /**
24
+ * Count files recursively in a directory.
25
+ */
26
+ function countFiles(dir) {
27
+ if (!fs.existsSync(dir)) return 0;
28
+ let count = 0;
29
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
30
+ if (entry.isDirectory()) {
31
+ count += countFiles(path.join(dir, entry.name));
32
+ } else {
33
+ count++;
34
+ }
35
+ }
36
+ return count;
37
+ }
38
+
39
+ /**
40
+ * Compute how many agents would be kept after project-type filtering.
41
+ */
42
+ function countAgents(projectType) {
43
+ const all = TEMPLATE_AGENTS.length; // 9
44
+ if (projectType === 'backend') return all - FRONTEND_AGENTS.length;
45
+ if (projectType === 'frontend') return all - BACKEND_AGENTS.length;
46
+ return all;
47
+ }
48
+
49
+ /**
50
+ * Compute how many standards would be created for a project type.
51
+ * base + documentation + backend (if not frontend) + frontend (if not backend)
52
+ */
53
+ function countStandards(projectType) {
54
+ let count = 2; // base + documentation
55
+ if (projectType !== 'frontend') count++; // backend
56
+ if (projectType !== 'backend') count++; // frontend
57
+ return count;
58
+ }
59
+
60
+ // ── Init Diff ────────────────────────────────────────────────
61
+
62
+ function runInitDiffReport(config) {
63
+ const dest = config.projectDir;
64
+ const scan = config.scanResult;
65
+ const projectType = config.projectType;
66
+ const aiTools = config.aiTools;
67
+
68
+ const lines = [];
69
+ lines.push('\n🔍 SDD DevFlow — Preview (--init)\n');
70
+
71
+ // Detected stack
72
+ lines.push(' Detected stack:');
73
+ lines.push(formatScanSummary(scan));
74
+ lines.push('');
75
+
76
+ // Compute what would be created
77
+ const wouldCreate = [];
78
+
79
+ // AI tool directories
80
+ const agentCount = countAgents(projectType);
81
+ const removedAgents = TEMPLATE_AGENTS.length - agentCount;
82
+
83
+ if (aiTools !== 'gemini') {
84
+ const claudeSkills = countFiles(path.join(templateDir, '.claude', 'skills'));
85
+ wouldCreate.push(` .claude/agents/ ${agentCount} agents${removedAgents > 0 ? ` (${removedAgents} removed: ${projectType}-only)` : ''}`);
86
+ wouldCreate.push(` .claude/skills/ ${claudeSkills} files (4 skills)`);
87
+ wouldCreate.push(' .claude/hooks/ quick-scan.sh');
88
+ wouldCreate.push(' .claude/settings.json');
89
+ wouldCreate.push(' .claude/commands/ .gitkeep');
90
+ }
91
+
92
+ if (aiTools !== 'claude') {
93
+ const geminiSkills = countFiles(path.join(templateDir, '.gemini', 'skills'));
94
+ const geminiCmds = countFiles(path.join(templateDir, '.gemini', 'commands'));
95
+ wouldCreate.push(` .gemini/agents/ ${agentCount} agents${removedAgents > 0 ? ` (${removedAgents} removed: ${projectType}-only)` : ''}`);
96
+ wouldCreate.push(` .gemini/skills/ ${geminiSkills} files (4 skills)`);
97
+ wouldCreate.push(` .gemini/commands/ ${geminiCmds} commands`);
98
+ wouldCreate.push(' .gemini/styles/ default.md');
99
+ wouldCreate.push(' .gemini/settings.json');
100
+ }
101
+
102
+ // Standards
103
+ const stdCount = countStandards(projectType);
104
+ const stdDetails = ['base-standards'];
105
+ if (projectType !== 'frontend') stdDetails.push('backend-standards');
106
+ if (projectType !== 'backend') stdDetails.push('frontend-standards');
107
+ stdDetails.push('documentation-standards');
108
+ wouldCreate.push(` ai-specs/specs/ ${stdCount} standards (${stdDetails.join(', ')})`);
109
+
110
+ // Docs
111
+ wouldCreate.push(' docs/project_notes/ 4 files (bugs, decisions, key_facts, product-tracker)');
112
+ const specFiles = [];
113
+ if (projectType !== 'frontend') specFiles.push('api-spec.yaml');
114
+ if (projectType !== 'backend') specFiles.push('ui-components.md');
115
+ if (specFiles.length > 0) {
116
+ wouldCreate.push(` docs/specs/ ${specFiles.join(', ')}`);
117
+ }
118
+ wouldCreate.push(' docs/tickets/ .gitkeep');
119
+
120
+ // Top-level files
121
+ wouldCreate.push(' AGENTS.md');
122
+ if (aiTools !== 'gemini') wouldCreate.push(' CLAUDE.md');
123
+ if (aiTools !== 'claude') wouldCreate.push(' GEMINI.md');
124
+ wouldCreate.push(' .env.example');
125
+ wouldCreate.push(' .github/workflows/ci.yml');
126
+ wouldCreate.push(' .sdd-version');
127
+
128
+ lines.push(` Would create (${wouldCreate.length} items):`);
129
+ for (const item of wouldCreate) {
130
+ lines.push(` + ${item.trim()}`);
131
+ }
132
+
133
+ // .gitignore handling
134
+ const gitignorePath = path.join(dest, '.gitignore');
135
+ if (fs.existsSync(gitignorePath)) {
136
+ lines.push('\n Would modify:');
137
+ lines.push(' ~ .gitignore append SDD entries');
138
+ } else {
139
+ lines.push('\n Would create:');
140
+ lines.push(' + .gitignore with SDD entries');
141
+ }
142
+
143
+ lines.push('\n No files deleted. Run without --diff to apply.\n');
144
+
145
+ console.log(lines.join('\n'));
146
+ }
147
+
148
+ // ── Upgrade Diff ─────────────────────────────────────────────
149
+
150
+ function runUpgradeDiffReport(config, state) {
151
+ const dest = config.projectDir;
152
+ const scan = config.scanResult;
153
+ const projectType = config.projectType;
154
+ const aiTools = config.aiTools;
155
+
156
+ const lines = [];
157
+ lines.push('\n🔍 SDD DevFlow — Preview (--upgrade)\n');
158
+ lines.push(` ${state.installedVersion} → ${state.packageVersion}\n`);
159
+
160
+ // Would replace
161
+ const replaceItems = [];
162
+
163
+ if (aiTools !== 'gemini') {
164
+ const agentCount = countAgents(projectType);
165
+ replaceItems.push(`.claude/agents/ ${agentCount} agents`);
166
+ replaceItems.push('.claude/skills/ 4 skills');
167
+ replaceItems.push('.claude/hooks/ quick-scan.sh');
168
+ replaceItems.push('.claude/settings.json hooks updated, permissions preserved');
169
+ }
170
+ if (aiTools !== 'claude') {
171
+ const agentCount = countAgents(projectType);
172
+ replaceItems.push(`.gemini/agents/ ${agentCount} agents`);
173
+ replaceItems.push('.gemini/skills/ 4 skills');
174
+ replaceItems.push('.gemini/commands/ TOML commands');
175
+ replaceItems.push('.gemini/styles/ default.md');
176
+ replaceItems.push('.gemini/settings.json');
177
+ }
178
+ replaceItems.push('AGENTS.md');
179
+ if (aiTools !== 'gemini') replaceItems.push('CLAUDE.md');
180
+ if (aiTools !== 'claude') replaceItems.push('GEMINI.md');
181
+ replaceItems.push('.env.example custom vars preserved');
182
+ replaceItems.push('.sdd-version');
183
+
184
+ lines.push(` Would replace (${replaceItems.length} items):`);
185
+ for (const item of replaceItems) {
186
+ lines.push(` ✓ ${item}`);
187
+ }
188
+
189
+ // Would preserve
190
+ const preserveItems = [];
191
+ if (state.settingsLocal) preserveItems.push('.claude/settings.local.json personal settings');
192
+ for (const c of state.customCommands) preserveItems.push(`${c.relativePath} custom command`);
193
+ for (const a of state.customAgents) preserveItems.push(`${a.relativePath} custom agent`);
194
+ preserveItems.push('docs/project_notes/* project memory');
195
+ preserveItems.push('docs/specs/* your specs');
196
+ preserveItems.push('docs/tickets/* your tickets');
197
+ preserveItems.push('.gitignore');
198
+
199
+ lines.push(`\n Would preserve (${preserveItems.length} items):`);
200
+ for (const item of preserveItems) {
201
+ lines.push(` ⊘ ${item}`);
202
+ }
203
+
204
+ // Standards diff
205
+ const standardsStatus = [];
206
+
207
+ const baseStdPath = path.join(dest, 'ai-specs', 'specs', 'base-standards.mdc');
208
+ if (fs.existsSync(baseStdPath)) {
209
+ const existing = fs.readFileSync(baseStdPath, 'utf8');
210
+ const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'base-standards.mdc'), 'utf8');
211
+ const fresh = adaptBaseStandards(template, scan, config);
212
+ standardsStatus.push({
213
+ name: 'base-standards.mdc',
214
+ modified: isStandardModified(existing, fresh),
215
+ });
216
+ }
217
+
218
+ if (projectType !== 'frontend') {
219
+ const backendStdPath = path.join(dest, 'ai-specs', 'specs', 'backend-standards.mdc');
220
+ if (fs.existsSync(backendStdPath)) {
221
+ const existing = fs.readFileSync(backendStdPath, 'utf8');
222
+ const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'backend-standards.mdc'), 'utf8');
223
+ const fresh = adaptBackendStandards(template, scan);
224
+ standardsStatus.push({
225
+ name: 'backend-standards.mdc',
226
+ modified: isStandardModified(existing, fresh),
227
+ });
228
+ }
229
+ }
230
+
231
+ if (projectType !== 'backend') {
232
+ const frontendStdPath = path.join(dest, 'ai-specs', 'specs', 'frontend-standards.mdc');
233
+ if (fs.existsSync(frontendStdPath)) {
234
+ const existing = fs.readFileSync(frontendStdPath, 'utf8');
235
+ const template = fs.readFileSync(path.join(templateDir, 'ai-specs', 'specs', 'frontend-standards.mdc'), 'utf8');
236
+ const fresh = adaptFrontendStandards(template, scan);
237
+ standardsStatus.push({
238
+ name: 'frontend-standards.mdc',
239
+ modified: isStandardModified(existing, fresh),
240
+ });
241
+ }
242
+ }
243
+
244
+ standardsStatus.push({ name: 'documentation-standards.mdc', modified: false });
245
+
246
+ lines.push('\n Standards:');
247
+ for (const s of standardsStatus) {
248
+ if (s.modified) {
249
+ lines.push(` ⚠ ${s.name} customized → preserve`);
250
+ } else {
251
+ lines.push(` ✓ ${s.name} unchanged → update`);
252
+ }
253
+ }
254
+
255
+ // Would add (new files)
256
+ const addItems = [];
257
+ const ciPath = path.join(dest, '.github', 'workflows', 'ci.yml');
258
+ if (!fs.existsSync(ciPath)) {
259
+ addItems.push('.github/workflows/ci.yml');
260
+ }
261
+
262
+ if (addItems.length > 0) {
263
+ lines.push(`\n Would add (${addItems.length} new):`);
264
+ for (const item of addItems) {
265
+ lines.push(` + ${item}`);
266
+ }
267
+ }
268
+
269
+ lines.push('\n Run without --diff to apply.\n');
270
+
271
+ console.log(lines.join('\n'));
272
+ }
273
+
274
+ module.exports = { runInitDiffReport, runUpgradeDiffReport };
package/lib/generator.js CHANGED
@@ -66,6 +66,10 @@ function generate(config) {
66
66
  adaptAgentContent(dest, config);
67
67
  }
68
68
 
69
+ // 7c. Adapt CI workflow for project type and stack
70
+ step('Configuring CI workflow');
71
+ adaptCiWorkflow(dest, config);
72
+
69
73
  // 8. Remove AI tool config if single tool selected
70
74
  if (config.aiTools === 'claude') {
71
75
  step('Removing Gemini config (Claude only)');
@@ -350,4 +354,46 @@ function adaptAgentContent(dest, config) {
350
354
  adaptAgentContentForProjectType(dest, config, replaceInFile);
351
355
  }
352
356
 
357
+ function adaptCiWorkflow(dest, config) {
358
+ const ciPath = path.join(dest, '.github', 'workflows', 'ci.yml');
359
+ if (!fs.existsSync(ciPath)) return;
360
+
361
+ const bPreset = config.backendPreset || BACKEND_STACKS[0];
362
+
363
+ // Adapt branches for gitflow
364
+ if (config.branching === 'gitflow') {
365
+ replaceInFile(ciPath, [
366
+ ['branches: [main]', 'branches: [main, develop]'],
367
+ ]);
368
+ }
369
+
370
+ // Adapt DB services based on stack
371
+ if (config.projectType === 'frontend') {
372
+ // Frontend-only: remove services and DATABASE_URL
373
+ replaceInFile(ciPath, [
374
+ [/\n # -- Database service.*?--health-retries=5\n/s, ''],
375
+ [/\n DATABASE_URL:.*\n/, '\n'],
376
+ ]);
377
+ } else if (bPreset.db === 'MongoDB') {
378
+ // MongoDB: replace postgres service with mongo
379
+ replaceInFile(ciPath, [
380
+ [/ # -- Database service.*?--health-retries=5\n/s,
381
+ ` # -- Database service (remove if not needed) --\n` +
382
+ ` services:\n` +
383
+ ` mongodb:\n` +
384
+ ` image: mongo:7\n` +
385
+ ` ports:\n` +
386
+ ` - 27017:27017\n` +
387
+ ` options: >-\n` +
388
+ ` --health-cmd="mongosh --eval 'db.runCommand({ping:1})'"\n` +
389
+ ` --health-interval=10s\n` +
390
+ ` --health-timeout=5s\n` +
391
+ ` --health-retries=5\n`],
392
+ ['DATABASE_URL: postgresql://test:test@localhost:5432/testdb',
393
+ 'MONGODB_URI: mongodb://localhost:27017/testdb'],
394
+ ]);
395
+ }
396
+ // Default (PostgreSQL) — template already has correct services
397
+ }
398
+
353
399
  module.exports = { generate };
@@ -184,7 +184,20 @@ function generateInit(config) {
184
184
  // 8. Append to .gitignore
185
185
  appendGitignore(dest, skipped);
186
186
 
187
- // 9. Copy and adapt .env.example if not present
187
+ // 9. Copy CI workflow if not present
188
+ const githubDir = path.join(dest, '.github', 'workflows');
189
+ const ciPath = path.join(githubDir, 'ci.yml');
190
+ if (!fs.existsSync(ciPath)) {
191
+ step('Creating .github/workflows/ci.yml');
192
+ ensureDir(githubDir);
193
+ let ciContent = fs.readFileSync(path.join(templateDir, '.github', 'workflows', 'ci.yml'), 'utf8');
194
+ ciContent = adaptCiWorkflowContent(ciContent, config, scan);
195
+ fs.writeFileSync(ciPath, ciContent, 'utf8');
196
+ } else {
197
+ skipped.push('.github/workflows/ci.yml');
198
+ }
199
+
200
+ // 10. Copy and adapt .env.example if not present
188
201
  const envExamplePath = path.join(dest, '.env.example');
189
202
  if (!fs.existsSync(envExamplePath)) {
190
203
  let envContent = fs.readFileSync(path.join(templateDir, '.env.example'), 'utf8');
@@ -950,6 +963,47 @@ function updateAutonomy(dest, config) {
950
963
  }
951
964
  }
952
965
 
966
+ // --- CI Workflow Adaptation ---
967
+
968
+ function adaptCiWorkflowContent(template, config, scan) {
969
+ let content = template;
970
+
971
+ // Adapt branches for gitflow
972
+ if (config.branching === 'gitflow') {
973
+ content = content.replaceAll('branches: [main]', 'branches: [main, develop]');
974
+ }
975
+
976
+ // Adapt DB services based on detected stack
977
+ if (!scan.backend.detected) {
978
+ // Frontend-only: remove services block and DATABASE_URL
979
+ content = content.replace(/\n # -- Database service.*?--health-retries=5\n/s, '');
980
+ content = content.replace(/\n DATABASE_URL:.*\n/, '\n');
981
+ } else if (scan.backend.db === 'MongoDB') {
982
+ // MongoDB: replace postgres service with mongo
983
+ content = content.replace(
984
+ / # -- Database service.*?--health-retries=5\n/s,
985
+ ` # -- Database service (remove if not needed) --\n` +
986
+ ` services:\n` +
987
+ ` mongodb:\n` +
988
+ ` image: mongo:7\n` +
989
+ ` ports:\n` +
990
+ ` - 27017:27017\n` +
991
+ ` options: >-\n` +
992
+ ` --health-cmd="mongosh --eval 'db.runCommand({ping:1})'"\n` +
993
+ ` --health-interval=10s\n` +
994
+ ` --health-timeout=5s\n` +
995
+ ` --health-retries=5\n`
996
+ );
997
+ content = content.replace(
998
+ 'DATABASE_URL: postgresql://test:test@localhost:5432/testdb',
999
+ 'MONGODB_URI: mongodb://localhost:27017/testdb'
1000
+ );
1001
+ }
1002
+ // Default (PostgreSQL) — template already has correct services
1003
+
1004
+ return content;
1005
+ }
1006
+
953
1007
  // --- .env.example Adaptation ---
954
1008
 
955
1009
  function adaptEnvExample(template, config, scan) {
@@ -1008,6 +1062,7 @@ module.exports = {
1008
1062
  adaptAgentsMd,
1009
1063
  adaptCopiedFiles,
1010
1064
  adaptEnvExample,
1065
+ adaptCiWorkflowContent,
1011
1066
  updateAutonomy,
1012
1067
  regexReplaceInFile,
1013
1068
  };
@@ -283,4 +283,4 @@ function buildInitDefaultConfig(scanResult) {
283
283
  return config;
284
284
  }
285
285
 
286
- module.exports = { runInitWizard, buildInitDefaultConfig };
286
+ module.exports = { runInitWizard, buildInitDefaultConfig, formatScanSummary };
@@ -16,6 +16,7 @@ const {
16
16
  adaptAgentsMd,
17
17
  adaptCopiedFiles,
18
18
  adaptEnvExample,
19
+ adaptCiWorkflowContent,
19
20
  updateAutonomy,
20
21
  regexReplaceInFile,
21
22
  } = require('./init-generator');
@@ -408,6 +409,17 @@ function generateUpgrade(config) {
408
409
 
409
410
  step('Replaced AGENTS.md, CLAUDE.md/GEMINI.md, .env.example');
410
411
 
412
+ // --- e2) CI workflow — only add if not present (user may have customized) ---
413
+ const ciPath = path.join(dest, '.github', 'workflows', 'ci.yml');
414
+ if (!fs.existsSync(ciPath)) {
415
+ fs.mkdirSync(path.join(dest, '.github', 'workflows'), { recursive: true });
416
+ let ciContent = fs.readFileSync(path.join(templateDir, '.github', 'workflows', 'ci.yml'), 'utf8');
417
+ ciContent = adaptCiWorkflowContent(ciContent, config, scan);
418
+ fs.writeFileSync(ciPath, ciContent, 'utf8');
419
+ step('Added .github/workflows/ci.yml');
420
+ replaced++;
421
+ }
422
+
411
423
  // --- f) Adapt for project type ---
412
424
  // Remove agents for single-stack projects
413
425
  if (projectType === 'backend') {
@@ -500,5 +512,6 @@ module.exports = {
500
512
  readAutonomyLevel,
501
513
  collectCustomAgents,
502
514
  collectCustomCommands,
515
+ isStandardModified,
503
516
  buildSummary,
504
517
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sdd-project",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Create a new SDD DevFlow project with AI-assisted development workflow",
5
5
  "bin": {
6
6
  "create-sdd-project": "bin/cli.js"
@@ -0,0 +1,56 @@
1
+ # ===========================================
2
+ # CI Pipeline — Generated by SDD DevFlow
3
+ # ===========================================
4
+ # Runs on pushes and PRs to main branch.
5
+ # Customize steps as your project grows.
6
+
7
+ name: CI
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+ branches: [main]
14
+
15
+ jobs:
16
+ ci:
17
+ runs-on: ubuntu-latest
18
+
19
+ # -- Database service (remove if not needed) --
20
+ services:
21
+ postgres:
22
+ image: postgres:16
23
+ env:
24
+ POSTGRES_USER: test
25
+ POSTGRES_PASSWORD: test
26
+ POSTGRES_DB: testdb
27
+ ports:
28
+ - 5432:5432
29
+ options: >-
30
+ --health-cmd="pg_isready"
31
+ --health-interval=10s
32
+ --health-timeout=5s
33
+ --health-retries=5
34
+
35
+ env:
36
+ NODE_ENV: test
37
+ DATABASE_URL: postgresql://test:test@localhost:5432/testdb
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - uses: actions/setup-node@v4
43
+ with:
44
+ node-version: 20
45
+ cache: npm
46
+
47
+ - run: npm ci
48
+
49
+ - name: Lint
50
+ run: npm run lint --if-present
51
+
52
+ - name: Test
53
+ run: npm test --if-present
54
+
55
+ - name: Build
56
+ run: npm run build --if-present