sdd-workflow 1.1.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.
Files changed (41) hide show
  1. package/README.md +226 -0
  2. package/bin/sdd-init.js +59 -0
  3. package/package.json +30 -0
  4. package/src/installer.js +558 -0
  5. package/templates/skills/sdd-constitution/SKILL.md +128 -0
  6. package/templates/skills/sdd-implement/SKILL.md +153 -0
  7. package/templates/skills/sdd-init/SKILL.md +302 -0
  8. package/templates/skills/sdd-plan/SKILL.md +226 -0
  9. package/templates/skills/sdd-review/SKILL.md +498 -0
  10. package/templates/skills/sdd-run/SKILL.md +439 -0
  11. package/templates/skills/sdd-specify/SKILL.md +280 -0
  12. package/templates/skills/sdd-split/SKILL.md +432 -0
  13. package/templates/skills/sdd-tasks/SKILL.md +199 -0
  14. package/templates/skills/sdd-testcases/SKILL.md +235 -0
  15. package/templates/specify/README.md +179 -0
  16. package/templates/specify/scripts/create-feature.sh +144 -0
  17. package/templates/specify/templates/constitution-template.md +74 -0
  18. package/templates/specify/templates/plan-modular-template/README.md +98 -0
  19. package/templates/specify/templates/plan-modular-template/architecture.md +127 -0
  20. package/templates/specify/templates/plan-modular-template/backend-api.md +191 -0
  21. package/templates/specify/templates/plan-modular-template/backend-impl.md +134 -0
  22. package/templates/specify/templates/plan-modular-template/changelog.md +34 -0
  23. package/templates/specify/templates/plan-modular-template/data-model.md +130 -0
  24. package/templates/specify/templates/plan-modular-template/frontend-api.md +126 -0
  25. package/templates/specify/templates/plan-modular-template/frontend-impl.md +108 -0
  26. package/templates/specify/templates/plan-modular-template/performance.md +112 -0
  27. package/templates/specify/templates/plan-modular-template/security.md +85 -0
  28. package/templates/specify/templates/plan-template.md +190 -0
  29. package/templates/specify/templates/requirements/metadata-template.json +12 -0
  30. package/templates/specify/templates/requirements/original-template.md +26 -0
  31. package/templates/specify/templates/spec-modular-template/README.md +69 -0
  32. package/templates/specify/templates/spec-modular-template/acceptance-criteria.md +49 -0
  33. package/templates/specify/templates/spec-modular-template/changelog.md +27 -0
  34. package/templates/specify/templates/spec-modular-template/constraints.md +125 -0
  35. package/templates/specify/templates/spec-modular-template/overview.md +60 -0
  36. package/templates/specify/templates/spec-modular-template/user-stories.md +59 -0
  37. package/templates/specify/templates/spec-template.md +214 -0
  38. package/templates/specify/templates/tasks-modular-template/README.md +79 -0
  39. package/templates/specify/templates/tasks-template.md +232 -0
  40. package/templates/specify/templates/testcases-template.md +434 -0
  41. package/templates/teams/sdd-development-team.md +318 -0
@@ -0,0 +1,558 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const crypto = require('crypto');
6
+
7
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
8
+ const MANIFEST_FILE = '.sdd-manifest.json';
9
+
10
+ /**
11
+ * Install SDD workflow to target project directory.
12
+ * @param {string} targetDir - Absolute path to target project
13
+ * @param {object} options - Installation options
14
+ * @param {boolean} options.force - Overwrite existing files
15
+ * @param {boolean} options.dryRun - Preview only, don't write files
16
+ * @param {boolean} options.update - Incremental update: only update changed files, skip user-customized files
17
+ */
18
+ async function install(targetDir, options = {}) {
19
+ const { force = false, dryRun = false, update = false } = options;
20
+
21
+ if (update) {
22
+ return updateInstall(targetDir, options);
23
+ }
24
+
25
+ console.log('');
26
+ console.log('━━━ SDD Workflow Installer ━━━');
27
+ console.log('');
28
+
29
+ // 1. Validate target directory
30
+ if (!fs.existsSync(path.join(targetDir, '.git'))) {
31
+ console.log('⚠️ Warning: Target directory is not a git repository.');
32
+ console.log(' SDD workflow works best in a git-managed project.');
33
+ console.log('');
34
+ }
35
+
36
+ // 2. Define installation targets
37
+ const operations = [
38
+ // Skills → .claude/skills/sdd-*
39
+ {
40
+ description: 'SDD Skills',
41
+ source: path.join(TEMPLATES_DIR, 'skills'),
42
+ target: path.join(targetDir, '.claude', 'skills'),
43
+ type: 'skills',
44
+ },
45
+ // .specify/ directory structure
46
+ {
47
+ description: 'SDD Templates & Scripts',
48
+ source: path.join(TEMPLATES_DIR, 'specify'),
49
+ target: path.join(targetDir, '.specify'),
50
+ type: 'specify',
51
+ },
52
+ // Team configuration
53
+ {
54
+ description: 'Team Configuration',
55
+ source: path.join(TEMPLATES_DIR, 'teams'),
56
+ target: path.join(targetDir, '.claude', 'teams'),
57
+ type: 'teams',
58
+ },
59
+ ];
60
+
61
+ // 3. Execute installations
62
+ let totalFiles = 0;
63
+ let skippedFiles = 0;
64
+ let createdDirs = 0;
65
+ const installedFiles = []; // track for manifest
66
+
67
+ for (const op of operations) {
68
+ if (!fs.existsSync(op.source)) {
69
+ console.log(`⚠️ Source not found: ${op.description} — skipping`);
70
+ continue;
71
+ }
72
+
73
+ console.log(`📦 Installing ${op.description}...`);
74
+
75
+ const result = copyDirectory(op.source, op.target, {
76
+ force,
77
+ dryRun,
78
+ installedFiles,
79
+ sourceBase: op.source,
80
+ targetBase: op.target,
81
+ trackSkipped: true, // also track skipped files for manifest
82
+ });
83
+
84
+ totalFiles += result.copied;
85
+ skippedFiles += result.skipped;
86
+ createdDirs += result.dirs;
87
+ }
88
+
89
+ // 4. Create empty specs directory if not exists
90
+ const specsDir = path.join(targetDir, '.specify', 'specs');
91
+ if (!fs.existsSync(specsDir) && !dryRun) {
92
+ fs.mkdirSync(specsDir, { recursive: true });
93
+ }
94
+
95
+ // 5. Create empty memory directory if not exists
96
+ const memoryDir = path.join(targetDir, '.specify', 'memory');
97
+ if (!fs.existsSync(memoryDir) && !dryRun) {
98
+ fs.mkdirSync(path.join(targetDir, '.specify', 'memory'), { recursive: true });
99
+ }
100
+
101
+ // 6. Save manifest (tracks installed file hashes for future updates)
102
+ if (!dryRun) {
103
+ saveManifest(targetDir, installedFiles);
104
+ }
105
+
106
+ // 7. Output results
107
+ console.log('');
108
+ console.log('━━━ Installation Complete ━━━');
109
+ console.log('');
110
+ console.log(` Files installed: ${totalFiles}`);
111
+ console.log(` Files skipped: ${skippedFiles}`);
112
+ console.log(` Directories: ${createdDirs}`);
113
+ console.log('');
114
+
115
+ if (dryRun) {
116
+ console.log('🔍 This was a dry run. No files were actually created.');
117
+ console.log(' Run without --dry-run to install.');
118
+ return;
119
+ }
120
+
121
+ // 8. Print next steps
122
+ console.log('📋 Next Steps:');
123
+ console.log('');
124
+ console.log(' 1. Open Claude Code in this project');
125
+ console.log(' 2. Run /sdd-init to generate project constitution');
126
+ console.log(' (This analyzes your project and creates a tailored config)');
127
+ console.log('');
128
+ console.log(' 3. Start using SDD workflow:');
129
+ console.log(' /sdd-specify <feature-name> — Create specification');
130
+ console.log(' /sdd-testcases — Design test cases');
131
+ console.log(' /sdd-plan — Plan implementation');
132
+ console.log(' /sdd-tasks — Break down tasks');
133
+ console.log(' /sdd-implement — Execute development');
134
+ console.log(' /sdd-review — Quality review');
135
+ console.log(' /sdd-run <id> <description> — Full auto pipeline');
136
+ console.log('');
137
+ console.log(' 💡 To update SDD workflow in the future:');
138
+ console.log(' npx sdd-workflow --update');
139
+ console.log('');
140
+
141
+ // 9. Suggest CLAUDE.md additions
142
+ printClaudeMdSuggestions(targetDir);
143
+ }
144
+
145
+ /**
146
+ * Incremental update: only update files that changed in source,
147
+ * skip files that user has customized.
148
+ */
149
+ async function updateInstall(targetDir, options = {}) {
150
+ const { dryRun = false } = options;
151
+
152
+ console.log('');
153
+ console.log('━━━ SDD Workflow Updater ━━━');
154
+ console.log('');
155
+
156
+ // 1. Load existing manifest
157
+ const manifestPath = path.join(targetDir, '.specify', MANIFEST_FILE);
158
+ const manifest = loadManifest(targetDir);
159
+
160
+ if (!manifest) {
161
+ console.log('⚠️ No manifest found. This project was installed before manifest tracking was added.');
162
+ console.log('');
163
+ console.log(' To enable smart updates, run a regular install first to create the manifest:');
164
+ console.log(' npx sdd-workflow # Creates manifest without overwriting existing files');
165
+ console.log('');
166
+ console.log(' Then --update will correctly distinguish source updates from your customizations.');
167
+ console.log('');
168
+ console.log(' Falling back to conservative mode: all changed files treated as user-customized.');
169
+ console.log(' Use --force to overwrite everything.');
170
+ console.log('');
171
+ }
172
+
173
+ // 2. Collect all source files
174
+ const operations = [
175
+ {
176
+ description: 'SDD Skills',
177
+ source: path.join(TEMPLATES_DIR, 'skills'),
178
+ target: path.join(targetDir, '.claude', 'skills'),
179
+ },
180
+ {
181
+ description: 'SDD Templates & Scripts',
182
+ source: path.join(TEMPLATES_DIR, 'specify'),
183
+ target: path.join(targetDir, '.specify'),
184
+ },
185
+ {
186
+ description: 'Team Configuration',
187
+ source: path.join(TEMPLATES_DIR, 'teams'),
188
+ target: path.join(targetDir, '.claude', 'teams'),
189
+ },
190
+ ];
191
+
192
+ const stats = {
193
+ updated: [], // source changed, target was original → auto-update
194
+ newFiles: [], // not in target → auto-add
195
+ customized: [], // user modified → skip with warning
196
+ unchanged: [], // source == target → skip
197
+ removed: [], // in manifest but not in source → report
198
+ };
199
+
200
+ // 3. Compare source vs target using manifest
201
+ for (const op of operations) {
202
+ if (!fs.existsSync(op.source)) continue;
203
+
204
+ const sourceFiles = collectFiles(op.source);
205
+ for (const relPath of sourceFiles) {
206
+ const sourcePath = path.join(op.source, relPath);
207
+ const targetPath = path.join(op.target, relPath);
208
+ const sourceContent = fs.readFileSync(sourcePath);
209
+ const sourceHash = hashContent(sourceContent);
210
+
211
+ if (!fs.existsSync(targetPath)) {
212
+ // New file
213
+ stats.newFiles.push({ relPath: `${op.type || ''}/${relPath}`, sourcePath, targetPath, sourceContent });
214
+ continue;
215
+ }
216
+
217
+ const targetContent = fs.readFileSync(targetPath);
218
+ const targetHash = hashContent(targetContent);
219
+
220
+ if (sourceHash === targetHash) {
221
+ // Already up to date
222
+ stats.unchanged.push(relPath);
223
+ continue;
224
+ }
225
+
226
+ // Content differs - check if user customized
227
+ // Manifest records source hash from last install/update
228
+ const manifestEntry = manifest ? manifest.files[relPath] : null;
229
+
230
+ if (manifestEntry) {
231
+ if (targetHash === manifestEntry.hash) {
232
+ // Target matches original source → user hasn't modified → safe to update
233
+ stats.updated.push({ relPath, sourcePath, targetPath, sourceContent });
234
+ } else {
235
+ // Target differs from original source → user customized → skip
236
+ stats.customized.push({ relPath, sourcePath, targetPath, sourceContent });
237
+ }
238
+ } else {
239
+ // No manifest entry - conservative: treat all as customized
240
+ // User should run regular install first to create manifest
241
+ stats.customized.push({ relPath, sourcePath, targetPath, sourceContent });
242
+ }
243
+ }
244
+ }
245
+
246
+ // 4. Check for removed files (in manifest but not in source)
247
+ if (manifest) {
248
+ const sourceFilesSet = new Set();
249
+ for (const op of operations) {
250
+ if (!fs.existsSync(op.source)) continue;
251
+ for (const relPath of collectFiles(op.source)) {
252
+ sourceFilesSet.add(relPath);
253
+ }
254
+ }
255
+ for (const relPath of Object.keys(manifest.files)) {
256
+ if (!sourceFilesSet.has(relPath)) {
257
+ stats.removed.push(relPath);
258
+ }
259
+ }
260
+ }
261
+
262
+ // 5. Print update summary
263
+ console.log('📊 Update Analysis:');
264
+ console.log('');
265
+ console.log(` ✅ Up to date: ${stats.unchanged.length} file(s)`);
266
+ console.log(` 📥 Source updated: ${stats.updated.length} file(s) will be updated`);
267
+ console.log(` 🆕 New files: ${stats.newFiles.length} file(s) will be added`);
268
+ console.log(` ✏️ Customized: ${stats.customized.length} file(s) skipped (user modified)`);
269
+ if (stats.removed.length > 0) {
270
+ console.log(` 🗑️ Removed in source: ${stats.removed.length} file(s) (not deleted, manual cleanup needed)`);
271
+ }
272
+ console.log('');
273
+
274
+ if (stats.updated.length === 0 && stats.newFiles.length === 0) {
275
+ console.log('✅ Everything is up to date. No changes needed.');
276
+ return;
277
+ }
278
+
279
+ // 6. Show details of changes
280
+ if (stats.updated.length > 0) {
281
+ console.log('📥 Files to update:');
282
+ for (const f of stats.updated) {
283
+ console.log(` - ${f.relPath}`);
284
+ }
285
+ console.log('');
286
+ }
287
+
288
+ if (stats.newFiles.length > 0) {
289
+ console.log('🆕 New files to add:');
290
+ for (const f of stats.newFiles) {
291
+ console.log(` - ${f.relPath}`);
292
+ }
293
+ console.log('');
294
+ }
295
+
296
+ if (stats.customized.length > 0) {
297
+ console.log('⏭️ Customized files (skipped, use --force to overwrite):');
298
+ for (const f of stats.customized) {
299
+ console.log(` - ${f.relPath}`);
300
+ }
301
+ console.log('');
302
+ }
303
+
304
+ if (dryRun) {
305
+ console.log('🔍 This was a dry run. No files were actually updated.');
306
+ console.log(' Run without --dry-run to apply updates.');
307
+ return;
308
+ }
309
+
310
+ // 7. Apply updates
311
+ let appliedCount = 0;
312
+
313
+ for (const f of stats.updated) {
314
+ const parentDir = path.dirname(f.targetPath);
315
+ if (!fs.existsSync(parentDir)) {
316
+ fs.mkdirSync(parentDir, { recursive: true });
317
+ }
318
+ fs.writeFileSync(f.targetPath, f.sourceContent);
319
+ appliedCount++;
320
+ }
321
+
322
+ for (const f of stats.newFiles) {
323
+ const parentDir = path.dirname(f.targetPath);
324
+ if (!fs.existsSync(parentDir)) {
325
+ fs.mkdirSync(parentDir, { recursive: true });
326
+ }
327
+ fs.writeFileSync(f.targetPath, f.sourceContent);
328
+ appliedCount++;
329
+ }
330
+
331
+ // 8. Update manifest
332
+ const allInstalledFiles = [];
333
+ // Re-scan all installed files to rebuild manifest
334
+ for (const op of operations) {
335
+ if (!fs.existsSync(op.source)) continue;
336
+ for (const relPath of collectFiles(op.source)) {
337
+ const targetPath = path.join(op.target, relPath);
338
+ if (fs.existsSync(targetPath)) {
339
+ allInstalledFiles.push({
340
+ relPath,
341
+ targetPath,
342
+ });
343
+ }
344
+ }
345
+ }
346
+ saveManifest(targetDir, allInstalledFiles);
347
+
348
+ console.log(`✅ ${appliedCount} file(s) updated successfully.`);
349
+ console.log('');
350
+
351
+ if (stats.customized.length > 0) {
352
+ console.log('💡 To overwrite customized files, run:');
353
+ console.log(' npx sdd-workflow --force');
354
+ console.log('');
355
+ }
356
+ }
357
+
358
+ // ─── Manifest Management ───
359
+
360
+ /**
361
+ * Save installation manifest for future update tracking.
362
+ */
363
+ function saveManifest(targetDir, installedFiles) {
364
+ const manifest = {
365
+ version: getVersion(),
366
+ installedAt: new Date().toISOString(),
367
+ files: {},
368
+ };
369
+
370
+ for (const item of installedFiles) {
371
+ // Record SOURCE hash (not target hash) so we can detect user customizations later
372
+ // If source file exists, hash it; otherwise hash the target (e.g. for skipped files without source)
373
+ let hash;
374
+ if (item.sourcePath && fs.existsSync(item.sourcePath)) {
375
+ hash = hashContent(fs.readFileSync(item.sourcePath));
376
+ } else if (fs.existsSync(item.targetPath)) {
377
+ hash = hashContent(fs.readFileSync(item.targetPath));
378
+ }
379
+ if (hash) {
380
+ manifest.files[item.relPath] = {
381
+ hash: hash,
382
+ installedAt: new Date().toISOString(),
383
+ };
384
+ }
385
+ }
386
+
387
+ const manifestDir = path.join(targetDir, '.specify');
388
+ if (!fs.existsSync(manifestDir)) {
389
+ fs.mkdirSync(manifestDir, { recursive: true });
390
+ }
391
+
392
+ fs.writeFileSync(
393
+ path.join(manifestDir, MANIFEST_FILE),
394
+ JSON.stringify(manifest, null, 2)
395
+ );
396
+ }
397
+
398
+ /**
399
+ * Load existing manifest.
400
+ */
401
+ function loadManifest(targetDir) {
402
+ const manifestPath = path.join(targetDir, '.specify', MANIFEST_FILE);
403
+ if (!fs.existsSync(manifestPath)) return null;
404
+
405
+ try {
406
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
407
+ } catch (e) {
408
+ return null;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Get package version.
414
+ */
415
+ function getVersion() {
416
+ try {
417
+ const pkg = JSON.parse(
418
+ fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
419
+ );
420
+ return pkg.version;
421
+ } catch (e) {
422
+ return 'unknown';
423
+ }
424
+ }
425
+
426
+ // ─── File Utilities ───
427
+
428
+ /**
429
+ * Hash file content for comparison.
430
+ */
431
+ function hashContent(content) {
432
+ return crypto.createHash('md5').update(content).digest('hex');
433
+ }
434
+
435
+ /**
436
+ * Collect all files in a directory, returning relative paths.
437
+ */
438
+ function collectFiles(dir) {
439
+ const files = [];
440
+ if (!fs.existsSync(dir)) return files;
441
+
442
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
443
+ for (const entry of entries) {
444
+ const fullPath = path.join(dir, entry.name);
445
+ if (entry.isDirectory()) {
446
+ for (const subFile of collectFiles(fullPath)) {
447
+ files.push(path.join(entry.name, subFile));
448
+ }
449
+ } else {
450
+ files.push(entry.name);
451
+ }
452
+ }
453
+ return files;
454
+ }
455
+
456
+ /**
457
+ * Recursively copy a directory.
458
+ */
459
+ function copyDirectory(source, target, options) {
460
+ const result = { copied: 0, skipped: 0, dirs: 0 };
461
+
462
+ if (!fs.existsSync(source)) return result;
463
+
464
+ const entries = fs.readdirSync(source, { withFileTypes: true });
465
+
466
+ for (const entry of entries) {
467
+ const sourcePath = path.join(source, entry.name);
468
+ const targetPath = path.join(target, entry.name);
469
+
470
+ if (entry.isDirectory()) {
471
+ if (!options.dryRun && !fs.existsSync(targetPath)) {
472
+ fs.mkdirSync(targetPath, { recursive: true });
473
+ result.dirs++;
474
+ }
475
+ const subResult = copyDirectory(sourcePath, targetPath, options);
476
+ result.copied += subResult.copied;
477
+ result.skipped += subResult.skipped;
478
+ result.dirs += subResult.dirs;
479
+ } else {
480
+ if (fs.existsSync(targetPath) && !options.force) {
481
+ result.skipped++;
482
+ // Also track skipped files for manifest (to build baseline for --update)
483
+ if (options.installedFiles && options.sourceBase && options.trackSkipped) {
484
+ const relPath = path.relative(options.sourceBase, sourcePath);
485
+ options.installedFiles.push({ relPath, sourcePath, targetPath });
486
+ }
487
+ continue;
488
+ }
489
+
490
+ if (!options.dryRun) {
491
+ const parentDir = path.dirname(targetPath);
492
+ if (!fs.existsSync(parentDir)) {
493
+ fs.mkdirSync(parentDir, { recursive: true });
494
+ result.dirs++;
495
+ }
496
+ fs.copyFileSync(sourcePath, targetPath);
497
+ }
498
+ result.copied++;
499
+
500
+ // Track for manifest
501
+ if (options.installedFiles && options.sourceBase) {
502
+ const relPath = path.relative(options.sourceBase, sourcePath);
503
+ options.installedFiles.push({ relPath, sourcePath, targetPath });
504
+ }
505
+ }
506
+ }
507
+
508
+ return result;
509
+ }
510
+
511
+ /**
512
+ * Print suggestions for CLAUDE.md additions.
513
+ */
514
+ function printClaudeMdSuggestions(targetDir) {
515
+ const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
516
+ const hasClaudeMd = fs.existsSync(claudeMdPath);
517
+
518
+ console.log('━━━ CLAUDE.md Suggestions ━━━');
519
+ console.log('');
520
+
521
+ if (hasClaudeMd) {
522
+ console.log(' Add the following to your existing CLAUDE.md:');
523
+ } else {
524
+ console.log(' Create a CLAUDE.md with the following content:');
525
+ }
526
+
527
+ console.log('');
528
+ console.log(' ```markdown');
529
+ console.log(' ## SDD Workflow');
530
+ console.log('');
531
+ console.log(' This project uses SDD (Specification-Driven Development).');
532
+ console.log('');
533
+ console.log(' ### Quick Start');
534
+ console.log(' 1. `/sdd-init` — Initialize SDD for this project (first time only)');
535
+ console.log(' 2. `/sdd-run <id> <description>` — Full auto pipeline (recommended)');
536
+ console.log(' 3. Or use individual commands: /sdd-specify → /sdd-testcases → /sdd-plan → /sdd-tasks → /sdd-implement → /sdd-review');
537
+ console.log('');
538
+ console.log(' ### Document Structure');
539
+ console.log(' - `.specify/memory/constitution.md` — Project constitution (tech stack, principles)');
540
+ console.log(' - `.specify/specs/<id>-<name>/` — Feature specifications');
541
+ console.log(' - `.specify/templates/` — Document templates');
542
+ console.log(' ```');
543
+ console.log('');
544
+
545
+ // Check if .gitignore needs updating
546
+ const gitignorePath = path.join(targetDir, '.gitignore');
547
+ if (fs.existsSync(gitignorePath)) {
548
+ const gitignore = fs.readFileSync(gitignorePath, 'utf8');
549
+ if (!gitignore.includes('.specify/specs/')) {
550
+ console.log(' 💡 Consider adding to .gitignore:');
551
+ console.log(' .specify/specs/ # SDD feature specs (project-specific)');
552
+ console.log(' .specify/memory/ # SDD project memory');
553
+ console.log('');
554
+ }
555
+ }
556
+ }
557
+
558
+ module.exports = { install };
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: sdd-constitution
3
+ description: 创建或更新项目宪法(Constitution)- 定义项目的开发原则、技术栈约束和架构决策
4
+ invocable: true
5
+ ---
6
+
7
+ # SDD Constitution - 项目宪法生成器
8
+
9
+ 创建或更新项目宪法,定义项目的开发原则、技术栈约束和架构决策。
10
+
11
+ ## 执行步骤
12
+
13
+ ### 1. 读取模板
14
+ 读取模板文件: `.specify/templates/constitution-template.md`
15
+
16
+ ### 2. 收集项目信息
17
+
18
+ #### 2.1 读取项目文档
19
+ - `CLAUDE.md` - 项目配置和说明
20
+ - `README.md` - 项目概述
21
+ - `docs/` 目录下的相关文档
22
+
23
+ #### 2.2 自动检测项目结构
24
+
25
+ 扫描项目根目录,自动识别技术栈和模块结构:
26
+
27
+ | 检测文件 | 识别信息 |
28
+ |----------|----------|
29
+ | `package.json` | 前端/Node.js 技术栈、框架、依赖版本 |
30
+ | `pom.xml` / `build.gradle` | Java/JVM 后端技术栈、框架、依赖版本 |
31
+ | `go.mod` | Go 后端技术栈 |
32
+ | `Cargo.toml` | Rust 技术栈 |
33
+ | `requirements.txt` / `pyproject.toml` | Python 技术栈 |
34
+ | `*.sln` / `*.csproj` | .NET 技术栈 |
35
+
36
+ 根据检测结果,分析各模块的目录结构:
37
+ - 前端源码目录(从 `package.json` 的入口和构建配置推断)
38
+ - 后端源码目录(从构建配置和模块结构推断)
39
+ - 数据库相关配置(从配置文件推断)
40
+
41
+ #### 2.3 提取技术栈信息
42
+
43
+ 从检测文件中提取:
44
+ - 前端框架和版本(如 React/Vue/Angular/Svelte)
45
+ - 后端框架和版本(如 Spring Boot/Express/Django/FastAPI)
46
+ - 状态管理方案(如 MobX/Redux/Vuex/Zustand)
47
+ - 数据库类型和版本
48
+ - 测试框架
49
+ - 构建工具
50
+
51
+ 从现有代码中分析架构模式:
52
+ - 是否使用 DDD 分层(Domain/Application/Infrastructure)
53
+ - 是否使用传统三层架构(Controller/Service/DAO)
54
+ - 是否使用 Clean Architecture
55
+ - 是否使用六边形架构(Hexagonal)
56
+
57
+ ### 3. 生成宪法文档
58
+
59
+ 基于模板和收集的信息生成宪法文档。**宪法只记录开发约束和原则**,项目基本信息和技术栈由 CLAUDE.md 承载。
60
+
61
+ #### 必须包含的内容:
62
+ 1. **绝对铁律** - 不可违反的开发规则
63
+ 2. **开发原则** - 简单性原则、N+1禁令、代码质量、测试标准
64
+ 3. **架构约束** - 检测到的架构模式的分层原则、API响应格式
65
+ 4. **SDD文档职责边界** - spec/plan/tasks的What/How/When分工
66
+ 5. **SDD阶段合约与评审标准**(SDD v2 新增)
67
+ - 评审维度与权重
68
+ - 硬阈值规则
69
+ - 迭代规则
70
+ 6. **经验教训** - 实际开发中积累的案例
71
+
72
+ #### SDD v2 评审标准模板:
73
+
74
+ ```markdown
75
+ ## 七、SDD 阶段合约与评审标准
76
+
77
+ ### 7.1 评审维度与权重
78
+
79
+ | 维度 | 权重 | 硬阈值 | 评审重点 |
80
+ |------|------|--------|----------|
81
+ | 功能完整性 | ★★★ | ≥ 4/5 | spec 中定义的所有功能点是否实现?有无存根/TODO? |
82
+ | 需求一致性 | ★★★ | ≥ 4/5 | 实现是否与 spec/testcases/plan 一致?有无偏离? |
83
+ | 代码质量 | ★★ | ≥ 3/5 | 宪法约束遵守?N+1?命名?架构分层? |
84
+ | 边界处理 | ★★ | ≥ 3/5 | spec 中的边界条件和异常场景是否覆盖? |
85
+ | 集成完整性 | ★★ | ≥ 3/5 | 前后端对接完整?API 链路通畅?数据流闭环? |
86
+
87
+ ### 7.2 硬阈值规则
88
+
89
+ - 功能完整性 < 4 → 必须 ITERATE
90
+ - 需求一致性 < 4 → 必须 ITERATE
91
+ - 任意维度出现 HIGH 严重度问题 → 必须 ITERATE
92
+
93
+ ### 7.3 迭代规则
94
+
95
+ - 硬阈值未达标 → 必须迭代
96
+ - 评审问题中存在 HIGH 严重度 → 必须迭代
97
+ - 最多迭代 3 轮,超过则暂停等待用户决策
98
+ ```
99
+
100
+ ### 4. 保存文档
101
+ 将生成的文档保存到: `.specify/memory/constitution.md`
102
+
103
+ ### 5. 确认与修改
104
+ 向用户展示生成的文档,询问是否需要修改。
105
+
106
+ ## 输出示例
107
+
108
+ ```
109
+ ✅ 项目宪法已生成: .specify/memory/constitution.md
110
+
111
+ 文档包含以下章节:
112
+ - 绝对铁律
113
+ - 开发原则(简单性、N+1禁令、代码质量、测试标准)
114
+ - 架构约束(检测到的架构模式分层原则、API响应格式)
115
+ - SDD文档职责边界
116
+ - SDD阶段合约与评审标准
117
+ - 经验教训
118
+
119
+ 请检查文档内容,如需修改请告诉我。
120
+ ```
121
+
122
+ ## 注意事项
123
+
124
+ 1. **首次创建**: 如果是首次创建,需要全面分析项目并生成完整文档
125
+ 2. **更新模式**: 如果文档已存在,询问用户是追加还是覆盖
126
+ 3. **技术栈自适应**: 不预设特定技术栈,根据自动检测结果生成对应约束
127
+ 4. **架构模式自适应**: 根据检测到的架构模式(DDD/三层/Clean/六边形)生成对应的分层原则
128
+ 5. **CLAUDE.md 协作**: 项目基本信息和技术栈由 CLAUDE.md 承载,宪法聚焦于开发约束和原则