musubi-sdd 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +32 -0
- package/README.md +10 -0
- package/bin/musubi-validate.js +271 -0
- package/package.json +3 -2
- package/src/validators/constitution.js +535 -0
package/README.ja.md
CHANGED
|
@@ -16,6 +16,11 @@ MUSUBIは、6つの主要フレームワークのベスト機能を統合した
|
|
|
16
16
|
- 📝 **EARS要件形式** - 完全なトレーサビリティを持つ明確な要件
|
|
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,33 @@ 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 # 詳細付き完全検証
|
|
75
107
|
```
|
|
76
108
|
|
|
77
109
|
### プロジェクトタイプ
|
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ MUSUBI is a comprehensive SDD (Specification Driven Development) framework that
|
|
|
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,6 +100,15 @@ 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
|
|
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
|
+
```
|
|
102
112
|
musubi-share sync # Sync across AI platforms
|
|
103
113
|
musubi-share status # Show sharing status
|
|
104
114
|
```
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MUSUBI Constitutional Validator CLI
|
|
5
|
+
*
|
|
6
|
+
* Validates project compliance with 9 Constitutional Articles
|
|
7
|
+
* Enforces Phase -1 Gates and SDD governance rules
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* musubi-validate constitution # Validate all 9 articles
|
|
11
|
+
* musubi-validate article <1-9> # Validate specific article
|
|
12
|
+
* musubi-validate gates # Validate Phase -1 Gates
|
|
13
|
+
* musubi-validate complexity # Validate complexity limits
|
|
14
|
+
* musubi-validate all # Run all validations
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { Command } = require('commander');
|
|
18
|
+
const chalk = require('chalk');
|
|
19
|
+
const ConstitutionValidator = require('../src/validators/constitution');
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('musubi-validate')
|
|
25
|
+
.description('Constitutional Governance Validator - Enforce 9 Immutable Articles')
|
|
26
|
+
.version('0.7.0');
|
|
27
|
+
|
|
28
|
+
// Main validation command
|
|
29
|
+
program
|
|
30
|
+
.command('constitution')
|
|
31
|
+
.description('Validate all 9 Constitutional Articles')
|
|
32
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
33
|
+
.option('-f, --format <type>', 'Output format (console|json|markdown)', 'console')
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
try {
|
|
36
|
+
const validator = new ConstitutionValidator(process.cwd());
|
|
37
|
+
const results = await validator.validateAll();
|
|
38
|
+
|
|
39
|
+
displayResults('Constitutional Validation', results, options);
|
|
40
|
+
process.exit(results.passed ? 0 : 1);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(chalk.red('✗ Validation error:'), error.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Article-specific validation
|
|
48
|
+
program
|
|
49
|
+
.command('article <number>')
|
|
50
|
+
.description('Validate specific Constitutional Article (1-9)')
|
|
51
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
52
|
+
.option('-f, --format <type>', 'Output format (console|json|markdown)', 'console')
|
|
53
|
+
.action(async (number, options) => {
|
|
54
|
+
try {
|
|
55
|
+
const articleNum = parseInt(number);
|
|
56
|
+
if (articleNum < 1 || articleNum > 9) {
|
|
57
|
+
console.error(chalk.red('✗ Article number must be between 1 and 9'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const validator = new ConstitutionValidator(process.cwd());
|
|
62
|
+
const result = await validator.validateArticle(articleNum);
|
|
63
|
+
|
|
64
|
+
displayResults(`Article ${articleNum} Validation`, result, options);
|
|
65
|
+
process.exit(result.passed ? 0 : 1);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(chalk.red('✗ Validation error:'), error.message);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Phase -1 Gates validation
|
|
73
|
+
program
|
|
74
|
+
.command('gates')
|
|
75
|
+
.description('Validate Phase -1 Gates (Simplicity, Anti-Abstraction)')
|
|
76
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
77
|
+
.option('-f, --format <type>', 'Output format (console|json|markdown)', 'console')
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
try {
|
|
80
|
+
const validator = new ConstitutionValidator(process.cwd());
|
|
81
|
+
const results = await validator.validateGates();
|
|
82
|
+
|
|
83
|
+
displayResults('Phase -1 Gates Validation', results, options);
|
|
84
|
+
process.exit(results.passed ? 0 : 1);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(chalk.red('✗ Validation error:'), error.message);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Complexity validation
|
|
92
|
+
program
|
|
93
|
+
.command('complexity')
|
|
94
|
+
.description('Validate complexity limits (modules ≤1500 lines, functions ≤50 lines)')
|
|
95
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
96
|
+
.option('-f, --format <type>', 'Output format (console|json|markdown)', 'console')
|
|
97
|
+
.action(async (options) => {
|
|
98
|
+
try {
|
|
99
|
+
const validator = new ConstitutionValidator(process.cwd());
|
|
100
|
+
const results = await validator.validateComplexity();
|
|
101
|
+
|
|
102
|
+
displayResults('Complexity Validation', results, options);
|
|
103
|
+
process.exit(results.passed ? 0 : 1);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(chalk.red('✗ Validation error:'), error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// All validations
|
|
111
|
+
program
|
|
112
|
+
.command('all')
|
|
113
|
+
.description('Run all validations (constitution + gates + complexity)')
|
|
114
|
+
.option('-v, --verbose', 'Show detailed validation results')
|
|
115
|
+
.option('-f, --format <type>', 'Output format (console|json|markdown)', 'console')
|
|
116
|
+
.action(async (options) => {
|
|
117
|
+
try {
|
|
118
|
+
const validator = new ConstitutionValidator(process.cwd());
|
|
119
|
+
|
|
120
|
+
console.log(chalk.bold('\n🔍 Running comprehensive constitutional validation...\n'));
|
|
121
|
+
|
|
122
|
+
const [constitutionResults, gatesResults, complexityResults] = await Promise.all([
|
|
123
|
+
validator.validateAll(),
|
|
124
|
+
validator.validateGates(),
|
|
125
|
+
validator.validateComplexity()
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const allResults = {
|
|
129
|
+
constitution: constitutionResults,
|
|
130
|
+
gates: gatesResults,
|
|
131
|
+
complexity: complexityResults,
|
|
132
|
+
passed: constitutionResults.passed && gatesResults.passed && complexityResults.passed
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
displayAllResults(allResults, options);
|
|
136
|
+
process.exit(allResults.passed ? 0 : 1);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(chalk.red('✗ Validation error:'), error.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Display validation results
|
|
145
|
+
*/
|
|
146
|
+
function displayResults(title, results, options) {
|
|
147
|
+
if (options.format === 'json') {
|
|
148
|
+
console.log(JSON.stringify(results, null, 2));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.format === 'markdown') {
|
|
153
|
+
displayMarkdown(title, results);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Console format (default)
|
|
158
|
+
console.log(chalk.bold(`\n📋 ${title}\n`));
|
|
159
|
+
|
|
160
|
+
if (results.passed) {
|
|
161
|
+
console.log(chalk.green('✓ All checks passed\n'));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.red('✗ Validation failed\n'));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options.verbose && results.details) {
|
|
167
|
+
console.log(chalk.bold('Details:'));
|
|
168
|
+
results.details.forEach(detail => {
|
|
169
|
+
const icon = detail.passed ? chalk.green('✓') : chalk.red('✗');
|
|
170
|
+
console.log(` ${icon} ${detail.message}`);
|
|
171
|
+
});
|
|
172
|
+
console.log();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (results.violations && results.violations.length > 0) {
|
|
176
|
+
console.log(chalk.bold.red('Violations:'));
|
|
177
|
+
results.violations.forEach(violation => {
|
|
178
|
+
console.log(chalk.red(` • ${violation}`));
|
|
179
|
+
});
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (results.warnings && results.warnings.length > 0) {
|
|
184
|
+
console.log(chalk.bold.yellow('Warnings:'));
|
|
185
|
+
results.warnings.forEach(warning => {
|
|
186
|
+
console.log(chalk.yellow(` ⚠ ${warning}`));
|
|
187
|
+
});
|
|
188
|
+
console.log();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (results.summary) {
|
|
192
|
+
console.log(chalk.bold('Summary:'));
|
|
193
|
+
console.log(chalk.dim(results.summary));
|
|
194
|
+
console.log();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Display all validation results
|
|
200
|
+
*/
|
|
201
|
+
function displayAllResults(allResults, options) {
|
|
202
|
+
if (options.format === 'json') {
|
|
203
|
+
console.log(JSON.stringify(allResults, null, 2));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(chalk.bold('\n📊 Comprehensive Validation Results\n'));
|
|
208
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
209
|
+
|
|
210
|
+
// Constitution
|
|
211
|
+
const constitutionIcon = allResults.constitution.passed ? chalk.green('✓') : chalk.red('✗');
|
|
212
|
+
console.log(`\n${constitutionIcon} ${chalk.bold('Constitutional Articles')}: ${allResults.constitution.passed ? chalk.green('PASSED') : chalk.red('FAILED')}`);
|
|
213
|
+
|
|
214
|
+
// Gates
|
|
215
|
+
const gatesIcon = allResults.gates.passed ? chalk.green('✓') : chalk.red('✗');
|
|
216
|
+
console.log(`${gatesIcon} ${chalk.bold('Phase -1 Gates')}: ${allResults.gates.passed ? chalk.green('PASSED') : chalk.red('FAILED')}`);
|
|
217
|
+
|
|
218
|
+
// Complexity
|
|
219
|
+
const complexityIcon = allResults.complexity.passed ? chalk.green('✓') : chalk.red('✗');
|
|
220
|
+
console.log(`${complexityIcon} ${chalk.bold('Complexity Limits')}: ${allResults.complexity.passed ? chalk.green('PASSED') : chalk.red('FAILED')}`);
|
|
221
|
+
|
|
222
|
+
console.log('\n' + chalk.bold('━'.repeat(60)));
|
|
223
|
+
|
|
224
|
+
if (allResults.passed) {
|
|
225
|
+
console.log(chalk.bold.green('\n✓ ALL VALIDATIONS PASSED\n'));
|
|
226
|
+
console.log(chalk.dim('Your project complies with all 9 Constitutional Articles.'));
|
|
227
|
+
} else {
|
|
228
|
+
console.log(chalk.bold.red('\n✗ VALIDATION FAILURES DETECTED\n'));
|
|
229
|
+
console.log(chalk.dim('Please address the violations above before proceeding.'));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Display results in Markdown format
|
|
237
|
+
*/
|
|
238
|
+
function displayMarkdown(title, results) {
|
|
239
|
+
console.log(`# ${title}\n`);
|
|
240
|
+
console.log(`**Status**: ${results.passed ? '✓ PASSED' : '✗ FAILED'}\n`);
|
|
241
|
+
|
|
242
|
+
if (results.violations && results.violations.length > 0) {
|
|
243
|
+
console.log('## Violations\n');
|
|
244
|
+
results.violations.forEach(violation => {
|
|
245
|
+
console.log(`- ${violation}`);
|
|
246
|
+
});
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (results.warnings && results.warnings.length > 0) {
|
|
251
|
+
console.log('## Warnings\n');
|
|
252
|
+
results.warnings.forEach(warning => {
|
|
253
|
+
console.log(`- ${warning}`);
|
|
254
|
+
});
|
|
255
|
+
console.log();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (results.summary) {
|
|
259
|
+
console.log('## Summary\n');
|
|
260
|
+
console.log(results.summary);
|
|
261
|
+
console.log();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Parse arguments
|
|
266
|
+
program.parse(process.argv);
|
|
267
|
+
|
|
268
|
+
// Show help if no command provided
|
|
269
|
+
if (!process.argv.slice(2).length) {
|
|
270
|
+
program.outputHelp();
|
|
271
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musubi-sdd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"musubi-onboard": "bin/musubi-onboard.js",
|
|
11
11
|
"musubi-sync": "bin/musubi-sync.js",
|
|
12
12
|
"musubi-analyze": "bin/musubi-analyze.js",
|
|
13
|
-
"musubi-share": "bin/musubi-share.js"
|
|
13
|
+
"musubi-share": "bin/musubi-share.js",
|
|
14
|
+
"musubi-validate": "bin/musubi-validate.js"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
17
|
"test": "jest",
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitutional Governance Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates project compliance with 9 Constitutional Articles:
|
|
5
|
+
*
|
|
6
|
+
* Article I: Library-First Principle
|
|
7
|
+
* Article II: CLI Interface Mandate
|
|
8
|
+
* Article III: Test-First Imperative
|
|
9
|
+
* Article IV: EARS Requirements Format
|
|
10
|
+
* Article V: Traceability Mandate
|
|
11
|
+
* Article VI: Project Memory (Steering System)
|
|
12
|
+
* Article VII: Simplicity Gate (≤3 sub-projects)
|
|
13
|
+
* Article VIII: Anti-Abstraction Gate
|
|
14
|
+
* Article IX: Integration-First Testing
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs-extra');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { glob } = require('glob');
|
|
20
|
+
|
|
21
|
+
class ConstitutionValidator {
|
|
22
|
+
constructor(projectRoot) {
|
|
23
|
+
this.projectRoot = projectRoot;
|
|
24
|
+
this.steeringPath = path.join(projectRoot, 'steering');
|
|
25
|
+
this.constitutionPath = path.join(this.steeringPath, 'rules', 'constitution.md');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate all 9 Constitutional Articles
|
|
30
|
+
*/
|
|
31
|
+
async validateAll() {
|
|
32
|
+
const results = {
|
|
33
|
+
passed: true,
|
|
34
|
+
violations: [],
|
|
35
|
+
warnings: [],
|
|
36
|
+
details: [],
|
|
37
|
+
articles: {}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (let i = 1; i <= 9; i++) {
|
|
41
|
+
const articleResult = await this.validateArticle(i);
|
|
42
|
+
results.articles[i] = articleResult;
|
|
43
|
+
|
|
44
|
+
if (!articleResult.passed) {
|
|
45
|
+
results.passed = false;
|
|
46
|
+
results.violations.push(...articleResult.violations);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
results.warnings.push(...(articleResult.warnings || []));
|
|
50
|
+
results.details.push({
|
|
51
|
+
article: i,
|
|
52
|
+
passed: articleResult.passed,
|
|
53
|
+
message: articleResult.summary
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
results.summary = `${Object.values(results.articles).filter(a => a.passed).length}/9 Articles validated successfully`;
|
|
58
|
+
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate specific Constitutional Article
|
|
64
|
+
*/
|
|
65
|
+
async validateArticle(articleNumber) {
|
|
66
|
+
const validators = {
|
|
67
|
+
1: this.validateArticle1.bind(this), // Library-First
|
|
68
|
+
2: this.validateArticle2.bind(this), // CLI Interface
|
|
69
|
+
3: this.validateArticle3.bind(this), // Test-First
|
|
70
|
+
4: this.validateArticle4.bind(this), // EARS Format
|
|
71
|
+
5: this.validateArticle5.bind(this), // Traceability
|
|
72
|
+
6: this.validateArticle6.bind(this), // Project Memory
|
|
73
|
+
7: this.validateArticle7.bind(this), // Simplicity Gate
|
|
74
|
+
8: this.validateArticle8.bind(this), // Anti-Abstraction
|
|
75
|
+
9: this.validateArticle9.bind(this) // Integration Testing
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const validator = validators[articleNumber];
|
|
79
|
+
if (!validator) {
|
|
80
|
+
throw new Error(`Invalid article number: ${articleNumber}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return await validator();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Article I: Library-First Principle
|
|
88
|
+
*/
|
|
89
|
+
async validateArticle1() {
|
|
90
|
+
const result = {
|
|
91
|
+
article: 1,
|
|
92
|
+
name: 'Library-First Principle',
|
|
93
|
+
passed: true,
|
|
94
|
+
violations: [],
|
|
95
|
+
warnings: [],
|
|
96
|
+
summary: ''
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Check for lib/ directory
|
|
100
|
+
const libPath = path.join(this.projectRoot, 'lib');
|
|
101
|
+
const srcPath = path.join(this.projectRoot, 'src');
|
|
102
|
+
|
|
103
|
+
if (await fs.pathExists(libPath)) {
|
|
104
|
+
result.warnings.push('Article I: lib/ directory found - verify libraries are independent');
|
|
105
|
+
} else if (await fs.pathExists(srcPath)) {
|
|
106
|
+
result.warnings.push('Article I: src/ directory found - consider separating libraries to lib/');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
result.summary = 'Article I: Library-First Principle - Manual review recommended';
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Article II: CLI Interface Mandate
|
|
115
|
+
*/
|
|
116
|
+
async validateArticle2() {
|
|
117
|
+
const result = {
|
|
118
|
+
article: 2,
|
|
119
|
+
name: 'CLI Interface Mandate',
|
|
120
|
+
passed: true,
|
|
121
|
+
violations: [],
|
|
122
|
+
warnings: [],
|
|
123
|
+
summary: ''
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Check for bin/ directory or package.json bin entries
|
|
127
|
+
const binPath = path.join(this.projectRoot, 'bin');
|
|
128
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
129
|
+
|
|
130
|
+
if (await fs.pathExists(binPath)) {
|
|
131
|
+
const binFiles = await fs.readdir(binPath);
|
|
132
|
+
result.warnings.push(`Article II: ${binFiles.length} CLI scripts found in bin/`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
136
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
137
|
+
if (packageJson.bin) {
|
|
138
|
+
const binCount = Object.keys(packageJson.bin).length;
|
|
139
|
+
result.warnings.push(`Article II: ${binCount} CLI commands defined in package.json`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
result.summary = 'Article II: CLI Interface Mandate - CLI structure detected';
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Article III: Test-First Imperative
|
|
149
|
+
*/
|
|
150
|
+
async validateArticle3() {
|
|
151
|
+
const result = {
|
|
152
|
+
article: 3,
|
|
153
|
+
name: 'Test-First Imperative',
|
|
154
|
+
passed: true,
|
|
155
|
+
violations: [],
|
|
156
|
+
warnings: [],
|
|
157
|
+
summary: ''
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Check for test files
|
|
161
|
+
const testPatterns = [
|
|
162
|
+
'test/**/*.js',
|
|
163
|
+
'tests/**/*.js',
|
|
164
|
+
'src/**/*.test.js',
|
|
165
|
+
'src/**/*.spec.js',
|
|
166
|
+
'__tests__/**/*.js'
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
let testFileCount = 0;
|
|
170
|
+
for (const pattern of testPatterns) {
|
|
171
|
+
const files = await glob(pattern, { cwd: this.projectRoot });
|
|
172
|
+
testFileCount += files.length;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (testFileCount === 0) {
|
|
176
|
+
result.passed = false;
|
|
177
|
+
result.violations.push('Article III: No test files found - Test-First Imperative violated');
|
|
178
|
+
} else {
|
|
179
|
+
result.warnings.push(`Article III: ${testFileCount} test files found - verify Red-Green-Blue cycle in git history`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check for test coverage configuration
|
|
183
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
184
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
185
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
186
|
+
if (packageJson.jest && packageJson.jest.coverageThreshold) {
|
|
187
|
+
result.warnings.push('Article III: Test coverage thresholds configured');
|
|
188
|
+
} else {
|
|
189
|
+
result.warnings.push('Article III: No coverage thresholds - consider adding 80% minimum');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
result.summary = testFileCount > 0
|
|
194
|
+
? `Article III: ${testFileCount} test files found`
|
|
195
|
+
: 'Article III: No tests found - VIOLATION';
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Article IV: EARS Requirements Format
|
|
202
|
+
*/
|
|
203
|
+
async validateArticle4() {
|
|
204
|
+
const result = {
|
|
205
|
+
article: 4,
|
|
206
|
+
name: 'EARS Requirements Format',
|
|
207
|
+
passed: true,
|
|
208
|
+
violations: [],
|
|
209
|
+
warnings: [],
|
|
210
|
+
summary: ''
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Check for requirements files
|
|
214
|
+
const reqPaths = [
|
|
215
|
+
path.join(this.steeringPath, 'requirements.md'),
|
|
216
|
+
path.join(this.projectRoot, 'docs', 'requirements.md'),
|
|
217
|
+
path.join(this.projectRoot, 'specs', 'requirements.md')
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
let requirementsFound = false;
|
|
221
|
+
for (const reqPath of reqPaths) {
|
|
222
|
+
if (await fs.pathExists(reqPath)) {
|
|
223
|
+
requirementsFound = true;
|
|
224
|
+
const content = await fs.readFile(reqPath, 'utf-8');
|
|
225
|
+
|
|
226
|
+
// Check for EARS patterns
|
|
227
|
+
const earsPatterns = [
|
|
228
|
+
/WHEN\s+.*,?\s+the\s+.*\s+SHALL/i,
|
|
229
|
+
/WHILE\s+.*,?\s+the\s+.*\s+SHALL/i,
|
|
230
|
+
/IF\s+.*,?\s+THEN\s+the\s+.*\s+SHALL/i,
|
|
231
|
+
/WHERE\s+.*,?\s+the\s+.*\s+SHALL/i,
|
|
232
|
+
/The\s+.*\s+SHALL/i
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const hasEarsFormat = earsPatterns.some(pattern => pattern.test(content));
|
|
236
|
+
|
|
237
|
+
if (hasEarsFormat) {
|
|
238
|
+
result.warnings.push('Article IV: EARS format detected in requirements');
|
|
239
|
+
} else {
|
|
240
|
+
result.violations.push(`Article IV: Requirements file exists but no EARS patterns found: ${reqPath}`);
|
|
241
|
+
result.passed = false;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!requirementsFound) {
|
|
248
|
+
result.warnings.push('Article IV: No requirements.md found - create when starting new features');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
result.summary = requirementsFound
|
|
252
|
+
? 'Article IV: Requirements file found'
|
|
253
|
+
: 'Article IV: No requirements file - OK for infrastructure phase';
|
|
254
|
+
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Article V: Traceability Mandate
|
|
260
|
+
*/
|
|
261
|
+
async validateArticle5() {
|
|
262
|
+
const result = {
|
|
263
|
+
article: 5,
|
|
264
|
+
name: 'Traceability Mandate',
|
|
265
|
+
passed: true,
|
|
266
|
+
violations: [],
|
|
267
|
+
warnings: [],
|
|
268
|
+
summary: ''
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Check for traceability matrix
|
|
272
|
+
const traceabilityPaths = [
|
|
273
|
+
path.join(this.steeringPath, 'traceability.md'),
|
|
274
|
+
path.join(this.projectRoot, 'docs', 'traceability.md')
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
let traceabilityFound = false;
|
|
278
|
+
for (const tracePath of traceabilityPaths) {
|
|
279
|
+
if (await fs.pathExists(tracePath)) {
|
|
280
|
+
traceabilityFound = true;
|
|
281
|
+
result.warnings.push('Article V: Traceability matrix found');
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!traceabilityFound) {
|
|
287
|
+
result.warnings.push('Article V: No traceability matrix - create when requirements exist');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
result.summary = 'Article V: Traceability - Create matrix when requirements are defined';
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Article VI: Project Memory (Steering System)
|
|
296
|
+
*/
|
|
297
|
+
async validateArticle6() {
|
|
298
|
+
const result = {
|
|
299
|
+
article: 6,
|
|
300
|
+
name: 'Project Memory (Steering System)',
|
|
301
|
+
passed: true,
|
|
302
|
+
violations: [],
|
|
303
|
+
warnings: [],
|
|
304
|
+
summary: ''
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Check for steering files
|
|
308
|
+
const requiredFiles = [
|
|
309
|
+
'structure.md',
|
|
310
|
+
'tech.md',
|
|
311
|
+
'product.md'
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
const missingFiles = [];
|
|
315
|
+
for (const file of requiredFiles) {
|
|
316
|
+
const filePath = path.join(this.steeringPath, file);
|
|
317
|
+
if (!await fs.pathExists(filePath)) {
|
|
318
|
+
missingFiles.push(file);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (missingFiles.length > 0) {
|
|
323
|
+
result.passed = false;
|
|
324
|
+
result.violations.push(`Article VI: Missing steering files: ${missingFiles.join(', ')}`);
|
|
325
|
+
} else {
|
|
326
|
+
result.warnings.push('Article VI: All steering files present');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check for project.yml
|
|
330
|
+
const projectYmlPath = path.join(this.steeringPath, 'project.yml');
|
|
331
|
+
if (await fs.pathExists(projectYmlPath)) {
|
|
332
|
+
result.warnings.push('Article VI: project.yml found');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
result.summary = missingFiles.length === 0
|
|
336
|
+
? 'Article VI: All steering files present'
|
|
337
|
+
: `Article VI: Missing ${missingFiles.length} steering files`;
|
|
338
|
+
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Article VII: Simplicity Gate (≤3 sub-projects)
|
|
344
|
+
*/
|
|
345
|
+
async validateArticle7() {
|
|
346
|
+
const result = {
|
|
347
|
+
article: 7,
|
|
348
|
+
name: 'Simplicity Gate',
|
|
349
|
+
passed: true,
|
|
350
|
+
violations: [],
|
|
351
|
+
warnings: [],
|
|
352
|
+
summary: ''
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Count independently deployable projects (directories with package.json)
|
|
356
|
+
const packageJsonFiles = await glob('**/package.json', {
|
|
357
|
+
cwd: this.projectRoot,
|
|
358
|
+
ignore: ['**/node_modules/**']
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const projectCount = packageJsonFiles.length;
|
|
362
|
+
|
|
363
|
+
if (projectCount > 3) {
|
|
364
|
+
result.passed = false;
|
|
365
|
+
result.violations.push(`Article VII: ${projectCount} sub-projects detected (limit: 3) - Phase -1 Gate approval required`);
|
|
366
|
+
} else {
|
|
367
|
+
result.warnings.push(`Article VII: ${projectCount} sub-project(s) detected - within Simplicity Gate limit`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
result.summary = `Article VII: ${projectCount} sub-project(s) ${projectCount > 3 ? '(EXCEEDS LIMIT)' : '(within limit)'}`;
|
|
371
|
+
return result;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Article VIII: Anti-Abstraction Gate
|
|
376
|
+
*/
|
|
377
|
+
async validateArticle8() {
|
|
378
|
+
const result = {
|
|
379
|
+
article: 8,
|
|
380
|
+
name: 'Anti-Abstraction Gate',
|
|
381
|
+
passed: true,
|
|
382
|
+
violations: [],
|
|
383
|
+
warnings: [],
|
|
384
|
+
summary: ''
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Detect potential abstraction layers (common patterns)
|
|
388
|
+
const abstractionPatterns = [
|
|
389
|
+
'**/lib/*wrapper*.js',
|
|
390
|
+
'**/lib/*abstraction*.js',
|
|
391
|
+
'**/src/*wrapper*.js',
|
|
392
|
+
'**/src/*facade*.js',
|
|
393
|
+
'**/adapters/**/*.js'
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
let abstractionCount = 0;
|
|
397
|
+
for (const pattern of abstractionPatterns) {
|
|
398
|
+
const files = await glob(pattern, { cwd: this.projectRoot });
|
|
399
|
+
abstractionCount += files.length;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (abstractionCount > 0) {
|
|
403
|
+
result.warnings.push(`Article VIII: ${abstractionCount} potential abstraction layers detected - verify necessity`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
result.summary = abstractionCount > 0
|
|
407
|
+
? `Article VIII: ${abstractionCount} potential abstractions - manual review needed`
|
|
408
|
+
: 'Article VIII: No obvious abstraction layers detected';
|
|
409
|
+
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Article IX: Integration-First Testing
|
|
415
|
+
*/
|
|
416
|
+
async validateArticle9() {
|
|
417
|
+
const result = {
|
|
418
|
+
article: 9,
|
|
419
|
+
name: 'Integration-First Testing',
|
|
420
|
+
passed: true,
|
|
421
|
+
violations: [],
|
|
422
|
+
warnings: [],
|
|
423
|
+
summary: ''
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// Check for integration test files
|
|
427
|
+
const integrationPatterns = [
|
|
428
|
+
'**/test/integration/**/*.js',
|
|
429
|
+
'**/tests/integration/**/*.js',
|
|
430
|
+
'**/*.integration.test.js',
|
|
431
|
+
'**/*.integration.spec.js'
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
let integrationTestCount = 0;
|
|
435
|
+
for (const pattern of integrationPatterns) {
|
|
436
|
+
const files = await glob(pattern, { cwd: this.projectRoot });
|
|
437
|
+
integrationTestCount += files.length;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (integrationTestCount > 0) {
|
|
441
|
+
result.warnings.push(`Article IX: ${integrationTestCount} integration test files found - verify real services usage`);
|
|
442
|
+
} else {
|
|
443
|
+
result.warnings.push('Article IX: No integration tests found - add when integrating external services');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
result.summary = integrationTestCount > 0
|
|
447
|
+
? `Article IX: ${integrationTestCount} integration tests found`
|
|
448
|
+
: 'Article IX: No integration tests - OK for early development';
|
|
449
|
+
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Validate Phase -1 Gates
|
|
455
|
+
*/
|
|
456
|
+
async validateGates() {
|
|
457
|
+
const results = {
|
|
458
|
+
passed: true,
|
|
459
|
+
violations: [],
|
|
460
|
+
warnings: [],
|
|
461
|
+
gates: {}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// Simplicity Gate (Article VII)
|
|
465
|
+
const simplicityGate = await this.validateArticle7();
|
|
466
|
+
results.gates.simplicity = simplicityGate;
|
|
467
|
+
if (!simplicityGate.passed) {
|
|
468
|
+
results.passed = false;
|
|
469
|
+
results.violations.push(...simplicityGate.violations);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Anti-Abstraction Gate (Article VIII)
|
|
473
|
+
const abstractionGate = await this.validateArticle8();
|
|
474
|
+
results.gates.abstraction = abstractionGate;
|
|
475
|
+
results.warnings.push(...abstractionGate.warnings);
|
|
476
|
+
|
|
477
|
+
results.summary = results.passed
|
|
478
|
+
? 'All Phase -1 Gates passed'
|
|
479
|
+
: 'Phase -1 Gate violations detected';
|
|
480
|
+
|
|
481
|
+
return results;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Validate complexity limits
|
|
486
|
+
*/
|
|
487
|
+
async validateComplexity() {
|
|
488
|
+
const results = {
|
|
489
|
+
passed: true,
|
|
490
|
+
violations: [],
|
|
491
|
+
warnings: [],
|
|
492
|
+
files: []
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// Find all source files
|
|
496
|
+
const sourcePatterns = [
|
|
497
|
+
'src/**/*.js',
|
|
498
|
+
'lib/**/*.js',
|
|
499
|
+
'bin/**/*.js'
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
const files = [];
|
|
503
|
+
for (const pattern of sourcePatterns) {
|
|
504
|
+
const matches = await glob(pattern, { cwd: this.projectRoot });
|
|
505
|
+
files.push(...matches.map(f => path.join(this.projectRoot, f)));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Check module line counts (≤1500 lines)
|
|
509
|
+
for (const file of files) {
|
|
510
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
511
|
+
const lines = content.split('\n').length;
|
|
512
|
+
|
|
513
|
+
if (lines > 1500) {
|
|
514
|
+
results.passed = false;
|
|
515
|
+
results.violations.push(`${path.relative(this.projectRoot, file)}: ${lines} lines (limit: 1500)`);
|
|
516
|
+
} else if (lines > 1000) {
|
|
517
|
+
results.warnings.push(`${path.relative(this.projectRoot, file)}: ${lines} lines (approaching limit)`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
results.files.push({
|
|
521
|
+
file: path.relative(this.projectRoot, file),
|
|
522
|
+
lines,
|
|
523
|
+
exceeds: lines > 1500
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
results.summary = results.passed
|
|
528
|
+
? `${files.length} files analyzed - all within complexity limits`
|
|
529
|
+
: `${results.violations.length} files exceed 1500 line limit`;
|
|
530
|
+
|
|
531
|
+
return results;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
module.exports = ConstitutionValidator;
|