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 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.6.1",
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;