musubi-sdd 0.6.1 → 0.8.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.ja.md +40 -1
- package/README.md +17 -3
- package/bin/musubi-requirements.js +330 -0
- package/bin/musubi-validate.js +271 -0
- package/package.json +4 -2
- package/src/generators/requirements.js +425 -0
- package/src/validators/constitution.js +535 -0
package/README.ja.md
CHANGED
|
@@ -13,9 +13,14 @@ MUSUBIは、6つの主要フレームワークのベスト機能を統合した
|
|
|
13
13
|
- GitHub Copilot & Cursor: AGENTS.md(公式サポート)
|
|
14
14
|
- その他4エージェント: AGENTS.md(互換形式)
|
|
15
15
|
- 📋 **憲法ガバナンス** - 9つの不変条項 + フェーズ-1ゲートによる品質保証
|
|
16
|
-
- 📝 **EARS
|
|
16
|
+
- 📝 **EARS要件ジェネレーター** - 5つのEARSパターンで明確な要件を作成(v0.8.0)
|
|
17
17
|
- 🔄 **差分仕様** - ブラウンフィールドおよびグリーンフィールドプロジェクト対応
|
|
18
18
|
- 🧭 **自動更新プロジェクトメモリ** - ステアリングシステムがアーキテクチャ、技術スタック、製品コンテキストを維持
|
|
19
|
+
- 🚀 **自動オンボーディング** - `musubi-onboard` が既存プロジェクトを分析し、ステアリングドキュメントを生成(2-5分)
|
|
20
|
+
- 🔄 **自動同期** - `musubi-sync` がコードベースの変更を検出し、ステアリングドキュメントを最新に保つ
|
|
21
|
+
- 🔍 **インテリジェントコード分析** - `musubi-analyze` が品質メトリクス、複雑度分析、技術的負債検出を提供
|
|
22
|
+
- 🤝 **チーム連携** - `musubi-share` がメモリ共有、インポート/エクスポート、マルチプラットフォーム同期を実現(v0.6.0)
|
|
23
|
+
- ✅ **憲法バリデーション** - `musubi-validate` が9つの不変ガバナンス条項とフェーズ-1ゲートを強制(v0.7.0)
|
|
19
24
|
- ✅ **完全なトレーサビリティ** - 要件 → 設計 → コード → テストのマッピング
|
|
20
25
|
- 🌐 **バイリンガルドキュメント** - すべてのエージェント生成ドキュメントは英語と日本語の両方で作成
|
|
21
26
|
|
|
@@ -72,6 +77,40 @@ npx musubi-sdd init --windsurf
|
|
|
72
77
|
# またはグローバルインストール
|
|
73
78
|
npm install -g musubi-sdd
|
|
74
79
|
musubi init --claude # または --copilot、--cursorなど
|
|
80
|
+
|
|
81
|
+
# 既存プロジェクトのオンボーディング(自動分析)
|
|
82
|
+
musubi-onboard
|
|
83
|
+
|
|
84
|
+
# コードベースとステアリングドキュメントの同期
|
|
85
|
+
musubi-sync
|
|
86
|
+
musubi-sync --dry-run # 変更のプレビュー
|
|
87
|
+
musubi-sync --auto-approve # 自動適用(CI/CD)
|
|
88
|
+
|
|
89
|
+
# コード品質分析(v0.5.0)
|
|
90
|
+
musubi-analyze # 完全分析
|
|
91
|
+
musubi-analyze --type=quality # 品質メトリクスのみ
|
|
92
|
+
musubi-analyze --type=dependencies # 依存関係のみ
|
|
93
|
+
musubi-analyze --type=security # セキュリティ監査
|
|
94
|
+
musubi-analyze --output=report.md # レポート保存
|
|
95
|
+
|
|
96
|
+
# チームとプロジェクトメモリを共有(v0.6.0)
|
|
97
|
+
musubi-share export # メモリをJSONにエクスポート
|
|
98
|
+
musubi-share import memories.json # チームメイトからインポート
|
|
99
|
+
musubi-share sync --platform=copilot # 特定プラットフォームに同期
|
|
100
|
+
|
|
101
|
+
# 憲法準拠の検証(v0.7.0)
|
|
102
|
+
musubi-validate constitution # 全9条項を検証
|
|
103
|
+
musubi-validate article 3 # テストファースト原則を検証
|
|
104
|
+
musubi-validate gates # フェーズ-1ゲートを検証
|
|
105
|
+
musubi-validate complexity # 複雑度制限をチェック
|
|
106
|
+
musubi-validate all -v # 詳細付き完全検証
|
|
107
|
+
|
|
108
|
+
# EARS要件の作成(v0.8.0)
|
|
109
|
+
musubi-requirements init "ユーザー認証" # 要件ドキュメント初期化
|
|
110
|
+
musubi-requirements add # インタラクティブに要件追加
|
|
111
|
+
musubi-requirements list # 全要件リスト表示
|
|
112
|
+
musubi-requirements validate # EARS形式検証
|
|
113
|
+
musubi-requirements trace # トレーサビリティマトリクス表示
|
|
75
114
|
```
|
|
76
115
|
|
|
77
116
|
### プロジェクトタイプ
|
package/README.md
CHANGED
|
@@ -17,13 +17,14 @@ MUSUBI is a comprehensive SDD (Specification Driven Development) framework that
|
|
|
17
17
|
- GitHub Copilot & Cursor: AGENTS.md (official support)
|
|
18
18
|
- Other 4 agents: AGENTS.md (compatible format)
|
|
19
19
|
- 📋 **Constitutional Governance** - 9 immutable articles + Phase -1 Gates for quality enforcement
|
|
20
|
-
- 📝 **EARS Requirements
|
|
20
|
+
- 📝 **EARS Requirements Generator** - Create unambiguous requirements with 5 EARS patterns (v0.8.0)
|
|
21
21
|
- 🔄 **Delta Specifications** - Brownfield and greenfield project support
|
|
22
22
|
- 🧭 **Auto-Updating Project Memory** - Steering system maintains architecture, tech stack, and product context
|
|
23
23
|
- 🚀 **Automatic Onboarding** - `musubi-onboard` analyzes existing projects and generates steering docs (2-5 minutes)
|
|
24
24
|
- 🔄 **Auto-Sync** - `musubi-sync` detects codebase changes and keeps steering docs current
|
|
25
25
|
- 🔍 **Intelligent Code Analysis** - `musubi-analyze` provides quality metrics, complexity analysis, and technical debt detection
|
|
26
26
|
- 🤝 **Team Collaboration** - `musubi-share` enables memory sharing, import/export, and multi-platform sync (v0.6.0)
|
|
27
|
+
- ✅ **Constitutional Validation** - `musubi-validate` enforces 9 immutable governance articles with Phase -1 Gates (v0.7.0)
|
|
27
28
|
- ✅ **Complete Traceability** - Requirements → Design → Code → Tests mapping
|
|
28
29
|
- 🌐 **Bilingual Documentation** - All agent-generated documents created in both English and Japanese
|
|
29
30
|
|
|
@@ -99,8 +100,21 @@ musubi-analyze --output=report.md # Save report
|
|
|
99
100
|
# Share project memories with team (v0.6.0)
|
|
100
101
|
musubi-share export # Export memories to JSON
|
|
101
102
|
musubi-share import memories.json # Import from teammate
|
|
102
|
-
musubi-share sync
|
|
103
|
-
|
|
103
|
+
musubi-share sync --platform=copilot # Sync to specific platform
|
|
104
|
+
|
|
105
|
+
# Validate constitutional compliance (v0.7.0)
|
|
106
|
+
musubi-validate constitution # Validate all 9 articles
|
|
107
|
+
musubi-validate article 3 # Validate Test-First Imperative
|
|
108
|
+
musubi-validate gates # Validate Phase -1 Gates
|
|
109
|
+
musubi-validate complexity # Check complexity limits
|
|
110
|
+
musubi-validate all -v # Full validation with details
|
|
111
|
+
|
|
112
|
+
# Create EARS requirements (v0.8.0)
|
|
113
|
+
musubi-requirements init "User Authentication" # Initialize requirements doc
|
|
114
|
+
musubi-requirements add # Add requirement interactively
|
|
115
|
+
musubi-requirements list # List all requirements
|
|
116
|
+
musubi-requirements validate # Validate EARS format
|
|
117
|
+
musubi-requirements trace # Show traceability matrix
|
|
104
118
|
```
|
|
105
119
|
|
|
106
120
|
### Project Types
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MUSUBI Requirements Generator CLI
|
|
5
|
+
*
|
|
6
|
+
* Generates EARS (Easy Approach to Requirements Syntax) formatted requirements
|
|
7
|
+
* Supports 5 EARS patterns for unambiguous specification
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* musubi-requirements init <feature> # Initialize requirements document
|
|
11
|
+
* musubi-requirements add <pattern> <title> # Add requirement with EARS pattern
|
|
12
|
+
* musubi-requirements list # List all requirements
|
|
13
|
+
* musubi-requirements validate # Validate EARS format compliance
|
|
14
|
+
* musubi-requirements trace # Show traceability matrix
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { Command } = require('commander');
|
|
18
|
+
const chalk = require('chalk');
|
|
19
|
+
const inquirer = require('inquirer');
|
|
20
|
+
const RequirementsGenerator = require('../src/generators/requirements');
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.name('musubi-requirements')
|
|
26
|
+
.description('EARS Requirements Generator - Create unambiguous specifications')
|
|
27
|
+
.version('0.8.0');
|
|
28
|
+
|
|
29
|
+
// Initialize requirements document
|
|
30
|
+
program
|
|
31
|
+
.command('init <feature>')
|
|
32
|
+
.description('Initialize requirements document for a feature')
|
|
33
|
+
.option('-o, --output <path>', 'Output directory', 'docs/requirements')
|
|
34
|
+
.option('-a, --author <name>', 'Author name')
|
|
35
|
+
.option('--project <name>', 'Project name')
|
|
36
|
+
.action(async (feature, options) => {
|
|
37
|
+
try {
|
|
38
|
+
console.log(chalk.bold(`\n📋 Initializing requirements for: ${feature}\n`));
|
|
39
|
+
|
|
40
|
+
const generator = new RequirementsGenerator(process.cwd());
|
|
41
|
+
const result = await generator.init(feature, options);
|
|
42
|
+
|
|
43
|
+
console.log(chalk.green('✓ Requirements document created'));
|
|
44
|
+
console.log(chalk.dim(` ${result.path}`));
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.bold('Next steps:'));
|
|
47
|
+
console.log(chalk.dim(` 1. Edit ${result.path}`));
|
|
48
|
+
console.log(chalk.dim(' 2. Add requirements: musubi-requirements add <pattern> <title>'));
|
|
49
|
+
console.log(chalk.dim(' 3. Validate: musubi-requirements validate'));
|
|
50
|
+
console.log();
|
|
51
|
+
|
|
52
|
+
process.exit(0);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Add requirement with EARS pattern
|
|
60
|
+
program
|
|
61
|
+
.command('add')
|
|
62
|
+
.description('Add requirement with EARS pattern (interactive)')
|
|
63
|
+
.option('-f, --file <path>', 'Requirements file path')
|
|
64
|
+
.option('-p, --pattern <type>', 'EARS pattern (ubiquitous|event|state|unwanted|optional)')
|
|
65
|
+
.option('-t, --title <text>', 'Requirement title')
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
try {
|
|
68
|
+
console.log(chalk.bold('\n📝 Add EARS Requirement\n'));
|
|
69
|
+
|
|
70
|
+
const generator = new RequirementsGenerator(process.cwd());
|
|
71
|
+
|
|
72
|
+
// Find requirements file
|
|
73
|
+
let reqFile = options.file;
|
|
74
|
+
if (!reqFile) {
|
|
75
|
+
const files = await generator.findRequirementsFiles();
|
|
76
|
+
if (files.length === 0) {
|
|
77
|
+
console.error(chalk.red('✗ No requirements files found'));
|
|
78
|
+
console.log(chalk.dim(' Run: musubi-requirements init <feature>'));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (files.length === 1) {
|
|
83
|
+
reqFile = files[0];
|
|
84
|
+
} else {
|
|
85
|
+
const answer = await inquirer.prompt([{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'file',
|
|
88
|
+
message: 'Select requirements file:',
|
|
89
|
+
choices: files
|
|
90
|
+
}]);
|
|
91
|
+
reqFile = answer.file;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Interactive prompts if not provided
|
|
96
|
+
let pattern = options.pattern;
|
|
97
|
+
let title = options.title;
|
|
98
|
+
|
|
99
|
+
if (!pattern || !title) {
|
|
100
|
+
const answers = await inquirer.prompt([
|
|
101
|
+
{
|
|
102
|
+
type: 'list',
|
|
103
|
+
name: 'pattern',
|
|
104
|
+
message: 'Select EARS pattern:',
|
|
105
|
+
choices: [
|
|
106
|
+
{ name: 'Ubiquitous - The [system] SHALL [requirement]', value: 'ubiquitous' },
|
|
107
|
+
{ name: 'Event-Driven - WHEN [event], THEN [system] SHALL [response]', value: 'event' },
|
|
108
|
+
{ name: 'State-Driven - WHILE [state], [system] SHALL [response]', value: 'state' },
|
|
109
|
+
{ name: 'Unwanted Behavior - IF [error], THEN [system] SHALL [response]', value: 'unwanted' },
|
|
110
|
+
{ name: 'Optional Feature - WHERE [feature], [system] SHALL [response]', value: 'optional' }
|
|
111
|
+
],
|
|
112
|
+
when: () => !pattern
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'input',
|
|
116
|
+
name: 'title',
|
|
117
|
+
message: 'Requirement title:',
|
|
118
|
+
when: () => !title,
|
|
119
|
+
validate: (input) => input.length > 0 || 'Title is required'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'input',
|
|
123
|
+
name: 'system',
|
|
124
|
+
message: 'System/component name:',
|
|
125
|
+
default: 'system'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'input',
|
|
129
|
+
name: 'statement',
|
|
130
|
+
message: (answers) => {
|
|
131
|
+
const prompts = {
|
|
132
|
+
ubiquitous: 'What SHALL the system do?',
|
|
133
|
+
event: 'What event triggers this? (WHEN...)',
|
|
134
|
+
state: 'What state/condition? (WHILE...)',
|
|
135
|
+
unwanted: 'What error/unwanted condition? (IF...)',
|
|
136
|
+
optional: 'What optional feature? (WHERE...)'
|
|
137
|
+
};
|
|
138
|
+
return prompts[answers.pattern || pattern];
|
|
139
|
+
},
|
|
140
|
+
validate: (input) => input.length > 0 || 'Statement is required'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'input',
|
|
144
|
+
name: 'response',
|
|
145
|
+
message: 'What SHALL the system do? (response)',
|
|
146
|
+
validate: (input) => input.length > 0 || 'Response is required'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'input',
|
|
150
|
+
name: 'criteria',
|
|
151
|
+
message: 'Acceptance criteria (comma-separated):',
|
|
152
|
+
filter: (input) => input.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
153
|
+
}
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
pattern = pattern || answers.pattern;
|
|
157
|
+
title = title || answers.title;
|
|
158
|
+
|
|
159
|
+
const requirement = {
|
|
160
|
+
pattern,
|
|
161
|
+
title,
|
|
162
|
+
system: answers.system,
|
|
163
|
+
statement: answers.statement,
|
|
164
|
+
response: answers.response,
|
|
165
|
+
criteria: answers.criteria
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = await generator.addRequirement(reqFile, requirement);
|
|
169
|
+
|
|
170
|
+
console.log(chalk.green('\n✓ Requirement added:'));
|
|
171
|
+
console.log(chalk.dim(` ${result.id}: ${title}`));
|
|
172
|
+
console.log(chalk.bold('\n📄 EARS Statement:'));
|
|
173
|
+
console.log(chalk.cyan(result.statement));
|
|
174
|
+
console.log();
|
|
175
|
+
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// List all requirements
|
|
185
|
+
program
|
|
186
|
+
.command('list')
|
|
187
|
+
.description('List all requirements in the project')
|
|
188
|
+
.option('-f, --file <path>', 'Specific requirements file')
|
|
189
|
+
.option('--format <type>', 'Output format (table|json|markdown)', 'table')
|
|
190
|
+
.action(async (options) => {
|
|
191
|
+
try {
|
|
192
|
+
const generator = new RequirementsGenerator(process.cwd());
|
|
193
|
+
const requirements = await generator.listRequirements(options.file);
|
|
194
|
+
|
|
195
|
+
if (requirements.length === 0) {
|
|
196
|
+
console.log(chalk.yellow('No requirements found'));
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(chalk.bold(`\n📋 Requirements (${requirements.length} total)\n`));
|
|
201
|
+
|
|
202
|
+
if (options.format === 'json') {
|
|
203
|
+
console.log(JSON.stringify(requirements, null, 2));
|
|
204
|
+
} else if (options.format === 'markdown') {
|
|
205
|
+
requirements.forEach(req => {
|
|
206
|
+
console.log(`## ${req.id}: ${req.title}`);
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(`**Pattern**: ${req.pattern}`);
|
|
209
|
+
console.log();
|
|
210
|
+
console.log(req.statement);
|
|
211
|
+
console.log();
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
// Table format
|
|
215
|
+
requirements.forEach(req => {
|
|
216
|
+
console.log(chalk.bold(`${req.id}: ${req.title}`));
|
|
217
|
+
console.log(chalk.dim(` Pattern: ${req.pattern}`));
|
|
218
|
+
console.log(chalk.cyan(` ${req.statement.substring(0, 100)}...`));
|
|
219
|
+
console.log();
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
process.exit(0);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Validate EARS format compliance
|
|
231
|
+
program
|
|
232
|
+
.command('validate')
|
|
233
|
+
.description('Validate requirements against EARS format')
|
|
234
|
+
.option('-f, --file <path>', 'Specific requirements file')
|
|
235
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
236
|
+
.action(async (options) => {
|
|
237
|
+
try {
|
|
238
|
+
console.log(chalk.bold('\n🔍 Validating EARS Requirements\n'));
|
|
239
|
+
|
|
240
|
+
const generator = new RequirementsGenerator(process.cwd());
|
|
241
|
+
const results = await generator.validate(options.file);
|
|
242
|
+
|
|
243
|
+
if (results.passed) {
|
|
244
|
+
console.log(chalk.green('✓ All requirements valid\n'));
|
|
245
|
+
} else {
|
|
246
|
+
console.log(chalk.red('✗ Validation failed\n'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(chalk.bold('Summary:'));
|
|
250
|
+
console.log(chalk.dim(` Total: ${results.total}`));
|
|
251
|
+
console.log(chalk.green(` Valid: ${results.valid}`));
|
|
252
|
+
console.log(chalk.red(` Invalid: ${results.invalid}`));
|
|
253
|
+
console.log();
|
|
254
|
+
|
|
255
|
+
if (results.violations.length > 0) {
|
|
256
|
+
console.log(chalk.bold.red('Violations:'));
|
|
257
|
+
results.violations.forEach(v => {
|
|
258
|
+
console.log(chalk.red(` • ${v}`));
|
|
259
|
+
});
|
|
260
|
+
console.log();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options.verbose && results.details) {
|
|
264
|
+
console.log(chalk.bold('Details:'));
|
|
265
|
+
results.details.forEach(d => {
|
|
266
|
+
const icon = d.valid ? chalk.green('✓') : chalk.red('✗');
|
|
267
|
+
console.log(` ${icon} ${d.id}: ${d.message}`);
|
|
268
|
+
});
|
|
269
|
+
console.log();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
process.exit(results.passed ? 0 : 1);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Show traceability matrix
|
|
280
|
+
program
|
|
281
|
+
.command('trace')
|
|
282
|
+
.description('Show requirements traceability matrix')
|
|
283
|
+
.option('-f, --file <path>', 'Specific requirements file')
|
|
284
|
+
.option('--format <type>', 'Output format (table|json|markdown)', 'table')
|
|
285
|
+
.action(async (options) => {
|
|
286
|
+
try {
|
|
287
|
+
console.log(chalk.bold('\n📊 Requirements Traceability Matrix\n'));
|
|
288
|
+
|
|
289
|
+
const generator = new RequirementsGenerator(process.cwd());
|
|
290
|
+
const matrix = await generator.generateTraceabilityMatrix(options.file);
|
|
291
|
+
|
|
292
|
+
if (options.format === 'json') {
|
|
293
|
+
console.log(JSON.stringify(matrix, null, 2));
|
|
294
|
+
} else {
|
|
295
|
+
console.log(chalk.bold('| Requirement | Design | Code | Tests | Status |'));
|
|
296
|
+
console.log('|-------------|--------|------|-------|--------|');
|
|
297
|
+
|
|
298
|
+
matrix.forEach(row => {
|
|
299
|
+
const design = row.design ? '✓' : '-';
|
|
300
|
+
const code = row.code ? '✓' : '-';
|
|
301
|
+
const tests = row.tests ? '✓' : '-';
|
|
302
|
+
const status = row.complete ? chalk.green('Complete') : chalk.yellow('Incomplete');
|
|
303
|
+
|
|
304
|
+
console.log(`| ${row.id} | ${design} | ${code} | ${tests} | ${status} |`);
|
|
305
|
+
});
|
|
306
|
+
console.log();
|
|
307
|
+
|
|
308
|
+
const complete = matrix.filter(r => r.complete).length;
|
|
309
|
+
const total = matrix.length;
|
|
310
|
+
const percentage = total > 0 ? Math.round((complete / total) * 100) : 0;
|
|
311
|
+
|
|
312
|
+
console.log(chalk.bold('Coverage:'));
|
|
313
|
+
console.log(chalk.dim(` ${complete}/${total} requirements traced (${percentage}%)`));
|
|
314
|
+
console.log();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
process.exit(0);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Parse arguments
|
|
325
|
+
program.parse(process.argv);
|
|
326
|
+
|
|
327
|
+
// Show help if no command provided
|
|
328
|
+
if (!process.argv.slice(2).length) {
|
|
329
|
+
program.outputHelp();
|
|
330
|
+
}
|