musubi-sdd 6.2.1 → 6.2.2

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.
@@ -21,7 +21,7 @@ const { DashboardCLI } = require('../src/cli/dashboard-cli');
21
21
  const args = process.argv.slice(2);
22
22
  const command = args[0];
23
23
  const subcommand = args[1];
24
- const feature = args[2] || args[1];
24
+ const _feature = args[2] || args[1];
25
25
 
26
26
  // Parse options
27
27
  const options = {};
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MUSUBI Upgrade Script
5
+ *
6
+ * Upgrades existing MUSUBI projects to newer versions:
7
+ * - Updates steering files
8
+ * - Adds new prompts/skills
9
+ * - Migrates configuration
10
+ * - Preserves user customizations
11
+ */
12
+
13
+ const fs = require('fs-extra');
14
+ const path = require('path');
15
+ const chalk = require('chalk');
16
+ const { Command } = require('commander');
17
+
18
+ const program = new Command();
19
+ const packageJson = require('../package.json');
20
+
21
+ // Version migration definitions
22
+ const MIGRATIONS = {
23
+ '6.2.0': {
24
+ description: 'Review Gate Engine, Dashboard, Traceability',
25
+ changes: [
26
+ 'Add Review Gate prompts to AGENTS.md',
27
+ 'Create storage/reviews/, storage/dashboard/, storage/traceability/ directories',
28
+ 'Update steering/project.yml with reviewGate settings',
29
+ ],
30
+ migrate: migrateToV620,
31
+ },
32
+ '6.2.1': {
33
+ description: 'Bug fixes and improvements',
34
+ changes: ['Minor fixes'],
35
+ migrate: migrateToV621,
36
+ },
37
+ };
38
+
39
+ // ============================================================================
40
+ // Migration Functions
41
+ // ============================================================================
42
+
43
+ async function migrateToV620(projectDir, _options) {
44
+ const results = { success: [], failed: [], skipped: [] };
45
+
46
+ // 1. Create new storage directories
47
+ const newDirs = [
48
+ 'storage/reviews',
49
+ 'storage/dashboard',
50
+ 'storage/traceability',
51
+ 'storage/transitions',
52
+ ];
53
+
54
+ for (const dir of newDirs) {
55
+ const dirPath = path.join(projectDir, dir);
56
+ try {
57
+ if (!fs.existsSync(dirPath)) {
58
+ await fs.ensureDir(dirPath);
59
+ await fs.writeFile(path.join(dirPath, '.gitkeep'), '');
60
+ results.success.push(`Created ${dir}/`);
61
+ } else {
62
+ results.skipped.push(`${dir}/ already exists`);
63
+ }
64
+ } catch (err) {
65
+ results.failed.push(`Failed to create ${dir}/: ${err.message}`);
66
+ }
67
+ }
68
+
69
+ // 2. Update AGENTS.md with new review prompts
70
+ const agentsPath = path.join(projectDir, 'AGENTS.md');
71
+ if (fs.existsSync(agentsPath)) {
72
+ try {
73
+ let content = await fs.readFile(agentsPath, 'utf8');
74
+
75
+ const reviewPrompts = `
76
+ ### Review Gate Prompts (v6.2.0)
77
+
78
+ - \`#sdd-review-requirements <feature>\` - Review requirements (EARS, stakeholders, acceptance criteria)
79
+ - \`#sdd-review-design <feature>\` - Review design (C4, ADR, Constitutional Articles)
80
+ - \`#sdd-review-implementation <feature>\` - Review implementation (coverage, lint, traceability)
81
+ - \`#sdd-review-all <feature>\` - Full review cycle for all phases
82
+ `;
83
+
84
+ if (!content.includes('#sdd-review-requirements')) {
85
+ // Find a good insertion point (after existing prompts section)
86
+ const promptsIndex = content.indexOf('### Prompts');
87
+ if (promptsIndex !== -1) {
88
+ const nextSectionIndex = content.indexOf('###', promptsIndex + 10);
89
+ if (nextSectionIndex !== -1) {
90
+ content =
91
+ content.slice(0, nextSectionIndex) + reviewPrompts + '\n' + content.slice(nextSectionIndex);
92
+ } else {
93
+ content += '\n' + reviewPrompts;
94
+ }
95
+ } else {
96
+ content += '\n' + reviewPrompts;
97
+ }
98
+
99
+ await fs.writeFile(agentsPath, content);
100
+ results.success.push('Added Review Gate prompts to AGENTS.md');
101
+ } else {
102
+ results.skipped.push('Review Gate prompts already in AGENTS.md');
103
+ }
104
+ } catch (err) {
105
+ results.failed.push(`Failed to update AGENTS.md: ${err.message}`);
106
+ }
107
+ } else {
108
+ results.skipped.push('AGENTS.md not found (not a MUSUBI project?)');
109
+ }
110
+
111
+ // 3. Update steering/project.yml with reviewGate settings
112
+ const projectYmlPath = path.join(projectDir, 'steering', 'project.yml');
113
+ if (fs.existsSync(projectYmlPath)) {
114
+ try {
115
+ let content = await fs.readFile(projectYmlPath, 'utf8');
116
+
117
+ const reviewGateConfig = `
118
+ # Review Gate Settings (v6.2.0)
119
+ reviewGate:
120
+ requirements:
121
+ earsCheck: true
122
+ stakeholderCoverage: true
123
+ acceptanceCriteriaRequired: true
124
+ design:
125
+ c4Required: ['context', 'container', 'component']
126
+ adrRequired: true
127
+ constitutionalArticles: [1, 2, 7, 8]
128
+ implementation:
129
+ minCoverage: 80
130
+ coverageType: 'line'
131
+ lintStrict: true
132
+
133
+ # Traceability Settings (v6.2.0)
134
+ traceability:
135
+ patterns:
136
+ - 'REQ-[A-Z0-9]+-\\\\d{3}'
137
+ - 'IMP-\\\\d+\\\\.\\\\d+-\\\\d{3}(?:-\\\\d{2})?'
138
+ extractFrom:
139
+ - 'src/**/*.{ts,js}'
140
+ - 'tests/**/*.test.{ts,js}'
141
+ outputPath: 'storage/traceability/matrix.yml'
142
+ `;
143
+
144
+ if (!content.includes('reviewGate:')) {
145
+ content += '\n' + reviewGateConfig;
146
+ await fs.writeFile(projectYmlPath, content);
147
+ results.success.push('Added reviewGate settings to steering/project.yml');
148
+ } else {
149
+ results.skipped.push('reviewGate settings already in steering/project.yml');
150
+ }
151
+ } catch (err) {
152
+ results.failed.push(`Failed to update steering/project.yml: ${err.message}`);
153
+ }
154
+ } else {
155
+ results.skipped.push('steering/project.yml not found');
156
+ }
157
+
158
+ // 4. Create version marker
159
+ const versionPath = path.join(projectDir, 'steering', '.musubi-version');
160
+ try {
161
+ await fs.writeFile(versionPath, '6.2.0\n');
162
+ results.success.push('Created version marker');
163
+ } catch (err) {
164
+ results.failed.push(`Failed to create version marker: ${err.message}`);
165
+ }
166
+
167
+ return results;
168
+ }
169
+
170
+ async function migrateToV621(projectDir, _options) {
171
+ const results = { success: [], failed: [], skipped: [] };
172
+
173
+ // v6.2.1 is mainly bug fixes, just update version marker
174
+ const versionPath = path.join(projectDir, 'steering', '.musubi-version');
175
+ try {
176
+ await fs.writeFile(versionPath, '6.2.1\n');
177
+ results.success.push('Updated version marker to 6.2.1');
178
+ } catch (err) {
179
+ results.failed.push(`Failed to update version marker: ${err.message}`);
180
+ }
181
+
182
+ return results;
183
+ }
184
+
185
+ // ============================================================================
186
+ // Utility Functions
187
+ // ============================================================================
188
+
189
+ function getCurrentVersion(projectDir) {
190
+ const versionPath = path.join(projectDir, 'steering', '.musubi-version');
191
+ if (fs.existsSync(versionPath)) {
192
+ return fs.readFileSync(versionPath, 'utf8').trim();
193
+ }
194
+
195
+ // Fallback: check if MUSUBI is initialized
196
+ const steeringDir = path.join(projectDir, 'steering');
197
+ if (fs.existsSync(steeringDir)) {
198
+ return '6.0.0'; // Assume pre-version-tracking
199
+ }
200
+
201
+ return null;
202
+ }
203
+
204
+ function compareVersions(v1, v2) {
205
+ const parts1 = v1.split('.').map(Number);
206
+ const parts2 = v2.split('.').map(Number);
207
+
208
+ for (let i = 0; i < 3; i++) {
209
+ if (parts1[i] > parts2[i]) return 1;
210
+ if (parts1[i] < parts2[i]) return -1;
211
+ }
212
+ return 0;
213
+ }
214
+
215
+ function getMigrationPath(fromVersion, toVersion) {
216
+ const versions = Object.keys(MIGRATIONS).sort(compareVersions);
217
+ const path = [];
218
+
219
+ for (const version of versions) {
220
+ if (compareVersions(version, fromVersion) > 0 && compareVersions(version, toVersion) <= 0) {
221
+ path.push(version);
222
+ }
223
+ }
224
+
225
+ return path;
226
+ }
227
+
228
+ // ============================================================================
229
+ // CLI Commands
230
+ // ============================================================================
231
+
232
+ program
233
+ .name('musubi-upgrade')
234
+ .description('Upgrade MUSUBI project to a newer version')
235
+ .version(packageJson.version)
236
+ .option('--to <version>', 'Target version to upgrade to', 'latest')
237
+ .option('--dry-run', 'Preview changes without applying')
238
+ .option('--force', 'Force upgrade even if already at target version')
239
+ .action(async options => {
240
+ const projectDir = process.cwd();
241
+
242
+ console.log(chalk.blue.bold('\n🔄 MUSUBI Upgrade\n'));
243
+
244
+ // Check if MUSUBI is initialized
245
+ const currentVersion = getCurrentVersion(projectDir);
246
+ if (!currentVersion) {
247
+ console.log(chalk.red('❌ MUSUBI is not initialized in this directory.'));
248
+ console.log(chalk.gray('\nRun: npx musubi-sdd init\n'));
249
+ process.exit(1);
250
+ }
251
+
252
+ // Determine target version
253
+ let targetVersion = options.to;
254
+ if (targetVersion === 'latest') {
255
+ targetVersion = packageJson.version;
256
+ }
257
+
258
+ // Validate target version
259
+ const availableVersions = Object.keys(MIGRATIONS);
260
+ if (!availableVersions.includes(targetVersion) && targetVersion !== packageJson.version) {
261
+ console.log(chalk.red(`❌ Unknown target version: ${targetVersion}`));
262
+ console.log(chalk.gray(`\nAvailable versions: ${availableVersions.join(', ')}\n`));
263
+ process.exit(1);
264
+ }
265
+
266
+ console.log(chalk.white(`Current version: ${chalk.yellow(currentVersion)}`));
267
+ console.log(chalk.white(`Target version: ${chalk.green(targetVersion)}`));
268
+
269
+ // Check if upgrade is needed
270
+ const comparison = compareVersions(currentVersion, targetVersion);
271
+ if (comparison >= 0 && !options.force) {
272
+ console.log(chalk.green('\n✅ Already at target version or newer.\n'));
273
+ console.log(chalk.gray('Use --force to re-run migrations.\n'));
274
+ process.exit(0);
275
+ }
276
+
277
+ // Get migration path
278
+ const migrationPath = getMigrationPath(currentVersion, targetVersion);
279
+
280
+ if (migrationPath.length === 0) {
281
+ console.log(chalk.yellow('\n⚠️ No migrations needed.\n'));
282
+ process.exit(0);
283
+ }
284
+
285
+ console.log(chalk.white(`\nMigrations to apply: ${migrationPath.join(' → ')}\n`));
286
+
287
+ // Show migration details
288
+ for (const version of migrationPath) {
289
+ const migration = MIGRATIONS[version];
290
+ console.log(chalk.cyan(`📦 v${version}: ${migration.description}`));
291
+ for (const change of migration.changes) {
292
+ console.log(chalk.gray(` - ${change}`));
293
+ }
294
+ }
295
+
296
+ if (options.dryRun) {
297
+ console.log(chalk.yellow('\n🔍 Dry run mode - no changes applied.\n'));
298
+ process.exit(0);
299
+ }
300
+
301
+ // Confirm upgrade
302
+ console.log('');
303
+
304
+ // Apply migrations
305
+ let totalSuccess = 0;
306
+ let totalFailed = 0;
307
+ let totalSkipped = 0;
308
+
309
+ for (const version of migrationPath) {
310
+ console.log(chalk.blue(`\n📦 Applying migration to v${version}...\n`));
311
+
312
+ const migration = MIGRATIONS[version];
313
+ const results = await migration.migrate(projectDir, options);
314
+
315
+ for (const msg of results.success) {
316
+ console.log(chalk.green(` ✅ ${msg}`));
317
+ totalSuccess++;
318
+ }
319
+ for (const msg of results.skipped) {
320
+ console.log(chalk.yellow(` ⏭️ ${msg}`));
321
+ totalSkipped++;
322
+ }
323
+ for (const msg of results.failed) {
324
+ console.log(chalk.red(` ❌ ${msg}`));
325
+ totalFailed++;
326
+ }
327
+ }
328
+
329
+ // Summary
330
+ console.log(chalk.blue.bold('\n📊 Upgrade Summary\n'));
331
+ console.log(chalk.green(` ✅ Success: ${totalSuccess}`));
332
+ console.log(chalk.yellow(` ⏭️ Skipped: ${totalSkipped}`));
333
+ console.log(chalk.red(` ❌ Failed: ${totalFailed}`));
334
+
335
+ if (totalFailed > 0) {
336
+ console.log(chalk.red('\n⚠️ Some migrations failed. Please check the errors above.\n'));
337
+ process.exit(1);
338
+ }
339
+
340
+ console.log(chalk.green(`\n✅ Successfully upgraded to v${targetVersion}!\n`));
341
+ });
342
+
343
+ // List available migrations
344
+ program
345
+ .command('list')
346
+ .description('List available migrations')
347
+ .action(() => {
348
+ console.log(chalk.blue.bold('\n📋 Available MUSUBI Migrations\n'));
349
+
350
+ const versions = Object.keys(MIGRATIONS).sort(compareVersions);
351
+ for (const version of versions) {
352
+ const migration = MIGRATIONS[version];
353
+ console.log(chalk.cyan(`v${version}: ${migration.description}`));
354
+ for (const change of migration.changes) {
355
+ console.log(chalk.gray(` - ${change}`));
356
+ }
357
+ console.log('');
358
+ }
359
+
360
+ console.log(chalk.white(`Current package version: ${packageJson.version}\n`));
361
+ });
362
+
363
+ // Check current version
364
+ program
365
+ .command('check')
366
+ .description('Check current project version')
367
+ .action(() => {
368
+ const projectDir = process.cwd();
369
+ const currentVersion = getCurrentVersion(projectDir);
370
+
371
+ console.log(chalk.blue.bold('\n🔍 MUSUBI Version Check\n'));
372
+
373
+ if (!currentVersion) {
374
+ console.log(chalk.red('❌ MUSUBI is not initialized in this directory.\n'));
375
+ process.exit(1);
376
+ }
377
+
378
+ console.log(chalk.white(`Project version: ${chalk.yellow(currentVersion)}`));
379
+ console.log(chalk.white(`Package version: ${chalk.green(packageJson.version)}`));
380
+
381
+ const comparison = compareVersions(currentVersion, packageJson.version);
382
+ if (comparison < 0) {
383
+ console.log(chalk.yellow(`\n⚠️ Upgrade available: ${currentVersion} → ${packageJson.version}`));
384
+ console.log(chalk.gray('\nRun: npx musubi-sdd upgrade\n'));
385
+ } else {
386
+ console.log(chalk.green('\n✅ Project is up to date.\n'));
387
+ }
388
+ });
389
+
390
+ program.parse(process.argv);
391
+
392
+ // Show help if no arguments
393
+ if (!process.argv.slice(2).length) {
394
+ program.outputHelp();
395
+ }
package/bin/musubi.js CHANGED
@@ -392,6 +392,21 @@ program
392
392
  }
393
393
  });
394
394
 
395
+ // ============================================================================
396
+ // Command: upgrade
397
+ // ============================================================================
398
+ program
399
+ .command('upgrade')
400
+ .description('Upgrade MUSUBI project to a newer version')
401
+ .option('--to <version>', 'Target version to upgrade to', 'latest')
402
+ .option('--dry-run', 'Preview changes without applying')
403
+ .option('--force', 'Force upgrade even if already at target version')
404
+ .action(async _options => {
405
+ // Delegate to musubi-upgrade.js
406
+ process.argv = ['node', 'musubi-upgrade', ...process.argv.slice(3)];
407
+ require('./musubi-upgrade.js');
408
+ });
409
+
395
410
  // ============================================================================
396
411
  // Command: info
397
412
  // ============================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musubi-sdd",
3
- "version": "6.2.1",
3
+ "version": "6.2.2",
4
4
  "description": "Ultimate Specification Driven Development Tool with 27 Agents for 7 AI Coding Platforms + MCP Integration (Claude Code, GitHub Copilot, Cursor, Gemini CLI, Windsurf, Codex, Qwen Code)",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -26,7 +26,8 @@
26
26
  "musubi-gui": "bin/musubi-gui.js",
27
27
  "musubi-orchestrate": "bin/musubi-orchestrate.js",
28
28
  "musubi-release": "bin/musubi-release.js",
29
- "musubi-config": "bin/musubi-config.js"
29
+ "musubi-config": "bin/musubi-config.js",
30
+ "musubi-upgrade": "bin/musubi-upgrade.js"
30
31
  },
31
32
  "scripts": {
32
33
  "test": "jest",
@@ -218,11 +218,13 @@ class CIReporter {
218
218
  }
219
219
  }
220
220
 
221
- // Set output
221
+ // Set output using Environment Files (GITHUB_OUTPUT)
222
+ // Note: In GitHub Actions, these would be written to $GITHUB_OUTPUT file
223
+ // Format: echo "name=value" >> $GITHUB_OUTPUT
222
224
  lines.push('');
223
- lines.push(`::set-output name=violations::${results.summary.totalViolations}`);
224
- lines.push(`::set-output name=blocked::${blockDecision.shouldBlock}`);
225
- lines.push(`::set-output name=phase_minus_one::${blockDecision.requiresPhaseMinusOne}`);
225
+ lines.push(`violations=${results.summary.totalViolations}`);
226
+ lines.push(`blocked=${blockDecision.shouldBlock}`);
227
+ lines.push(`phase_minus_one=${blockDecision.requiresPhaseMinusOne}`);
226
228
 
227
229
  return lines.join('\n');
228
230
  }
@@ -230,10 +232,10 @@ class CIReporter {
230
232
  /**
231
233
  * Format as JUnit XML
232
234
  * @param {Object} results - Check results
233
- * @param {Object} blockDecision - Block decision
235
+ * @param {Object} _blockDecision - Block decision (unused, for interface compatibility)
234
236
  * @returns {string} JUnit XML
235
237
  */
236
- formatJUnit(results, blockDecision) {
238
+ formatJUnit(results, _blockDecision) {
237
239
  const lines = [];
238
240
 
239
241
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
@@ -9,7 +9,7 @@
9
9
 
10
10
  const fs = require('fs').promises;
11
11
  const path = require('path');
12
- const { ConstitutionalChecker, SEVERITY } = require('./checker');
12
+ const { ConstitutionalChecker } = require('./checker');
13
13
 
14
14
  /**
15
15
  * Default configuration
@@ -51,7 +51,7 @@ class PhaseMinusOneGate {
51
51
  * @returns {Promise<Object>} Gate record
52
52
  */
53
53
  async trigger(options) {
54
- const gateId = `GATE-${Date.now()}`;
54
+ const gateId = `GATE-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
55
55
 
56
56
  const gate = {
57
57
  id: gateId,
@@ -102,7 +102,7 @@ class SteeringSync {
102
102
  // Check for orphaned references
103
103
  if (structure) {
104
104
  // Check if directories mentioned in structure.md exist
105
- const dirPattern = /`([a-z\-]+\/)`/g;
105
+ const dirPattern = /`([a-z-]+\/)`/g;
106
106
  let match;
107
107
  while ((match = dirPattern.exec(structure)) !== null) {
108
108
  const dirName = match[1].replace('/', '');
@@ -37,7 +37,7 @@ class SprintReporter {
37
37
  */
38
38
  async generateReport(sprint) {
39
39
  const report = {
40
- id: `RPT-${sprint.id}-${Date.now()}`,
40
+ id: `RPT-${sprint.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
41
41
  sprintId: sprint.id,
42
42
  sprintName: sprint.name,
43
43
  featureId: sprint.featureId,
@@ -230,7 +230,7 @@ class WorkflowDashboard {
230
230
 
231
231
  const actions = [];
232
232
  const currentStage = workflow.currentStage;
233
- const stageData = workflow.stages[currentStage];
233
+ const _stageData = workflow.stages[currentStage];
234
234
 
235
235
  // Check for blockers
236
236
  const unresolvedBlockers = workflow.blockers.filter(b => !b.resolvedAt);
@@ -393,7 +393,7 @@ class ExperimentReportGenerator {
393
393
  * @returns {string} HTML content
394
394
  */
395
395
  formatHTML(report) {
396
- const { metadata, summary, testResults, metrics, observations, conclusions } = report;
396
+ const { metadata, summary, testResults, observations, conclusions } = report;
397
397
 
398
398
  return `<!DOCTYPE html>
399
399
  <html lang="en">
@@ -120,7 +120,7 @@ class TraceabilityExtractor {
120
120
  const lines = stdout.trim().split('\n').filter(l => l.length > 0);
121
121
 
122
122
  for (const line of lines) {
123
- const [hash, message, date, author] = line.split('|');
123
+ const [hash, message] = line.split('|');
124
124
 
125
125
  for (const pattern of REQ_PATTERNS) {
126
126
  pattern.lastIndex = 0;