musubi-sdd 0.8.5 → 0.8.7

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
@@ -15,7 +15,8 @@ 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
+ - 🔍 **ギャップ検出システム** - 孤立した要件とテストされていないコードを特定(v0.8.7)
19
20
  - 🧭 **自動更新プロジェクトメモリ** - ステアリングシステムがアーキテクチャ、技術スタック、製品コンテキストを維持
20
21
  - 🚀 **自動オンボーディング** - `musubi-onboard` が既存プロジェクトを分析し、ステアリングドキュメントを生成(2-5分)
21
22
  - 🔄 **自動同期** - `musubi-sync` がコードベースの変更を検出し、ステアリングドキュメントを最新に保つ
@@ -138,6 +139,24 @@ musubi-trace coverage --min-coverage 100 # 100%カバレッジ要求
138
139
  musubi-trace gaps # 孤立した要件/コード検出
139
140
  musubi-trace requirement REQ-AUTH-001 # 特定要件をトレース
140
141
  musubi-trace validate # 100%トレーサビリティ検証(第5条)
142
+
143
+ # ブラウンフィールドプロジェクト向け変更管理(v0.8.6)
144
+ musubi-change init CHANGE-001 --title "認証機能追加" # 変更提案を作成
145
+ musubi-change validate CHANGE-001 --verbose # 差分仕様を検証
146
+ musubi-change apply CHANGE-001 --dry-run # 変更をプレビュー
147
+ musubi-change apply CHANGE-001 # コードベースに変更を適用
148
+ musubi-change archive CHANGE-001 # specs/にアーカイブ
149
+ musubi-change list --status pending # 保留中の変更をリスト
150
+ musubi-change list --format json # JSON形式でリスト
151
+
152
+ # ギャップ検出とカバレッジ検証(v0.8.7)
153
+ musubi-gaps detect # 全ギャップを検出
154
+ musubi-gaps detect --verbose # 詳細なギャップ情報を表示
155
+ musubi-gaps requirements # 孤立した要件を検出
156
+ musubi-gaps code # テストされていないコードを検出
157
+ musubi-gaps coverage # カバレッジ統計を計算
158
+ musubi-gaps coverage --min-coverage 100 # 100%カバレッジを要求
159
+ musubi-gaps detect --format markdown > gaps.md # ギャップレポートをエクスポート
141
160
  ```
142
161
 
143
162
  ### プロジェクトタイプ
package/README.md CHANGED
@@ -19,7 +19,8 @@ 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
- - 🔄 **Delta Specifications** - Brownfield and greenfield project support
22
+ - 🔄 **Change Management System** - Delta specifications for brownfield projects (v0.8.6)
23
+ - 🔍 **Gap Detection System** - Identify orphaned requirements and untested code (v0.8.7)
23
24
  - 🧭 **Auto-Updating Project Memory** - Steering system maintains architecture, tech stack, and product context
24
25
  - 🚀 **Automatic Onboarding** - `musubi-onboard` analyzes existing projects and generates steering docs (2-5 minutes)
25
26
  - 🔄 **Auto-Sync** - `musubi-sync` detects codebase changes and keeps steering docs current
@@ -142,6 +143,24 @@ musubi-trace coverage --min-coverage 100 # Require 100% coverage
142
143
  musubi-trace gaps # Detect orphaned requirements/code
143
144
  musubi-trace requirement REQ-AUTH-001 # Trace specific requirement
144
145
  musubi-trace validate # Validate 100% traceability (Article V)
146
+
147
+ # Change management for brownfield projects (v0.8.6)
148
+ musubi-change init CHANGE-001 --title "Add authentication" # Create change proposal
149
+ musubi-change validate CHANGE-001 --verbose # Validate delta specification
150
+ musubi-change apply CHANGE-001 --dry-run # Preview changes
151
+ musubi-change apply CHANGE-001 # Apply changes to codebase
152
+ musubi-change archive CHANGE-001 # Archive to specs/
153
+ musubi-change list --status pending # List pending changes
154
+ musubi-change list --format json # List in JSON format
155
+
156
+ # Gap detection and coverage validation (v0.8.7)
157
+ musubi-gaps detect # Detect all gaps
158
+ musubi-gaps detect --verbose # Show detailed gap information
159
+ musubi-gaps requirements # Detect orphaned requirements
160
+ musubi-gaps code # Detect untested code
161
+ musubi-gaps coverage # Calculate coverage statistics
162
+ musubi-gaps coverage --min-coverage 100 # Require 100% coverage
163
+ musubi-gaps detect --format markdown > gaps.md # Export gap report
145
164
  ```
146
165
 
147
166
  ### 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();
File without changes
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MUSUBI Gap Detection CLI
5
+ * Identifies orphaned requirements, untested code, and missing traceability
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const chalk = require('chalk');
10
+ const GapDetector = require('../src/analyzers/gap-detector');
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('musubi-gaps')
16
+ .description('Detect gaps in requirements, code, and test coverage')
17
+ .version('0.8.7');
18
+
19
+ program
20
+ .command('detect')
21
+ .description('Detect all gaps (requirements, code, tests)')
22
+ .option('--requirements <dir>', 'Requirements directory', 'docs/requirements')
23
+ .option('--design <dir>', 'Design directory', 'docs/design')
24
+ .option('--tasks <dir>', 'Tasks directory', 'docs/tasks')
25
+ .option('--src <dir>', 'Source code directory', 'src')
26
+ .option('--tests <dir>', 'Test directory', 'tests')
27
+ .option('--format <format>', 'Output format (table|json|markdown)', 'table')
28
+ .option('--verbose', 'Show detailed gap information')
29
+ .action(async (options) => {
30
+ try {
31
+ console.log(chalk.blue('\n🔍 Detecting gaps in traceability...\n'));
32
+
33
+ const detector = new GapDetector({
34
+ requirementsDir: options.requirements,
35
+ designDir: options.design,
36
+ tasksDir: options.tasks,
37
+ srcDir: options.src,
38
+ testsDir: options.tests
39
+ });
40
+
41
+ const gaps = await detector.detectAllGaps();
42
+
43
+ if (options.format === 'json') {
44
+ console.log(JSON.stringify(gaps, null, 2));
45
+ } else if (options.format === 'markdown') {
46
+ console.log(detector.formatMarkdown(gaps));
47
+ } else {
48
+ detector.displayTable(gaps, options.verbose);
49
+ }
50
+
51
+ const totalGaps = gaps.orphanedRequirements.length +
52
+ gaps.unimplementedRequirements.length +
53
+ gaps.untestedCode.length +
54
+ gaps.missingTests.length;
55
+
56
+ if (totalGaps === 0) {
57
+ console.log(chalk.green('\n✓ No gaps detected! 100% traceability achieved.\n'));
58
+ process.exit(0);
59
+ } else {
60
+ console.log(chalk.yellow(`\n⚠ Found ${totalGaps} gap(s). See details above.\n`));
61
+ process.exit(1);
62
+ }
63
+ } catch (error) {
64
+ console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
65
+ process.exit(1);
66
+ }
67
+ });
68
+
69
+ program
70
+ .command('requirements')
71
+ .description('Detect orphaned requirements (no design/code)')
72
+ .option('--requirements <dir>', 'Requirements directory', 'docs/requirements')
73
+ .option('--design <dir>', 'Design directory', 'docs/design')
74
+ .option('--tasks <dir>', 'Tasks directory', 'docs/tasks')
75
+ .option('--format <format>', 'Output format (table|json|markdown)', 'table')
76
+ .action(async (options) => {
77
+ try {
78
+ console.log(chalk.blue('\n🔍 Detecting orphaned requirements...\n'));
79
+
80
+ const detector = new GapDetector({
81
+ requirementsDir: options.requirements,
82
+ designDir: options.design,
83
+ tasksDir: options.tasks
84
+ });
85
+
86
+ const orphaned = await detector.detectOrphanedRequirements();
87
+
88
+ if (options.format === 'json') {
89
+ console.log(JSON.stringify(orphaned, null, 2));
90
+ } else if (options.format === 'markdown') {
91
+ console.log(detector.formatRequirementsMarkdown(orphaned));
92
+ } else {
93
+ detector.displayRequirementsTable(orphaned);
94
+ }
95
+
96
+ if (orphaned.length === 0) {
97
+ console.log(chalk.green('\n✓ No orphaned requirements detected.\n'));
98
+ process.exit(0);
99
+ } else {
100
+ console.log(chalk.yellow(`\n⚠ Found ${orphaned.length} orphaned requirement(s).\n`));
101
+ process.exit(1);
102
+ }
103
+ } catch (error) {
104
+ console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
105
+ process.exit(1);
106
+ }
107
+ });
108
+
109
+ program
110
+ .command('code')
111
+ .description('Detect untested code')
112
+ .option('--src <dir>', 'Source code directory', 'src')
113
+ .option('--tests <dir>', 'Test directory', 'tests')
114
+ .option('--format <format>', 'Output format (table|json|markdown)', 'table')
115
+ .action(async (options) => {
116
+ try {
117
+ console.log(chalk.blue('\n🔍 Detecting untested code...\n'));
118
+
119
+ const detector = new GapDetector({
120
+ srcDir: options.src,
121
+ testsDir: options.tests
122
+ });
123
+
124
+ const untested = await detector.detectUntestedCode();
125
+
126
+ if (options.format === 'json') {
127
+ console.log(JSON.stringify(untested, null, 2));
128
+ } else if (options.format === 'markdown') {
129
+ console.log(detector.formatCodeMarkdown(untested));
130
+ } else {
131
+ detector.displayCodeTable(untested);
132
+ }
133
+
134
+ if (untested.length === 0) {
135
+ console.log(chalk.green('\n✓ All code is tested.\n'));
136
+ process.exit(0);
137
+ } else {
138
+ console.log(chalk.yellow(`\n⚠ Found ${untested.length} untested file(s).\n`));
139
+ process.exit(1);
140
+ }
141
+ } catch (error) {
142
+ console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
143
+ process.exit(1);
144
+ }
145
+ });
146
+
147
+ program
148
+ .command('coverage')
149
+ .description('Calculate coverage statistics')
150
+ .option('--requirements <dir>', 'Requirements directory', 'docs/requirements')
151
+ .option('--design <dir>', 'Design directory', 'docs/design')
152
+ .option('--tasks <dir>', 'Tasks directory', 'docs/tasks')
153
+ .option('--src <dir>', 'Source code directory', 'src')
154
+ .option('--tests <dir>', 'Test directory', 'tests')
155
+ .option('--min-coverage <percent>', 'Minimum required coverage', '100')
156
+ .option('--format <format>', 'Output format (table|json|markdown)', 'table')
157
+ .action(async (options) => {
158
+ try {
159
+ console.log(chalk.blue('\n📊 Calculating coverage statistics...\n'));
160
+
161
+ const detector = new GapDetector({
162
+ requirementsDir: options.requirements,
163
+ designDir: options.design,
164
+ tasksDir: options.tasks,
165
+ srcDir: options.src,
166
+ testsDir: options.tests
167
+ });
168
+
169
+ const coverage = await detector.calculateCoverage();
170
+ const minCoverage = parseFloat(options.minCoverage);
171
+
172
+ if (options.format === 'json') {
173
+ console.log(JSON.stringify(coverage, null, 2));
174
+ } else if (options.format === 'markdown') {
175
+ console.log(detector.formatCoverageMarkdown(coverage));
176
+ } else {
177
+ detector.displayCoverageTable(coverage);
178
+ }
179
+
180
+ const avgCoverage = (
181
+ coverage.requirements.implementationCoverage +
182
+ coverage.requirements.testCoverage +
183
+ coverage.code.testCoverage
184
+ ) / 3;
185
+
186
+ if (avgCoverage >= minCoverage) {
187
+ console.log(chalk.green(`\n✓ Coverage ${avgCoverage.toFixed(1)}% meets minimum ${minCoverage}%\n`));
188
+ process.exit(0);
189
+ } else {
190
+ console.log(chalk.red(`\n✗ Coverage ${avgCoverage.toFixed(1)}% below minimum ${minCoverage}%\n`));
191
+ process.exit(1);
192
+ }
193
+ } catch (error) {
194
+ console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
195
+ process.exit(1);
196
+ }
197
+ });
198
+
199
+ program.parse(process.argv);
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musubi-sdd",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
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": {
@@ -15,7 +15,9 @@
15
15
  "musubi-requirements": "bin/musubi-requirements.js",
16
16
  "musubi-design": "bin/musubi-design.js",
17
17
  "musubi-tasks": "bin/musubi-tasks.js",
18
- "musubi-trace": "bin/musubi-trace.js"
18
+ "musubi-trace": "bin/musubi-trace.js",
19
+ "musubi-gaps": "bin/musubi-gaps.js",
20
+ "musubi-change": "bin/musubi-change.js"
19
21
  },
20
22
  "scripts": {
21
23
  "test": "jest",