musubi-sdd 0.8.4 → 0.8.6
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 +19 -1
- package/README.md +19 -1
- package/bin/musubi-change.js +250 -0
- package/bin/musubi-design.js +0 -0
- package/bin/musubi-tasks.js +0 -0
- package/bin/musubi-trace.js +367 -0
- package/package.json +4 -2
- package/src/analyzers/traceability.js +524 -0
- package/src/managers/change.js +476 -0
package/README.ja.md
CHANGED
|
@@ -15,7 +15,7 @@ MUSUBIは、6つの主要フレームワークのベスト機能を統合した
|
|
|
15
15
|
- 📋 **憲法ガバナンス** - 9つの不変条項 + フェーズ-1ゲートによる品質保証
|
|
16
16
|
- 📝 **EARS要件ジェネレーター** - 5つのEARSパターンで明確な要件を作成(v0.8.0)
|
|
17
17
|
- 🏗️ **設計ドキュメントジェネレーター** - トレーサビリティ付きC4モデルとADRを作成(v0.8.2)
|
|
18
|
-
- 🔄
|
|
18
|
+
- 🔄 **変更管理システム** - ブラウンフィールドプロジェクト向け差分仕様(v0.8.6)
|
|
19
19
|
- 🧭 **自動更新プロジェクトメモリ** - ステアリングシステムがアーキテクチャ、技術スタック、製品コンテキストを維持
|
|
20
20
|
- 🚀 **自動オンボーディング** - `musubi-onboard` が既存プロジェクトを分析し、ステアリングドキュメントを生成(2-5分)
|
|
21
21
|
- 🔄 **自動同期** - `musubi-sync` がコードベースの変更を検出し、ステアリングドキュメントを最新に保つ
|
|
@@ -129,6 +129,24 @@ musubi-tasks list --priority P0 # 重要タスクのみ表示
|
|
|
129
129
|
musubi-tasks update 001 "In Progress" # タスクステータス更新
|
|
130
130
|
musubi-tasks validate # タスク完全性を検証
|
|
131
131
|
musubi-tasks graph # 依存関係グラフ表示
|
|
132
|
+
|
|
133
|
+
# エンドツーエンドトレーサビリティ(v0.8.5)
|
|
134
|
+
musubi-trace matrix # トレーサビリティマトリクス生成
|
|
135
|
+
musubi-trace matrix --format markdown > trace.md # Markdownにエクスポート
|
|
136
|
+
musubi-trace coverage # カバレッジ統計計算
|
|
137
|
+
musubi-trace coverage --min-coverage 100 # 100%カバレッジ要求
|
|
138
|
+
musubi-trace gaps # 孤立した要件/コード検出
|
|
139
|
+
musubi-trace requirement REQ-AUTH-001 # 特定要件をトレース
|
|
140
|
+
musubi-trace validate # 100%トレーサビリティ検証(第5条)
|
|
141
|
+
|
|
142
|
+
# ブラウンフィールドプロジェクト向け変更管理(v0.8.6)
|
|
143
|
+
musubi-change init CHANGE-001 --title "認証機能追加" # 変更提案を作成
|
|
144
|
+
musubi-change validate CHANGE-001 --verbose # 差分仕様を検証
|
|
145
|
+
musubi-change apply CHANGE-001 --dry-run # 変更をプレビュー
|
|
146
|
+
musubi-change apply CHANGE-001 # コードベースに変更を適用
|
|
147
|
+
musubi-change archive CHANGE-001 # specs/にアーカイブ
|
|
148
|
+
musubi-change list --status pending # 保留中の変更をリスト
|
|
149
|
+
musubi-change list --format json # JSON形式でリスト
|
|
132
150
|
```
|
|
133
151
|
|
|
134
152
|
### プロジェクトタイプ
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ MUSUBI is a comprehensive SDD (Specification Driven Development) framework that
|
|
|
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
21
|
- 🏗️ **Design Document Generator** - Create C4 models and ADRs with traceability (v0.8.2)
|
|
22
|
-
- 🔄 **
|
|
22
|
+
- 🔄 **Change Management System** - Delta specifications for brownfield projects (v0.8.6)
|
|
23
23
|
- 🧭 **Auto-Updating Project Memory** - Steering system maintains architecture, tech stack, and product context
|
|
24
24
|
- 🚀 **Automatic Onboarding** - `musubi-onboard` analyzes existing projects and generates steering docs (2-5 minutes)
|
|
25
25
|
- 🔄 **Auto-Sync** - `musubi-sync` detects codebase changes and keeps steering docs current
|
|
@@ -133,6 +133,24 @@ musubi-tasks list --priority P0 # List critical tasks
|
|
|
133
133
|
musubi-tasks update 001 "In Progress" # Update task status
|
|
134
134
|
musubi-tasks validate # Validate task completeness
|
|
135
135
|
musubi-tasks graph # Show dependency graph
|
|
136
|
+
|
|
137
|
+
# End-to-end traceability (v0.8.5)
|
|
138
|
+
musubi-trace matrix # Generate traceability matrix
|
|
139
|
+
musubi-trace matrix --format markdown > trace.md # Export to markdown
|
|
140
|
+
musubi-trace coverage # Calculate coverage statistics
|
|
141
|
+
musubi-trace coverage --min-coverage 100 # Require 100% coverage
|
|
142
|
+
musubi-trace gaps # Detect orphaned requirements/code
|
|
143
|
+
musubi-trace requirement REQ-AUTH-001 # Trace specific requirement
|
|
144
|
+
musubi-trace validate # Validate 100% traceability (Article V)
|
|
145
|
+
|
|
146
|
+
# Change management for brownfield projects (v0.8.6)
|
|
147
|
+
musubi-change init CHANGE-001 --title "Add authentication" # Create change proposal
|
|
148
|
+
musubi-change validate CHANGE-001 --verbose # Validate delta specification
|
|
149
|
+
musubi-change apply CHANGE-001 --dry-run # Preview changes
|
|
150
|
+
musubi-change apply CHANGE-001 # Apply changes to codebase
|
|
151
|
+
musubi-change archive CHANGE-001 # Archive to specs/
|
|
152
|
+
musubi-change list --status pending # List pending changes
|
|
153
|
+
musubi-change list --format json # List in JSON format
|
|
136
154
|
```
|
|
137
155
|
|
|
138
156
|
### Project Types
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MUSUBI Change Management CLI
|
|
5
|
+
*
|
|
6
|
+
* Manages delta specifications for brownfield projects
|
|
7
|
+
* Implements ADDED/MODIFIED/REMOVED/RENAMED change tracking
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* musubi-change init <change-id> # Create change proposal
|
|
11
|
+
* musubi-change apply <change-id> # Apply change to codebase
|
|
12
|
+
* musubi-change archive <change-id> # Archive completed change
|
|
13
|
+
* musubi-change list # List all changes
|
|
14
|
+
* musubi-change validate <change-id> # Validate delta format
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { Command } = require('commander');
|
|
18
|
+
const chalk = require('chalk');
|
|
19
|
+
const ChangeManager = require('../src/managers/change.js');
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('musubi-change')
|
|
25
|
+
.description('MUSUBI Change Management - Delta specifications for brownfield projects')
|
|
26
|
+
.version('0.8.6');
|
|
27
|
+
|
|
28
|
+
// Initialize change proposal
|
|
29
|
+
program
|
|
30
|
+
.command('init <change-id>')
|
|
31
|
+
.description('Create new change proposal with delta specification')
|
|
32
|
+
.option('-t, --title <title>', 'Change title')
|
|
33
|
+
.option('-d, --description <description>', 'Change description')
|
|
34
|
+
.option('--changes <dir>', 'Changes directory', 'storage/changes')
|
|
35
|
+
.option('--template <path>', 'Custom delta template')
|
|
36
|
+
.action(async (changeId, options) => {
|
|
37
|
+
try {
|
|
38
|
+
const workspaceRoot = process.cwd();
|
|
39
|
+
const manager = new ChangeManager(workspaceRoot);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.blue('📝 Creating change proposal...'));
|
|
42
|
+
console.log(chalk.dim(`Change ID: ${changeId}`));
|
|
43
|
+
|
|
44
|
+
const result = await manager.initChange(changeId, {
|
|
45
|
+
title: options.title,
|
|
46
|
+
description: options.description,
|
|
47
|
+
changesDir: options.changes,
|
|
48
|
+
template: options.template
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(chalk.green('✓ Change proposal created successfully'));
|
|
52
|
+
console.log(chalk.dim(`Location: ${result.file}`));
|
|
53
|
+
console.log();
|
|
54
|
+
console.log(chalk.yellow('Next steps:'));
|
|
55
|
+
console.log(chalk.dim('1. Edit the delta specification'));
|
|
56
|
+
console.log(chalk.dim(`2. Run: musubi-change validate ${changeId}`));
|
|
57
|
+
console.log(chalk.dim(`3. Run: musubi-change apply ${changeId}`));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(chalk.red('✗ Failed to create change proposal'));
|
|
60
|
+
console.error(chalk.dim(error.message));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Apply change to codebase
|
|
66
|
+
program
|
|
67
|
+
.command('apply <change-id>')
|
|
68
|
+
.description('Apply change proposal to codebase')
|
|
69
|
+
.option('--changes <dir>', 'Changes directory', 'storage/changes')
|
|
70
|
+
.option('--dry-run', 'Preview changes without applying')
|
|
71
|
+
.option('--force', 'Force apply even with validation errors')
|
|
72
|
+
.action(async (changeId, options) => {
|
|
73
|
+
try {
|
|
74
|
+
const workspaceRoot = process.cwd();
|
|
75
|
+
const manager = new ChangeManager(workspaceRoot);
|
|
76
|
+
|
|
77
|
+
if (options.dryRun) {
|
|
78
|
+
console.log(chalk.blue('🔍 Previewing changes (dry run)...'));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(chalk.blue('⚙️ Applying changes...'));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = await manager.applyChange(changeId, {
|
|
84
|
+
changesDir: options.changes,
|
|
85
|
+
dryRun: options.dryRun,
|
|
86
|
+
force: options.force
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
console.log(chalk.yellow('\nPreview (no changes applied):'));
|
|
91
|
+
console.log(chalk.green(` ${result.stats.added} files to be added`));
|
|
92
|
+
console.log(chalk.blue(` ${result.stats.modified} files to be modified`));
|
|
93
|
+
console.log(chalk.red(` ${result.stats.removed} files to be removed`));
|
|
94
|
+
console.log(chalk.yellow(` ${result.stats.renamed} files to be renamed`));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(chalk.green('\n✓ Changes applied successfully'));
|
|
97
|
+
console.log(chalk.green(` ${result.stats.added} files added`));
|
|
98
|
+
console.log(chalk.blue(` ${result.stats.modified} files modified`));
|
|
99
|
+
console.log(chalk.red(` ${result.stats.removed} files removed`));
|
|
100
|
+
console.log(chalk.yellow(` ${result.stats.renamed} files renamed`));
|
|
101
|
+
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk.yellow('Next steps:'));
|
|
104
|
+
console.log(chalk.dim('1. Test the changes'));
|
|
105
|
+
console.log(chalk.dim(`2. Run: musubi-change archive ${changeId}`));
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error(chalk.red('✗ Failed to apply changes'));
|
|
109
|
+
console.error(chalk.dim(error.message));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Archive completed change
|
|
115
|
+
program
|
|
116
|
+
.command('archive <change-id>')
|
|
117
|
+
.description('Archive completed change to specs/')
|
|
118
|
+
.option('--changes <dir>', 'Changes directory', 'storage/changes')
|
|
119
|
+
.option('--specs <dir>', 'Specs archive directory', 'specs')
|
|
120
|
+
.action(async (changeId, options) => {
|
|
121
|
+
try {
|
|
122
|
+
const workspaceRoot = process.cwd();
|
|
123
|
+
const manager = new ChangeManager(workspaceRoot);
|
|
124
|
+
|
|
125
|
+
console.log(chalk.blue('📦 Archiving change...'));
|
|
126
|
+
|
|
127
|
+
const result = await manager.archiveChange(changeId, {
|
|
128
|
+
changesDir: options.changes,
|
|
129
|
+
specsDir: options.specs
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
console.log(chalk.green('✓ Change archived successfully'));
|
|
133
|
+
console.log(chalk.dim(`Source: ${result.source}`));
|
|
134
|
+
console.log(chalk.dim(`Archive: ${result.archive}`));
|
|
135
|
+
console.log();
|
|
136
|
+
console.log(chalk.yellow('Delta merged to canonical specification'));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(chalk.red('✗ Failed to archive change'));
|
|
139
|
+
console.error(chalk.dim(error.message));
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// List all changes
|
|
145
|
+
program
|
|
146
|
+
.command('list')
|
|
147
|
+
.description('List all change proposals')
|
|
148
|
+
.option('--changes <dir>', 'Changes directory', 'storage/changes')
|
|
149
|
+
.option('--status <status>', 'Filter by status (pending|applied|archived)')
|
|
150
|
+
.option('--format <format>', 'Output format (table|json)', 'table')
|
|
151
|
+
.action(async (options) => {
|
|
152
|
+
try {
|
|
153
|
+
const workspaceRoot = process.cwd();
|
|
154
|
+
const manager = new ChangeManager(workspaceRoot);
|
|
155
|
+
|
|
156
|
+
const changes = await manager.listChanges({
|
|
157
|
+
changesDir: options.changes,
|
|
158
|
+
status: options.status
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (options.format === 'json') {
|
|
162
|
+
console.log(JSON.stringify(changes, null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Table format
|
|
167
|
+
console.log(chalk.bold('\nChange Proposals:\n'));
|
|
168
|
+
|
|
169
|
+
if (changes.length === 0) {
|
|
170
|
+
console.log(chalk.dim('No changes found'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(chalk.dim('ID'.padEnd(20) + 'Title'.padEnd(40) + 'Status'.padEnd(15) + 'Date'));
|
|
175
|
+
console.log(chalk.dim('-'.repeat(90)));
|
|
176
|
+
|
|
177
|
+
changes.forEach(change => {
|
|
178
|
+
const statusColor = {
|
|
179
|
+
pending: chalk.yellow,
|
|
180
|
+
applied: chalk.blue,
|
|
181
|
+
archived: chalk.green
|
|
182
|
+
}[change.status] || chalk.white;
|
|
183
|
+
|
|
184
|
+
console.log(
|
|
185
|
+
change.id.padEnd(20) +
|
|
186
|
+
change.title.padEnd(40) +
|
|
187
|
+
statusColor(change.status.padEnd(15)) +
|
|
188
|
+
change.date
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(chalk.dim(`Total: ${changes.length} change(s)`));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(chalk.red('✗ Failed to list changes'));
|
|
196
|
+
console.error(chalk.dim(error.message));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Validate delta format
|
|
202
|
+
program
|
|
203
|
+
.command('validate <change-id>')
|
|
204
|
+
.description('Validate delta specification format')
|
|
205
|
+
.option('--changes <dir>', 'Changes directory', 'storage/changes')
|
|
206
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
207
|
+
.action(async (changeId, options) => {
|
|
208
|
+
try {
|
|
209
|
+
const workspaceRoot = process.cwd();
|
|
210
|
+
const manager = new ChangeManager(workspaceRoot);
|
|
211
|
+
|
|
212
|
+
console.log(chalk.blue('🔍 Validating delta specification...'));
|
|
213
|
+
|
|
214
|
+
const result = await manager.validateChange(changeId, {
|
|
215
|
+
changesDir: options.changes,
|
|
216
|
+
verbose: options.verbose
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (result.valid) {
|
|
220
|
+
console.log(chalk.green('✓ Delta specification is valid'));
|
|
221
|
+
|
|
222
|
+
if (options.verbose) {
|
|
223
|
+
console.log();
|
|
224
|
+
console.log(chalk.bold('Summary:'));
|
|
225
|
+
console.log(chalk.green(` ${result.stats.added} ADDED items`));
|
|
226
|
+
console.log(chalk.blue(` ${result.stats.modified} MODIFIED items`));
|
|
227
|
+
console.log(chalk.red(` ${result.stats.removed} REMOVED items`));
|
|
228
|
+
console.log(chalk.yellow(` ${result.stats.renamed} RENAMED items`));
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
console.log(chalk.red('✗ Validation failed'));
|
|
232
|
+
console.log();
|
|
233
|
+
|
|
234
|
+
result.errors.forEach(error => {
|
|
235
|
+
console.log(chalk.red(` • ${error.message}`));
|
|
236
|
+
if (error.line) {
|
|
237
|
+
console.log(chalk.dim(` Line ${error.line}`));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(chalk.red('✗ Failed to validate change'));
|
|
245
|
+
console.error(chalk.dim(error.message));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
program.parse();
|
package/bin/musubi-design.js
CHANGED
|
File without changes
|
package/bin/musubi-tasks.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MUSUBI Traceability System CLI
|
|
5
|
+
*
|
|
6
|
+
* Provides end-to-end traceability from requirements to code to tests
|
|
7
|
+
* Ensures 100% coverage and detects gaps
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* musubi-trace matrix # Generate full traceability matrix
|
|
11
|
+
* musubi-trace coverage # Calculate requirement coverage
|
|
12
|
+
* musubi-trace gaps # Detect orphaned requirements/code
|
|
13
|
+
* musubi-trace requirement <id> # Trace specific requirement
|
|
14
|
+
* musubi-trace validate # Validate 100% coverage
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { Command } = require('commander');
|
|
18
|
+
const chalk = require('chalk');
|
|
19
|
+
const TraceabilityAnalyzer = require('../src/analyzers/traceability');
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('musubi-trace')
|
|
25
|
+
.description('MUSUBI Traceability System - End-to-end requirement traceability')
|
|
26
|
+
.version('0.8.5');
|
|
27
|
+
|
|
28
|
+
// Generate traceability matrix
|
|
29
|
+
program
|
|
30
|
+
.command('matrix')
|
|
31
|
+
.description('Generate full traceability matrix')
|
|
32
|
+
.option('-f, --format <type>', 'Output format (table|markdown|json|html)', 'table')
|
|
33
|
+
.option('-o, --output <path>', 'Output file path')
|
|
34
|
+
.option('--requirements <path>', 'Requirements directory', 'docs/requirements')
|
|
35
|
+
.option('--design <path>', 'Design directory', 'docs/design')
|
|
36
|
+
.option('--tasks <path>', 'Tasks directory', 'docs/tasks')
|
|
37
|
+
.option('--code <path>', 'Source code directory', 'src')
|
|
38
|
+
.option('--tests <path>', 'Tests directory', 'tests')
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
try {
|
|
41
|
+
console.log(chalk.bold('\n📊 Generating Traceability Matrix\n'));
|
|
42
|
+
|
|
43
|
+
const analyzer = new TraceabilityAnalyzer(process.cwd());
|
|
44
|
+
const matrix = await analyzer.generateMatrix(options);
|
|
45
|
+
|
|
46
|
+
if (options.output) {
|
|
47
|
+
const fs = require('fs-extra');
|
|
48
|
+
await fs.writeFile(options.output, analyzer.formatMatrix(matrix, options.format), 'utf-8');
|
|
49
|
+
console.log(chalk.green(`✓ Matrix saved to ${options.output}`));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(analyzer.formatMatrix(matrix, options.format));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(chalk.bold('Summary:'));
|
|
56
|
+
console.log(chalk.dim(` Requirements: ${matrix.summary.totalRequirements}`));
|
|
57
|
+
console.log(chalk.dim(` With Design: ${matrix.summary.withDesign} (${matrix.summary.designCoverage}%)`));
|
|
58
|
+
console.log(chalk.dim(` With Tasks: ${matrix.summary.withTasks} (${matrix.summary.tasksCoverage}%)`));
|
|
59
|
+
console.log(chalk.dim(` With Code: ${matrix.summary.withCode} (${matrix.summary.codeCoverage}%)`));
|
|
60
|
+
console.log(chalk.dim(` With Tests: ${matrix.summary.withTests} (${matrix.summary.testsCoverage}%)`));
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
process.exit(0);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Calculate coverage
|
|
71
|
+
program
|
|
72
|
+
.command('coverage')
|
|
73
|
+
.description('Calculate requirement coverage statistics')
|
|
74
|
+
.option('--requirements <path>', 'Requirements directory', 'docs/requirements')
|
|
75
|
+
.option('--design <path>', 'Design directory', 'docs/design')
|
|
76
|
+
.option('--tasks <path>', 'Tasks directory', 'docs/tasks')
|
|
77
|
+
.option('--code <path>', 'Source code directory', 'src')
|
|
78
|
+
.option('--tests <path>', 'Tests directory', 'tests')
|
|
79
|
+
.option('--min-coverage <percent>', 'Minimum required coverage', '100')
|
|
80
|
+
.action(async (options) => {
|
|
81
|
+
try {
|
|
82
|
+
console.log(chalk.bold('\n📈 Calculating Coverage\n'));
|
|
83
|
+
|
|
84
|
+
const analyzer = new TraceabilityAnalyzer(process.cwd());
|
|
85
|
+
const coverage = await analyzer.calculateCoverage(options);
|
|
86
|
+
|
|
87
|
+
const minCoverage = parseInt(options.minCoverage);
|
|
88
|
+
|
|
89
|
+
console.log(chalk.bold('Coverage Report:'));
|
|
90
|
+
console.log();
|
|
91
|
+
|
|
92
|
+
const stages = [
|
|
93
|
+
{ name: 'Requirements → Design', value: coverage.designCoverage, color: coverage.designCoverage >= minCoverage ? chalk.green : chalk.red },
|
|
94
|
+
{ name: 'Requirements → Tasks', value: coverage.tasksCoverage, color: coverage.tasksCoverage >= minCoverage ? chalk.green : chalk.red },
|
|
95
|
+
{ name: 'Requirements → Code', value: coverage.codeCoverage, color: coverage.codeCoverage >= minCoverage ? chalk.green : chalk.red },
|
|
96
|
+
{ name: 'Requirements → Tests', value: coverage.testsCoverage, color: coverage.testsCoverage >= minCoverage ? chalk.green : chalk.red }
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
stages.forEach(stage => {
|
|
100
|
+
const bar = '█'.repeat(Math.floor(stage.value / 2));
|
|
101
|
+
console.log(stage.color(` ${stage.name.padEnd(25)} ${bar} ${stage.value}%`));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(chalk.bold('Overall Coverage:'));
|
|
106
|
+
const overallColor = coverage.overall >= minCoverage ? chalk.green : chalk.red;
|
|
107
|
+
console.log(overallColor(` ${coverage.overall}% (min: ${minCoverage}%)`));
|
|
108
|
+
console.log();
|
|
109
|
+
|
|
110
|
+
if (coverage.overall >= minCoverage) {
|
|
111
|
+
console.log(chalk.green('✓ Coverage meets requirements\n'));
|
|
112
|
+
process.exit(0);
|
|
113
|
+
} else {
|
|
114
|
+
console.log(chalk.red('✗ Coverage below minimum threshold\n'));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Detect gaps
|
|
124
|
+
program
|
|
125
|
+
.command('gaps')
|
|
126
|
+
.description('Detect orphaned requirements, design, tasks, and untested code')
|
|
127
|
+
.option('--requirements <path>', 'Requirements directory', 'docs/requirements')
|
|
128
|
+
.option('--design <path>', 'Design directory', 'docs/design')
|
|
129
|
+
.option('--tasks <path>', 'Tasks directory', 'docs/tasks')
|
|
130
|
+
.option('--code <path>', 'Source code directory', 'src')
|
|
131
|
+
.option('--tests <path>', 'Tests directory', 'tests')
|
|
132
|
+
.option('-v, --verbose', 'Show detailed gap information')
|
|
133
|
+
.action(async (options) => {
|
|
134
|
+
try {
|
|
135
|
+
console.log(chalk.bold('\n🔍 Detecting Gaps\n'));
|
|
136
|
+
|
|
137
|
+
const analyzer = new TraceabilityAnalyzer(process.cwd());
|
|
138
|
+
const gaps = await analyzer.detectGaps(options);
|
|
139
|
+
|
|
140
|
+
let hasGaps = false;
|
|
141
|
+
|
|
142
|
+
if (gaps.orphanedRequirements.length > 0) {
|
|
143
|
+
hasGaps = true;
|
|
144
|
+
console.log(chalk.red.bold('Orphaned Requirements (no design/tasks):'));
|
|
145
|
+
gaps.orphanedRequirements.forEach(req => {
|
|
146
|
+
console.log(chalk.red(` • ${req.id}: ${req.title}`));
|
|
147
|
+
if (options.verbose && req.file) {
|
|
148
|
+
console.log(chalk.dim(` ${req.file}`));
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
console.log();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (gaps.orphanedDesign.length > 0) {
|
|
155
|
+
hasGaps = true;
|
|
156
|
+
console.log(chalk.yellow.bold('Orphaned Design (no requirements):'));
|
|
157
|
+
gaps.orphanedDesign.forEach(design => {
|
|
158
|
+
console.log(chalk.yellow(` • ${design.id}: ${design.title}`));
|
|
159
|
+
if (options.verbose && design.file) {
|
|
160
|
+
console.log(chalk.dim(` ${design.file}`));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
console.log();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (gaps.orphanedTasks.length > 0) {
|
|
167
|
+
hasGaps = true;
|
|
168
|
+
console.log(chalk.yellow.bold('Orphaned Tasks (no requirements):'));
|
|
169
|
+
gaps.orphanedTasks.forEach(task => {
|
|
170
|
+
console.log(chalk.yellow(` • ${task.id}: ${task.title}`));
|
|
171
|
+
if (options.verbose && task.file) {
|
|
172
|
+
console.log(chalk.dim(` ${task.file}`));
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (gaps.untestedCode.length > 0) {
|
|
179
|
+
hasGaps = true;
|
|
180
|
+
console.log(chalk.red.bold('Untested Code (no test coverage):'));
|
|
181
|
+
gaps.untestedCode.forEach(code => {
|
|
182
|
+
console.log(chalk.red(` • ${code.file}:${code.function || code.class}`));
|
|
183
|
+
if (options.verbose && code.lines) {
|
|
184
|
+
console.log(chalk.dim(` Lines: ${code.lines}`));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
console.log();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (gaps.missingTests.length > 0) {
|
|
191
|
+
hasGaps = true;
|
|
192
|
+
console.log(chalk.red.bold('Missing Tests (requirements not tested):'));
|
|
193
|
+
gaps.missingTests.forEach(req => {
|
|
194
|
+
console.log(chalk.red(` • ${req.id}: ${req.title}`));
|
|
195
|
+
});
|
|
196
|
+
console.log();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!hasGaps) {
|
|
200
|
+
console.log(chalk.green('✓ No gaps detected - 100% traceability!\n'));
|
|
201
|
+
process.exit(0);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(chalk.bold('Gap Summary:'));
|
|
204
|
+
console.log(chalk.dim(` Orphaned Requirements: ${gaps.orphanedRequirements.length}`));
|
|
205
|
+
console.log(chalk.dim(` Orphaned Design: ${gaps.orphanedDesign.length}`));
|
|
206
|
+
console.log(chalk.dim(` Orphaned Tasks: ${gaps.orphanedTasks.length}`));
|
|
207
|
+
console.log(chalk.dim(` Untested Code: ${gaps.untestedCode.length}`));
|
|
208
|
+
console.log(chalk.dim(` Missing Tests: ${gaps.missingTests.length}`));
|
|
209
|
+
console.log();
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Trace specific requirement
|
|
219
|
+
program
|
|
220
|
+
.command('requirement <id>')
|
|
221
|
+
.description('Trace specific requirement through design, tasks, code, and tests')
|
|
222
|
+
.option('--requirements <path>', 'Requirements directory', 'docs/requirements')
|
|
223
|
+
.option('--design <path>', 'Design directory', 'docs/design')
|
|
224
|
+
.option('--tasks <path>', 'Tasks directory', 'docs/tasks')
|
|
225
|
+
.option('--code <path>', 'Source code directory', 'src')
|
|
226
|
+
.option('--tests <path>', 'Tests directory', 'tests')
|
|
227
|
+
.action(async (id, options) => {
|
|
228
|
+
try {
|
|
229
|
+
console.log(chalk.bold(`\n🔗 Tracing Requirement: ${id}\n`));
|
|
230
|
+
|
|
231
|
+
const analyzer = new TraceabilityAnalyzer(process.cwd());
|
|
232
|
+
const trace = await analyzer.traceRequirement(id, options);
|
|
233
|
+
|
|
234
|
+
if (!trace.requirement) {
|
|
235
|
+
console.log(chalk.red(`✗ Requirement ${id} not found\n`));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log(chalk.bold('Requirement:'));
|
|
240
|
+
console.log(chalk.cyan(` ${trace.requirement.id}: ${trace.requirement.title}`));
|
|
241
|
+
console.log(chalk.dim(` ${trace.requirement.file}`));
|
|
242
|
+
console.log();
|
|
243
|
+
|
|
244
|
+
if (trace.design.length > 0) {
|
|
245
|
+
console.log(chalk.bold('Design:'));
|
|
246
|
+
trace.design.forEach(d => {
|
|
247
|
+
console.log(chalk.green(` ✓ ${d.id}: ${d.title}`));
|
|
248
|
+
console.log(chalk.dim(` ${d.file}`));
|
|
249
|
+
});
|
|
250
|
+
console.log();
|
|
251
|
+
} else {
|
|
252
|
+
console.log(chalk.yellow('⚠ No design found\n'));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (trace.tasks.length > 0) {
|
|
256
|
+
console.log(chalk.bold('Tasks:'));
|
|
257
|
+
trace.tasks.forEach(t => {
|
|
258
|
+
console.log(chalk.green(` ✓ ${t.id}: ${t.title} (${t.status})`));
|
|
259
|
+
console.log(chalk.dim(` ${t.file}`));
|
|
260
|
+
});
|
|
261
|
+
console.log();
|
|
262
|
+
} else {
|
|
263
|
+
console.log(chalk.yellow('⚠ No tasks found\n'));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (trace.code.length > 0) {
|
|
267
|
+
console.log(chalk.bold('Code:'));
|
|
268
|
+
trace.code.forEach(c => {
|
|
269
|
+
console.log(chalk.green(` ✓ ${c.file}:${c.function || c.class}`));
|
|
270
|
+
console.log(chalk.dim(` Lines: ${c.lines}`));
|
|
271
|
+
});
|
|
272
|
+
console.log();
|
|
273
|
+
} else {
|
|
274
|
+
console.log(chalk.yellow('⚠ No code implementation found\n'));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (trace.tests.length > 0) {
|
|
278
|
+
console.log(chalk.bold('Tests:'));
|
|
279
|
+
trace.tests.forEach(t => {
|
|
280
|
+
console.log(chalk.green(` ✓ ${t.file}:${t.test}`));
|
|
281
|
+
console.log(chalk.dim(` Status: ${t.status || 'passing'}`));
|
|
282
|
+
});
|
|
283
|
+
console.log();
|
|
284
|
+
} else {
|
|
285
|
+
console.log(chalk.red('✗ No tests found\n'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const coverage = {
|
|
289
|
+
design: trace.design.length > 0,
|
|
290
|
+
tasks: trace.tasks.length > 0,
|
|
291
|
+
code: trace.code.length > 0,
|
|
292
|
+
tests: trace.tests.length > 0
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const coveragePercent = Object.values(coverage).filter(Boolean).length * 25;
|
|
296
|
+
const coverageColor = coveragePercent === 100 ? chalk.green : coveragePercent >= 75 ? chalk.yellow : chalk.red;
|
|
297
|
+
|
|
298
|
+
console.log(chalk.bold('Coverage:'));
|
|
299
|
+
console.log(coverageColor(` ${coveragePercent}% (${Object.values(coverage).filter(Boolean).length}/4 stages)`));
|
|
300
|
+
console.log();
|
|
301
|
+
|
|
302
|
+
process.exit(coveragePercent === 100 ? 0 : 1);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Validate 100% coverage
|
|
310
|
+
program
|
|
311
|
+
.command('validate')
|
|
312
|
+
.description('Validate 100% traceability coverage (Constitutional Article V)')
|
|
313
|
+
.option('--requirements <path>', 'Requirements directory', 'docs/requirements')
|
|
314
|
+
.option('--design <path>', 'Design directory', 'docs/design')
|
|
315
|
+
.option('--tasks <path>', 'Tasks directory', 'docs/tasks')
|
|
316
|
+
.option('--code <path>', 'Source code directory', 'src')
|
|
317
|
+
.option('--tests <path>', 'Tests directory', 'tests')
|
|
318
|
+
.option('--strict', 'Fail on any gaps (default: true)', true)
|
|
319
|
+
.action(async (options) => {
|
|
320
|
+
try {
|
|
321
|
+
console.log(chalk.bold('\n🔍 Validating Traceability (Article V)\n'));
|
|
322
|
+
|
|
323
|
+
const analyzer = new TraceabilityAnalyzer(process.cwd());
|
|
324
|
+
const validation = await analyzer.validate(options);
|
|
325
|
+
|
|
326
|
+
console.log(chalk.bold('Article V: Complete Traceability'));
|
|
327
|
+
console.log();
|
|
328
|
+
|
|
329
|
+
if (validation.passed) {
|
|
330
|
+
console.log(chalk.green('✓ 100% traceability achieved'));
|
|
331
|
+
console.log(chalk.dim(` Requirements: ${validation.coverage.totalRequirements}`));
|
|
332
|
+
console.log(chalk.dim(` Design Coverage: ${validation.coverage.designCoverage}%`));
|
|
333
|
+
console.log(chalk.dim(` Tasks Coverage: ${validation.coverage.tasksCoverage}%`));
|
|
334
|
+
console.log(chalk.dim(` Code Coverage: ${validation.coverage.codeCoverage}%`));
|
|
335
|
+
console.log(chalk.dim(` Test Coverage: ${validation.coverage.testsCoverage}%`));
|
|
336
|
+
console.log();
|
|
337
|
+
process.exit(0);
|
|
338
|
+
} else {
|
|
339
|
+
console.log(chalk.red('✗ Traceability gaps detected'));
|
|
340
|
+
console.log();
|
|
341
|
+
|
|
342
|
+
if (validation.gaps.orphanedRequirements.length > 0) {
|
|
343
|
+
console.log(chalk.red(` Orphaned Requirements: ${validation.gaps.orphanedRequirements.length}`));
|
|
344
|
+
}
|
|
345
|
+
if (validation.gaps.untestedCode.length > 0) {
|
|
346
|
+
console.log(chalk.red(` Untested Code: ${validation.gaps.untestedCode.length}`));
|
|
347
|
+
}
|
|
348
|
+
if (validation.gaps.missingTests.length > 0) {
|
|
349
|
+
console.log(chalk.red(` Missing Tests: ${validation.gaps.missingTests.length}`));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
console.log();
|
|
353
|
+
console.log(chalk.dim('Run `musubi-trace gaps` for detailed gap analysis'));
|
|
354
|
+
console.log();
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
program.parse(process.argv);
|
|
364
|
+
|
|
365
|
+
if (!process.argv.slice(2).length) {
|
|
366
|
+
program.outputHelp();
|
|
367
|
+
}
|