musubi-sdd 0.8.1 → 0.8.2

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.ja.md CHANGED
@@ -14,6 +14,7 @@ MUSUBIは、6つの主要フレームワークのベスト機能を統合した
14
14
  - その他4エージェント: AGENTS.md(互換形式)
15
15
  - 📋 **憲法ガバナンス** - 9つの不変条項 + フェーズ-1ゲートによる品質保証
16
16
  - 📝 **EARS要件ジェネレーター** - 5つのEARSパターンで明確な要件を作成(v0.8.0)
17
+ - 🏗️ **設計ドキュメントジェネレーター** - トレーサビリティ付きC4モデルとADRを作成(v0.8.2)
17
18
  - 🔄 **差分仕様** - ブラウンフィールドおよびグリーンフィールドプロジェクト対応
18
19
  - 🧭 **自動更新プロジェクトメモリ** - ステアリングシステムがアーキテクチャ、技術スタック、製品コンテキストを維持
19
20
  - 🚀 **自動オンボーディング** - `musubi-onboard` が既存プロジェクトを分析し、ステアリングドキュメントを生成(2-5分)
@@ -105,12 +106,20 @@ musubi-validate gates # フェーズ-1ゲートを検証
105
106
  musubi-validate complexity # 複雑度制限をチェック
106
107
  musubi-validate all -v # 詳細付き完全検証
107
108
 
108
- # EARS要件の作成(v0.8.0)
109
+ # EARS要件ドキュメント生成(v0.8.0)
109
110
  musubi-requirements init "ユーザー認証" # 要件ドキュメント初期化
110
111
  musubi-requirements add # インタラクティブに要件追加
111
112
  musubi-requirements list # 全要件リスト表示
112
- musubi-requirements validate # EARS形式検証
113
+ musubi-requirements validate # EARS形式を検証
113
114
  musubi-requirements trace # トレーサビリティマトリクス表示
115
+
116
+ # 設計ドキュメント生成(v0.8.2)
117
+ musubi-design init "ユーザー認証" # 設計ドキュメント初期化
118
+ musubi-design add-c4 context # C4コンテキスト図を追加
119
+ musubi-design add-c4 container --format plantuml # PlantUMLでコンテナ図追加
120
+ musubi-design add-adr "JWTトークン使用" # アーキテクチャ決定記録追加
121
+ musubi-design validate # 設計完全性を検証
122
+ musubi-design trace # 要件トレーサビリティ表示
114
123
  ```
115
124
 
116
125
  ### プロジェクトタイプ
package/README.md CHANGED
@@ -18,6 +18,7 @@ MUSUBI is a comprehensive SDD (Specification Driven Development) framework that
18
18
  - Other 4 agents: AGENTS.md (compatible format)
19
19
  - 📋 **Constitutional Governance** - 9 immutable articles + Phase -1 Gates for quality enforcement
20
20
  - 📝 **EARS Requirements Generator** - Create unambiguous requirements with 5 EARS patterns (v0.8.0)
21
+ - 🏗️ **Design Document Generator** - Create C4 models and ADRs with traceability (v0.8.2)
21
22
  - 🔄 **Delta Specifications** - Brownfield and greenfield project support
22
23
  - 🧭 **Auto-Updating Project Memory** - Steering system maintains architecture, tech stack, and product context
23
24
  - 🚀 **Automatic Onboarding** - `musubi-onboard` analyzes existing projects and generates steering docs (2-5 minutes)
@@ -109,12 +110,20 @@ musubi-validate gates # Validate Phase -1 Gates
109
110
  musubi-validate complexity # Check complexity limits
110
111
  musubi-validate all -v # Full validation with details
111
112
 
112
- # Create EARS requirements (v0.8.0)
113
+ # Generate EARS requirements documents (v0.8.0)
113
114
  musubi-requirements init "User Authentication" # Initialize requirements doc
114
115
  musubi-requirements add # Add requirement interactively
115
116
  musubi-requirements list # List all requirements
116
117
  musubi-requirements validate # Validate EARS format
117
118
  musubi-requirements trace # Show traceability matrix
119
+
120
+ # Generate design documents (v0.8.2)
121
+ musubi-design init "User Authentication" # Initialize design document
122
+ musubi-design add-c4 context # Add C4 Context diagram
123
+ musubi-design add-c4 container --format plantuml # Add Container with PlantUML
124
+ musubi-design add-adr "Use JWT for tokens" # Add Architecture Decision
125
+ musubi-design validate # Validate design completeness
126
+ musubi-design trace # Show requirements traceability
118
127
  ```
119
128
 
120
129
  ### Project Types
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MUSUBI Design Document Generator CLI
5
+ *
6
+ * Generates technical design documents with C4 model and ADR
7
+ * Complies with Article V (Traceability) and steering context
8
+ *
9
+ * Usage:
10
+ * musubi-design init <feature> # Initialize design document
11
+ * musubi-design add-c4 <level> # Add C4 diagram (context|container|component|code)
12
+ * musubi-design add-adr <decision> # Add Architecture Decision Record
13
+ * musubi-design validate # Validate design completeness
14
+ * musubi-design trace # Show requirement traceability
15
+ */
16
+
17
+ const { Command } = require('commander');
18
+ const chalk = require('chalk');
19
+ const inquirer = require('inquirer');
20
+ const DesignGenerator = require('../src/generators/design');
21
+
22
+ const program = new Command();
23
+
24
+ program
25
+ .name('musubi-design')
26
+ .description('Design Document Generator - Create C4 models and ADRs')
27
+ .version('0.8.2');
28
+
29
+ // Initialize design document
30
+ program
31
+ .command('init <feature>')
32
+ .description('Initialize design document for a feature')
33
+ .option('-o, --output <path>', 'Output directory', 'docs/design')
34
+ .option('-a, --author <name>', 'Author name')
35
+ .option('--project <name>', 'Project name')
36
+ .option('-r, --requirements <path>', 'Requirements file path')
37
+ .action(async (feature, options) => {
38
+ try {
39
+ console.log(chalk.bold(`\n🏗️ Initializing design for: ${feature}\n`));
40
+
41
+ const generator = new DesignGenerator(process.cwd());
42
+ const result = await generator.init(feature, options);
43
+
44
+ console.log(chalk.green('✓ Design document created'));
45
+ console.log(chalk.dim(` ${result.path}`));
46
+ console.log();
47
+ console.log(chalk.bold('Next steps:'));
48
+ console.log(chalk.dim(` 1. Edit ${result.path}`));
49
+ console.log(chalk.dim(' 2. Add C4 diagrams: musubi-design add-c4 <level>'));
50
+ console.log(chalk.dim(' 3. Add ADRs: musubi-design add-adr <decision>'));
51
+ console.log(chalk.dim(' 4. Validate: musubi-design validate'));
52
+ console.log();
53
+
54
+ process.exit(0);
55
+ } catch (error) {
56
+ console.error(chalk.red('✗ Error:'), error.message);
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ // Add C4 diagram
62
+ program
63
+ .command('add-c4 <level>')
64
+ .description('Add C4 model diagram (context|container|component|code)')
65
+ .option('-f, --file <path>', 'Design file path')
66
+ .option('--format <type>', 'Diagram format (mermaid|plantuml)', 'mermaid')
67
+ .action(async (level, options) => {
68
+ try {
69
+ console.log(chalk.bold(`\n📊 Adding C4 ${level} diagram\n`));
70
+
71
+ const generator = new DesignGenerator(process.cwd());
72
+
73
+ // Find design file
74
+ let designFile = options.file;
75
+ if (!designFile) {
76
+ const files = await generator.findDesignFiles();
77
+ if (files.length === 0) {
78
+ console.error(chalk.red('✗ No design files found'));
79
+ console.log(chalk.dim(' Run: musubi-design init <feature>'));
80
+ process.exit(1);
81
+ }
82
+
83
+ if (files.length === 1) {
84
+ designFile = files[0];
85
+ } else {
86
+ const answer = await inquirer.prompt([{
87
+ type: 'list',
88
+ name: 'file',
89
+ message: 'Select design file:',
90
+ choices: files
91
+ }]);
92
+ designFile = answer.file;
93
+ }
94
+ }
95
+
96
+ // Interactive prompts for C4 diagram
97
+ const answers = await inquirer.prompt([
98
+ {
99
+ type: 'input',
100
+ name: 'title',
101
+ message: 'Diagram title:',
102
+ default: `${level.charAt(0).toUpperCase() + level.slice(1)} Diagram`,
103
+ validate: (input) => input.length > 0 || 'Title is required'
104
+ },
105
+ {
106
+ type: 'input',
107
+ name: 'description',
108
+ message: 'Diagram description:',
109
+ default: `Shows ${level}-level architecture`
110
+ }
111
+ ]);
112
+
113
+ const diagram = {
114
+ level,
115
+ title: answers.title,
116
+ description: answers.description,
117
+ format: options.format
118
+ };
119
+
120
+ const result = await generator.addC4Diagram(designFile, diagram);
121
+
122
+ console.log(chalk.green('\n✓ C4 diagram added:'));
123
+ console.log(chalk.dim(` ${result.level}: ${result.title}`));
124
+ console.log(chalk.bold('\n📝 Diagram Template:'));
125
+ console.log(chalk.cyan(result.template));
126
+ console.log();
127
+
128
+ process.exit(0);
129
+ } catch (error) {
130
+ console.error(chalk.red('✗ Error:'), error.message);
131
+ process.exit(1);
132
+ }
133
+ });
134
+
135
+ // Add Architecture Decision Record
136
+ program
137
+ .command('add-adr <decision>')
138
+ .description('Add Architecture Decision Record')
139
+ .option('-f, --file <path>', 'Design file path')
140
+ .option('--status <status>', 'ADR status (proposed|accepted|rejected|deprecated)', 'proposed')
141
+ .action(async (decision, options) => {
142
+ try {
143
+ console.log(chalk.bold(`\n📜 Adding ADR: ${decision}\n`));
144
+
145
+ const generator = new DesignGenerator(process.cwd());
146
+
147
+ // Find design file
148
+ let designFile = options.file;
149
+ if (!designFile) {
150
+ const files = await generator.findDesignFiles();
151
+ if (files.length === 0) {
152
+ console.error(chalk.red('✗ No design files found'));
153
+ console.log(chalk.dim(' Run: musubi-design init <feature>'));
154
+ process.exit(1);
155
+ }
156
+
157
+ if (files.length === 1) {
158
+ designFile = files[0];
159
+ } else {
160
+ const answer = await inquirer.prompt([{
161
+ type: 'list',
162
+ name: 'file',
163
+ message: 'Select design file:',
164
+ choices: files
165
+ }]);
166
+ designFile = answer.file;
167
+ }
168
+ }
169
+
170
+ // Interactive prompts for ADR
171
+ const answers = await inquirer.prompt([
172
+ {
173
+ type: 'input',
174
+ name: 'context',
175
+ message: 'What is the context/problem?',
176
+ validate: (input) => input.length > 0 || 'Context is required'
177
+ },
178
+ {
179
+ type: 'input',
180
+ name: 'decision',
181
+ message: 'What is the decision?',
182
+ validate: (input) => input.length > 0 || 'Decision is required'
183
+ },
184
+ {
185
+ type: 'input',
186
+ name: 'consequences',
187
+ message: 'What are the consequences?',
188
+ validate: (input) => input.length > 0 || 'Consequences are required'
189
+ },
190
+ {
191
+ type: 'input',
192
+ name: 'alternatives',
193
+ message: 'Alternatives considered (comma-separated):',
194
+ filter: (input) => input.split(',').map(s => s.trim()).filter(s => s.length > 0)
195
+ }
196
+ ]);
197
+
198
+ const adr = {
199
+ title: decision,
200
+ status: options.status,
201
+ context: answers.context,
202
+ decision: answers.decision,
203
+ consequences: answers.consequences,
204
+ alternatives: answers.alternatives
205
+ };
206
+
207
+ const result = await generator.addADR(designFile, adr);
208
+
209
+ console.log(chalk.green('\n✓ ADR added:'));
210
+ console.log(chalk.dim(` ADR-${result.number}: ${result.title}`));
211
+ console.log(chalk.dim(` Status: ${result.status}`));
212
+ console.log();
213
+
214
+ process.exit(0);
215
+ } catch (error) {
216
+ console.error(chalk.red('✗ Error:'), error.message);
217
+ process.exit(1);
218
+ }
219
+ });
220
+
221
+ // Validate design document
222
+ program
223
+ .command('validate')
224
+ .description('Validate design document completeness')
225
+ .option('-f, --file <path>', 'Specific design file')
226
+ .option('-v, --verbose', 'Show detailed validation results')
227
+ .action(async (options) => {
228
+ try {
229
+ console.log(chalk.bold('\n🔍 Validating Design Documents\n'));
230
+
231
+ const generator = new DesignGenerator(process.cwd());
232
+ const results = await generator.validate(options.file);
233
+
234
+ if (results.passed) {
235
+ console.log(chalk.green('✓ All designs valid\n'));
236
+ } else {
237
+ console.log(chalk.red('✗ Validation failed\n'));
238
+ }
239
+
240
+ console.log(chalk.bold('Summary:'));
241
+ console.log(chalk.dim(` Total: ${results.total}`));
242
+ console.log(chalk.green(` Valid: ${results.valid}`));
243
+ console.log(chalk.red(` Invalid: ${results.invalid}`));
244
+ console.log();
245
+
246
+ if (results.violations.length > 0) {
247
+ console.log(chalk.bold.red('Violations:'));
248
+ results.violations.forEach(v => {
249
+ console.log(chalk.red(` • ${v}`));
250
+ });
251
+ console.log();
252
+ }
253
+
254
+ if (options.verbose && results.details) {
255
+ console.log(chalk.bold('Details:'));
256
+ results.details.forEach(d => {
257
+ const icon = d.valid ? chalk.green('✓') : chalk.red('✗');
258
+ console.log(` ${icon} ${d.file}: ${d.message}`);
259
+ });
260
+ console.log();
261
+ }
262
+
263
+ process.exit(results.passed ? 0 : 1);
264
+ } catch (error) {
265
+ console.error(chalk.red('✗ Error:'), error.message);
266
+ process.exit(1);
267
+ }
268
+ });
269
+
270
+ // Show requirement traceability
271
+ program
272
+ .command('trace')
273
+ .description('Show requirement-to-design traceability')
274
+ .option('-f, --file <path>', 'Specific design file')
275
+ .option('--format <type>', 'Output format (table|json|markdown)', 'table')
276
+ .action(async (options) => {
277
+ try {
278
+ console.log(chalk.bold('\n🔗 Design Traceability Matrix\n'));
279
+
280
+ const generator = new DesignGenerator(process.cwd());
281
+ const matrix = await generator.generateTraceabilityMatrix(options.file);
282
+
283
+ if (options.format === 'json') {
284
+ console.log(JSON.stringify(matrix, null, 2));
285
+ } else {
286
+ console.log(chalk.bold('| Requirement | Design | Components | Status |'));
287
+ console.log('|-------------|--------|------------|--------|');
288
+
289
+ matrix.forEach(row => {
290
+ const design = row.design ? '✓' : '-';
291
+ const components = row.components || 0;
292
+ const status = row.traced ? chalk.green('Traced') : chalk.yellow('Missing');
293
+
294
+ console.log(`| ${row.requirement} | ${design} | ${components} | ${status} |`);
295
+ });
296
+ console.log();
297
+
298
+ const traced = matrix.filter(r => r.traced).length;
299
+ const total = matrix.length;
300
+ const percentage = total > 0 ? Math.round((traced / total) * 100) : 0;
301
+
302
+ console.log(chalk.bold('Coverage:'));
303
+ console.log(chalk.dim(` ${traced}/${total} requirements traced (${percentage}%)`));
304
+ console.log();
305
+ }
306
+
307
+ process.exit(0);
308
+ } catch (error) {
309
+ console.error(chalk.red('✗ Error:'), error.message);
310
+ process.exit(1);
311
+ }
312
+ });
313
+
314
+ // Parse arguments
315
+ program.parse(process.argv);
316
+
317
+ // Show help if no command provided
318
+ if (!process.argv.slice(2).length) {
319
+ program.outputHelp();
320
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musubi-sdd",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Ultimate Specification Driven Development Tool with 25 Agents for 7 AI Coding Platforms (Claude Code, GitHub Copilot, Cursor, Gemini CLI, Windsurf, Codex, Qwen Code)",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  "musubi-analyze": "bin/musubi-analyze.js",
13
13
  "musubi-share": "bin/musubi-share.js",
14
14
  "musubi-validate": "bin/musubi-validate.js",
15
- "musubi-requirements": "bin/musubi-requirements.js"
15
+ "musubi-requirements": "bin/musubi-requirements.js",
16
+ "musubi-design": "bin/musubi-design.js"
16
17
  },
17
18
  "scripts": {
18
19
  "test": "jest",
@@ -0,0 +1,662 @@
1
+ /**
2
+ * MUSUBI Design Document Generator
3
+ *
4
+ * Generates technical design documents with C4 model and ADR
5
+ * Complies with Article V (Traceability) and steering context
6
+ *
7
+ * C4 Model Levels:
8
+ * 1. Context: System in its environment
9
+ * 2. Container: High-level technology choices
10
+ * 3. Component: Components within containers
11
+ * 4. Code: Class/component implementation details
12
+ *
13
+ * ADR Format:
14
+ * - Title: Decision name
15
+ * - Status: proposed|accepted|rejected|deprecated
16
+ * - Context: Problem/situation
17
+ * - Decision: What we decided
18
+ * - Consequences: Positive and negative outcomes
19
+ * - Alternatives: Other options considered
20
+ */
21
+
22
+ const fs = require('fs-extra');
23
+ const path = require('path');
24
+ const { glob } = require('glob');
25
+
26
+ class DesignGenerator {
27
+ constructor(rootDir) {
28
+ this.rootDir = rootDir;
29
+ this.templatePath = path.join(__dirname, '../../src/templates/shared/documents/design.md');
30
+ }
31
+
32
+ /**
33
+ * Initialize design document for a feature
34
+ * @param {string} feature - Feature name
35
+ * @param {object} options - Options (output, author, project, requirements)
36
+ * @returns {Promise<object>} Result with path
37
+ */
38
+ async init(feature, options = {}) {
39
+ const outputDir = path.join(this.rootDir, options.output || 'docs/design');
40
+ await fs.ensureDir(outputDir);
41
+
42
+ const fileName = `${this.slugify(feature)}.md`;
43
+ const filePath = path.join(outputDir, fileName);
44
+
45
+ // Check if file exists
46
+ if (await fs.pathExists(filePath)) {
47
+ throw new Error(`Design file already exists: ${filePath}`);
48
+ }
49
+
50
+ // Load template
51
+ const template = await fs.readFile(this.templatePath, 'utf-8');
52
+
53
+ // Get project name from package.json
54
+ let projectName = options.project;
55
+ if (!projectName) {
56
+ try {
57
+ const pkg = await fs.readJSON(path.join(this.rootDir, 'package.json'));
58
+ projectName = pkg.name || 'Project';
59
+ } catch {
60
+ projectName = 'Project';
61
+ }
62
+ }
63
+
64
+ // Get author from git config or options
65
+ let author = options.author;
66
+ if (!author) {
67
+ try {
68
+ const { execSync } = require('child_process');
69
+ author = execSync('git config user.name', { encoding: 'utf-8' }).trim();
70
+ } catch {
71
+ author = 'Author';
72
+ }
73
+ }
74
+
75
+ // Replace template variables
76
+ const content = template
77
+ .replace(/\{\{FEATURE_NAME\}\}/g, feature)
78
+ .replace(/\{\{PROJECT_NAME\}\}/g, projectName)
79
+ .replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0])
80
+ .replace(/\{\{AUTHOR\}\}/g, author)
81
+ .replace(/\{\{SYSTEM\}\}/g, projectName);
82
+
83
+ await fs.writeFile(filePath, content, 'utf-8');
84
+
85
+ return { path: filePath };
86
+ }
87
+
88
+ /**
89
+ * Find all design files in the project
90
+ * @returns {Promise<string[]>} List of design file paths
91
+ */
92
+ async findDesignFiles() {
93
+ const patterns = [
94
+ 'docs/design/**/*.md',
95
+ 'docs/design/*.md',
96
+ 'design/**/*.md',
97
+ 'design/*.md'
98
+ ];
99
+
100
+ const files = [];
101
+ for (const pattern of patterns) {
102
+ const matches = await glob(pattern, { cwd: this.rootDir, absolute: true });
103
+ files.push(...matches);
104
+ }
105
+
106
+ return [...new Set(files)];
107
+ }
108
+
109
+ /**
110
+ * Add C4 diagram to design file
111
+ * @param {string} filePath - Design file path
112
+ * @param {object} diagram - Diagram data
113
+ * @returns {Promise<object>} Result with level and template
114
+ */
115
+ async addC4Diagram(filePath, diagram) {
116
+ const content = await fs.readFile(filePath, 'utf-8');
117
+
118
+ // Generate C4 diagram template
119
+ const template = this.generateC4Template(diagram);
120
+
121
+ // Find insertion point based on level
122
+ const insertionPoint = this.findC4InsertionPoint(content, diagram.level);
123
+
124
+ const newContent = content.slice(0, insertionPoint) + template + '\n' + content.slice(insertionPoint);
125
+
126
+ await fs.writeFile(filePath, newContent, 'utf-8');
127
+
128
+ return {
129
+ level: diagram.level,
130
+ title: diagram.title,
131
+ template
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Generate C4 diagram template
137
+ * @param {object} diagram - Diagram data
138
+ * @returns {string} Mermaid or PlantUML template
139
+ */
140
+ generateC4Template(diagram) {
141
+ const { level, title, description, format } = diagram;
142
+
143
+ if (format === 'plantuml') {
144
+ return this.generatePlantUMLTemplate(level, title, description);
145
+ }
146
+
147
+ // Default: Mermaid
148
+ return this.generateMermaidTemplate(level, title, description);
149
+ }
150
+
151
+ /**
152
+ * Generate Mermaid C4 template
153
+ * @param {string} level - C4 level
154
+ * @param {string} title - Diagram title
155
+ * @param {string} description - Diagram description
156
+ * @returns {string} Mermaid template
157
+ */
158
+ generateMermaidTemplate(level, title, description) {
159
+ const templates = {
160
+ context: `### C4 Context: ${title}
161
+
162
+ **${description}**
163
+
164
+ \`\`\`mermaid
165
+ C4Context
166
+ title ${title}
167
+
168
+ Person(user, "User", "End user of the system")
169
+ System(system, "System Name", "System description")
170
+ System_Ext(external, "External System", "External service")
171
+
172
+ Rel(user, system, "Uses", "HTTPS")
173
+ Rel(system, external, "Calls", "API")
174
+ \`\`\`
175
+
176
+ **Key Elements**:
177
+ - **Users**: [List users/personas]
178
+ - **System**: [System boundary]
179
+ - **External Systems**: [External dependencies]
180
+ `,
181
+ container: `### C4 Container: ${title}
182
+
183
+ **${description}**
184
+
185
+ \`\`\`mermaid
186
+ C4Container
187
+ title ${title}
188
+
189
+ Person(user, "User")
190
+
191
+ Container_Boundary(c1, "System Name") {
192
+ Container(web, "Web Application", "React", "User interface")
193
+ Container(api, "API", "Node.js", "Business logic")
194
+ ContainerDb(db, "Database", "PostgreSQL", "Data storage")
195
+ }
196
+
197
+ Rel(user, web, "Uses", "HTTPS")
198
+ Rel(web, api, "Calls", "REST/JSON")
199
+ Rel(api, db, "Reads/Writes", "SQL")
200
+ \`\`\`
201
+
202
+ **Containers**:
203
+ - **Frontend**: [Technology and purpose]
204
+ - **Backend**: [Technology and purpose]
205
+ - **Database**: [Technology and purpose]
206
+ `,
207
+ component: `### C4 Component: ${title}
208
+
209
+ **${description}**
210
+
211
+ \`\`\`mermaid
212
+ C4Component
213
+ title ${title}
214
+
215
+ Container_Boundary(api, "API Container") {
216
+ Component(controller, "Controller", "Express", "Handles requests")
217
+ Component(service, "Service", "TypeScript", "Business logic")
218
+ Component(repository, "Repository", "TypeORM", "Data access")
219
+ }
220
+
221
+ ContainerDb(db, "Database")
222
+
223
+ Rel(controller, service, "Uses")
224
+ Rel(service, repository, "Uses")
225
+ Rel(repository, db, "Queries")
226
+ \`\`\`
227
+
228
+ **Components**:
229
+ - **Controller**: [Responsibility]
230
+ - **Service**: [Responsibility]
231
+ - **Repository**: [Responsibility]
232
+ `,
233
+ code: `### C4 Code: ${title}
234
+
235
+ **${description}**
236
+
237
+ \`\`\`mermaid
238
+ classDiagram
239
+ class Controller {
240
+ +handleRequest()
241
+ +validateInput()
242
+ }
243
+
244
+ class Service {
245
+ +executeBusinessLogic()
246
+ +processData()
247
+ }
248
+
249
+ class Repository {
250
+ +findById()
251
+ +save()
252
+ +delete()
253
+ }
254
+
255
+ Controller --> Service
256
+ Service --> Repository
257
+ \`\`\`
258
+
259
+ **Classes**:
260
+ - **Controller**: [Purpose and key methods]
261
+ - **Service**: [Purpose and key methods]
262
+ - **Repository**: [Purpose and key methods]
263
+ `
264
+ };
265
+
266
+ return templates[level] || templates.context;
267
+ }
268
+
269
+ /**
270
+ * Generate PlantUML C4 template
271
+ * @param {string} level - C4 level
272
+ * @param {string} title - Diagram title
273
+ * @param {string} description - Diagram description
274
+ * @returns {string} PlantUML template
275
+ */
276
+ generatePlantUMLTemplate(level, title, description) {
277
+ const templates = {
278
+ context: `### C4 Context: ${title}
279
+
280
+ **${description}**
281
+
282
+ \`\`\`plantuml
283
+ @startuml
284
+ !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
285
+
286
+ LAYOUT_WITH_LEGEND()
287
+
288
+ title ${title}
289
+
290
+ Person(user, "User", "End user")
291
+ System(system, "System Name", "System description")
292
+ System_Ext(external, "External System", "External service")
293
+
294
+ Rel(user, system, "Uses", "HTTPS")
295
+ Rel(system, external, "Calls", "API")
296
+
297
+ @enduml
298
+ \`\`\`
299
+ `,
300
+ container: `### C4 Container: ${title}
301
+
302
+ **${description}**
303
+
304
+ \`\`\`plantuml
305
+ @startuml
306
+ !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
307
+
308
+ LAYOUT_WITH_LEGEND()
309
+
310
+ title ${title}
311
+
312
+ Person(user, "User")
313
+
314
+ System_Boundary(c1, "System Name") {
315
+ Container(web, "Web App", "React", "User interface")
316
+ Container(api, "API", "Node.js", "Business logic")
317
+ ContainerDb(db, "Database", "PostgreSQL", "Data storage")
318
+ }
319
+
320
+ Rel(user, web, "Uses", "HTTPS")
321
+ Rel(web, api, "Calls", "REST/JSON")
322
+ Rel(api, db, "Reads/Writes", "SQL")
323
+
324
+ @enduml
325
+ \`\`\`
326
+ `,
327
+ component: `### C4 Component: ${title}
328
+
329
+ **${description}**
330
+
331
+ \`\`\`plantuml
332
+ @startuml
333
+ !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
334
+
335
+ LAYOUT_WITH_LEGEND()
336
+
337
+ title ${title}
338
+
339
+ Container_Boundary(api, "API Container") {
340
+ Component(controller, "Controller", "Express", "Handles requests")
341
+ Component(service, "Service", "TypeScript", "Business logic")
342
+ Component(repository, "Repository", "TypeORM", "Data access")
343
+ }
344
+
345
+ ContainerDb(db, "Database")
346
+
347
+ Rel(controller, service, "Uses")
348
+ Rel(service, repository, "Uses")
349
+ Rel(repository, db, "Queries")
350
+
351
+ @enduml
352
+ \`\`\`
353
+ `,
354
+ code: `### C4 Code: ${title}
355
+
356
+ **${description}**
357
+
358
+ \`\`\`plantuml
359
+ @startuml
360
+ title ${title}
361
+
362
+ class Controller {
363
+ +handleRequest()
364
+ +validateInput()
365
+ }
366
+
367
+ class Service {
368
+ +executeBusinessLogic()
369
+ +processData()
370
+ }
371
+
372
+ class Repository {
373
+ +findById()
374
+ +save()
375
+ +delete()
376
+ }
377
+
378
+ Controller --> Service
379
+ Service --> Repository
380
+
381
+ @enduml
382
+ \`\`\`
383
+ `
384
+ };
385
+
386
+ return templates[level] || templates.context;
387
+ }
388
+
389
+ /**
390
+ * Get C4 section name
391
+ * @param {string} level - C4 level
392
+ * @returns {string} Section name
393
+ */
394
+ getC4SectionName(level) {
395
+ const names = {
396
+ context: 'C4 Model: Context Diagram',
397
+ container: 'C4 Model: Container Diagram',
398
+ component: 'C4 Model: Component Diagram',
399
+ code: 'C4 Model: Code Diagram'
400
+ };
401
+ return names[level] || 'Architecture Design';
402
+ }
403
+
404
+ /**
405
+ * Find C4 insertion point in document
406
+ * @param {string} content - Document content
407
+ * @param {string} _level - C4 level
408
+ * @returns {number} Insertion index
409
+ */
410
+ findC4InsertionPoint(content, _level) {
411
+ // Find Architecture Design section
412
+ const archMatch = content.match(/## Architecture Design/);
413
+ if (!archMatch) {
414
+ throw new Error('Architecture Design section not found');
415
+ }
416
+
417
+ // Find next section after Architecture Design
418
+ const afterArch = content.slice(archMatch.index + archMatch[0].length);
419
+ const nextSectionMatch = afterArch.match(/\n## [^#]/);
420
+
421
+ if (nextSectionMatch) {
422
+ return archMatch.index + archMatch[0].length + nextSectionMatch.index;
423
+ }
424
+
425
+ // If no next section, append at end
426
+ return content.length;
427
+ }
428
+
429
+ /**
430
+ * Add Architecture Decision Record
431
+ * @param {string} filePath - Design file path
432
+ * @param {object} adr - ADR data
433
+ * @returns {Promise<object>} Result with ADR number
434
+ */
435
+ async addADR(filePath, adr) {
436
+ const content = await fs.readFile(filePath, 'utf-8');
437
+
438
+ // Generate ADR number
439
+ const number = this.generateADRNumber(content);
440
+
441
+ // Format ADR section
442
+ const section = this.formatADRSection(number, adr);
443
+
444
+ // Find insertion point (Architecture Decisions section)
445
+ const insertionPoint = this.findADRInsertionPoint(content);
446
+ const newContent = content.slice(0, insertionPoint) + section + '\n' + content.slice(insertionPoint);
447
+
448
+ await fs.writeFile(filePath, newContent, 'utf-8');
449
+
450
+ return {
451
+ number,
452
+ title: adr.title,
453
+ status: adr.status
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Generate ADR number
459
+ * @param {string} content - Document content
460
+ * @returns {string} ADR number (ADR-001, ADR-002, etc.)
461
+ */
462
+ generateADRNumber(content) {
463
+ const regex = /### ADR-(\d+):/g;
464
+
465
+ let maxNum = 0;
466
+ let match;
467
+ while ((match = regex.exec(content)) !== null) {
468
+ const num = parseInt(match[1], 10);
469
+ if (num > maxNum) maxNum = num;
470
+ }
471
+
472
+ const nextNum = (maxNum + 1).toString().padStart(3, '0');
473
+ return `ADR-${nextNum}`;
474
+ }
475
+
476
+ /**
477
+ * Format ADR section
478
+ * @param {string} number - ADR number
479
+ * @param {object} adr - ADR data
480
+ * @returns {string} Formatted ADR section
481
+ */
482
+ formatADRSection(number, adr) {
483
+ const date = new Date().toISOString().split('T')[0];
484
+
485
+ let section = `### ${number}: ${adr.title}\n\n`;
486
+ section += `**Status**: ${adr.status}\n`;
487
+ section += `**Date**: ${date}\n\n`;
488
+ section += `**Context**:\n\n${adr.context}\n\n`;
489
+ section += `**Decision**:\n\n${adr.decision}\n\n`;
490
+ section += `**Consequences**:\n\n${adr.consequences}\n\n`;
491
+
492
+ if (adr.alternatives && adr.alternatives.length > 0) {
493
+ section += '**Alternatives Considered**:\n\n';
494
+ adr.alternatives.forEach(alt => {
495
+ section += `- ${alt}\n`;
496
+ });
497
+ section += '\n';
498
+ }
499
+
500
+ return section;
501
+ }
502
+
503
+ /**
504
+ * Find ADR insertion point in document
505
+ * @param {string} content - Document content
506
+ * @returns {number} Insertion index
507
+ */
508
+ findADRInsertionPoint(content) {
509
+ // Find Architecture Decisions section
510
+ const adrMatch = content.match(/## Architecture Decisions/);
511
+ if (!adrMatch) {
512
+ // If section doesn't exist, add it before next major section
513
+ const nextMatch = content.match(/\n## [^#]/);
514
+ if (nextMatch) {
515
+ return nextMatch.index;
516
+ }
517
+ return content.length;
518
+ }
519
+
520
+ // Find next section after Architecture Decisions
521
+ const afterADR = content.slice(adrMatch.index + adrMatch[0].length);
522
+ const nextSectionMatch = afterADR.match(/\n## [^#]/);
523
+
524
+ if (nextSectionMatch) {
525
+ return adrMatch.index + adrMatch[0].length + nextSectionMatch.index;
526
+ }
527
+
528
+ return content.length;
529
+ }
530
+
531
+ /**
532
+ * Validate design documents
533
+ * @param {string} [filePath] - Specific file path
534
+ * @returns {Promise<object>} Validation results
535
+ */
536
+ async validate(filePath = null) {
537
+ const files = filePath ? [filePath] : await this.findDesignFiles();
538
+ const violations = [];
539
+ const details = [];
540
+
541
+ for (const file of files) {
542
+ const content = await fs.readFile(file, 'utf-8');
543
+ const errors = this.validateDesignDocument(content);
544
+
545
+ if (errors.length > 0) {
546
+ violations.push(`${path.basename(file)}: ${errors.join(', ')}`);
547
+ details.push({
548
+ file: path.basename(file),
549
+ valid: false,
550
+ message: errors.join(', ')
551
+ });
552
+ } else {
553
+ details.push({
554
+ file: path.basename(file),
555
+ valid: true,
556
+ message: 'Design document complete'
557
+ });
558
+ }
559
+ }
560
+
561
+ return {
562
+ passed: violations.length === 0,
563
+ total: files.length,
564
+ valid: files.length - violations.length,
565
+ invalid: violations.length,
566
+ violations,
567
+ details
568
+ };
569
+ }
570
+
571
+ /**
572
+ * Validate single design document
573
+ * @param {string} content - Document content
574
+ * @returns {string[]} List of errors
575
+ */
576
+ validateDesignDocument(content) {
577
+ const errors = [];
578
+
579
+ // Check for required sections
580
+ if (!content.includes('## Architecture Design')) {
581
+ errors.push('Missing Architecture Design section');
582
+ }
583
+
584
+ if (!content.includes('## Steering Context')) {
585
+ errors.push('Missing Steering Context section');
586
+ }
587
+
588
+ // Check for C4 diagrams (at least one level)
589
+ const hasC4 = content.includes('C4Context') ||
590
+ content.includes('C4Container') ||
591
+ content.includes('C4Component') ||
592
+ content.includes('C4 Model');
593
+
594
+ if (!hasC4) {
595
+ errors.push('Missing C4 model diagrams');
596
+ }
597
+
598
+ return errors;
599
+ }
600
+
601
+ /**
602
+ * Generate traceability matrix
603
+ * @param {string} [_filePath] - Specific file path
604
+ * @returns {Promise<object[]>} Traceability matrix
605
+ */
606
+ async generateTraceabilityMatrix(_filePath = null) {
607
+ const matrix = [];
608
+
609
+ // Find requirement files
610
+ const reqFiles = await glob('docs/requirements/**/*.md', {
611
+ cwd: this.rootDir,
612
+ absolute: true
613
+ });
614
+
615
+ for (const reqFile of reqFiles) {
616
+ const content = await fs.readFile(reqFile, 'utf-8');
617
+ const requirements = this.parseRequirements(content);
618
+
619
+ for (const req of requirements) {
620
+ matrix.push({
621
+ requirement: req.id,
622
+ design: false,
623
+ components: 0,
624
+ traced: false
625
+ });
626
+ }
627
+ }
628
+
629
+ return matrix;
630
+ }
631
+
632
+ /**
633
+ * Parse requirements from content
634
+ * @param {string} content - File content
635
+ * @returns {object[]} List of requirements
636
+ */
637
+ parseRequirements(content) {
638
+ const requirements = [];
639
+ const reqRegex = /### (REQ-[A-Z]+-\d+):/g;
640
+
641
+ let match;
642
+ while ((match = reqRegex.exec(content)) !== null) {
643
+ requirements.push({ id: match[1] });
644
+ }
645
+
646
+ return requirements;
647
+ }
648
+
649
+ /**
650
+ * Convert string to slug
651
+ * @param {string} str - Input string
652
+ * @returns {string} Slug
653
+ */
654
+ slugify(str) {
655
+ return str
656
+ .toLowerCase()
657
+ .replace(/[^a-z0-9]+/g, '-')
658
+ .replace(/^-|-$/g, '');
659
+ }
660
+ }
661
+
662
+ module.exports = DesignGenerator;