musubi-sdd 5.8.2 ā 5.9.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 +74 -0
- package/README.md +74 -0
- package/bin/musubi-config.js +230 -0
- package/bin/musubi-orchestrate.js +16 -0
- package/bin/musubi-release.js +415 -0
- package/bin/musubi-workflow.js +76 -7
- package/package.json +6 -2
- package/src/generators/changelog-generator.js +344 -0
- package/src/index.js +16 -0
- package/src/managers/package-manager.js +470 -0
- package/src/managers/workflow-mode-manager.js +290 -0
- package/src/managers/workflow.js +110 -12
- package/src/orchestration/builtin-skills.js +303 -0
- package/src/orchestration/index.js +20 -0
- package/src/orchestration/skill-registry.js +3 -0
- package/src/schemas/project-schema.json +296 -0
- package/src/validators/constitution-level-manager.js +387 -0
- package/src/validators/constitutional-validator.js +386 -161
- package/src/validators/project-validator.js +322 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MUSUBI Release CLI
|
|
5
|
+
*
|
|
6
|
+
* Automate release process including:
|
|
7
|
+
* - Version bumping
|
|
8
|
+
* - CHANGELOG generation
|
|
9
|
+
* - Git tagging
|
|
10
|
+
* - npm publishing
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* musubi-release patch|minor|major [options]
|
|
14
|
+
* musubi-release changelog
|
|
15
|
+
* musubi-release notes
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { program } = require('commander');
|
|
19
|
+
const { execSync } = require('child_process');
|
|
20
|
+
const fs = require('fs-extra');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const chalk = require('chalk');
|
|
23
|
+
const { ChangelogGenerator } = require('../src/generators/changelog-generator');
|
|
24
|
+
|
|
25
|
+
const projectRoot = process.cwd();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Read package.json
|
|
29
|
+
*/
|
|
30
|
+
async function readPackageJson() {
|
|
31
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
32
|
+
if (await fs.pathExists(packagePath)) {
|
|
33
|
+
return JSON.parse(await fs.readFile(packagePath, 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Write package.json
|
|
40
|
+
*/
|
|
41
|
+
async function writePackageJson(pkg) {
|
|
42
|
+
const packagePath = path.join(projectRoot, 'package.json');
|
|
43
|
+
await fs.writeFile(packagePath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get current version
|
|
48
|
+
*/
|
|
49
|
+
async function getCurrentVersion() {
|
|
50
|
+
const pkg = await readPackageJson();
|
|
51
|
+
return pkg?.version || '0.0.0';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Bump version
|
|
56
|
+
*/
|
|
57
|
+
function bumpVersion(version, type) {
|
|
58
|
+
const parts = version.split('.').map(Number);
|
|
59
|
+
|
|
60
|
+
switch (type) {
|
|
61
|
+
case 'major':
|
|
62
|
+
return `${parts[0] + 1}.0.0`;
|
|
63
|
+
case 'minor':
|
|
64
|
+
return `${parts[0]}.${parts[1] + 1}.0`;
|
|
65
|
+
case 'patch':
|
|
66
|
+
return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
|
|
67
|
+
default:
|
|
68
|
+
// If type is a specific version, return it
|
|
69
|
+
if (/^\d+\.\d+\.\d+/.test(type)) {
|
|
70
|
+
return type;
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Invalid version type: ${type}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Run pre-release checks
|
|
78
|
+
*/
|
|
79
|
+
async function preReleaseChecks(options) {
|
|
80
|
+
const checks = [];
|
|
81
|
+
|
|
82
|
+
// Check for uncommitted changes
|
|
83
|
+
if (!options.skipGitCheck) {
|
|
84
|
+
try {
|
|
85
|
+
const status = execSync('git status --porcelain', {
|
|
86
|
+
cwd: projectRoot,
|
|
87
|
+
encoding: 'utf8'
|
|
88
|
+
});
|
|
89
|
+
if (status.trim()) {
|
|
90
|
+
checks.push({
|
|
91
|
+
name: 'Uncommitted changes',
|
|
92
|
+
passed: false,
|
|
93
|
+
message: 'You have uncommitted changes'
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
checks.push({ name: 'Git status', passed: true });
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
checks.push({ name: 'Git status', passed: false, message: 'Git not available' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Run tests
|
|
104
|
+
if (!options.skipTests) {
|
|
105
|
+
try {
|
|
106
|
+
console.log(chalk.gray(' Running tests...'));
|
|
107
|
+
execSync('npm test', { cwd: projectRoot, stdio: 'pipe' });
|
|
108
|
+
checks.push({ name: 'Tests', passed: true });
|
|
109
|
+
} catch {
|
|
110
|
+
checks.push({ name: 'Tests', passed: false, message: 'Tests failed' });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check coverage (if available)
|
|
115
|
+
if (!options.skipCoverage) {
|
|
116
|
+
try {
|
|
117
|
+
const coveragePath = path.join(projectRoot, 'coverage/coverage-summary.json');
|
|
118
|
+
if (await fs.pathExists(coveragePath)) {
|
|
119
|
+
const coverage = JSON.parse(await fs.readFile(coveragePath, 'utf8'));
|
|
120
|
+
const totalCoverage = coverage.total?.lines?.pct || 0;
|
|
121
|
+
const threshold = options.coverageThreshold || 70;
|
|
122
|
+
|
|
123
|
+
if (totalCoverage >= threshold) {
|
|
124
|
+
checks.push({
|
|
125
|
+
name: 'Coverage',
|
|
126
|
+
passed: true,
|
|
127
|
+
message: `${totalCoverage}% >= ${threshold}%`
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
checks.push({
|
|
131
|
+
name: 'Coverage',
|
|
132
|
+
passed: false,
|
|
133
|
+
message: `${totalCoverage}% < ${threshold}%`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// Coverage check optional
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return checks;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create git tag
|
|
147
|
+
*/
|
|
148
|
+
function createGitTag(version, message) {
|
|
149
|
+
try {
|
|
150
|
+
execSync(`git tag -a v${version} -m "${message}"`, {
|
|
151
|
+
cwd: projectRoot,
|
|
152
|
+
encoding: 'utf8'
|
|
153
|
+
});
|
|
154
|
+
return true;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(chalk.yellow(`Warning: Could not create tag: ${error.message}`));
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Push to remote
|
|
163
|
+
*/
|
|
164
|
+
function pushToRemote(includeTags = true) {
|
|
165
|
+
try {
|
|
166
|
+
if (includeTags) {
|
|
167
|
+
execSync('git push --follow-tags', { cwd: projectRoot, encoding: 'utf8' });
|
|
168
|
+
} else {
|
|
169
|
+
execSync('git push', { cwd: projectRoot, encoding: 'utf8' });
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(chalk.yellow(`Warning: Could not push: ${error.message}`));
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Publish to npm
|
|
180
|
+
*/
|
|
181
|
+
function publishToNpm(options = {}) {
|
|
182
|
+
try {
|
|
183
|
+
let cmd = 'npm publish';
|
|
184
|
+
if (options.access) cmd += ` --access ${options.access}`;
|
|
185
|
+
if (options.tag) cmd += ` --tag ${options.tag}`;
|
|
186
|
+
if (options.dryRun) cmd += ' --dry-run';
|
|
187
|
+
|
|
188
|
+
execSync(cmd, { cwd: projectRoot, stdio: 'inherit' });
|
|
189
|
+
return true;
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(chalk.red(`Error publishing: ${error.message}`));
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// CLI Commands
|
|
197
|
+
program
|
|
198
|
+
.name('musubi-release')
|
|
199
|
+
.description('MUSUBI Release Manager - Automate release process')
|
|
200
|
+
.version('1.0.0');
|
|
201
|
+
|
|
202
|
+
program
|
|
203
|
+
.command('bump <type>')
|
|
204
|
+
.description('Bump version and prepare release (type: patch, minor, major, or specific version)')
|
|
205
|
+
.option('--skip-tests', 'Skip running tests')
|
|
206
|
+
.option('--skip-coverage', 'Skip coverage check')
|
|
207
|
+
.option('--skip-git-check', 'Skip git status check')
|
|
208
|
+
.option('--skip-changelog', 'Skip CHANGELOG update')
|
|
209
|
+
.option('--no-tag', 'Skip git tag creation')
|
|
210
|
+
.option('--no-push', 'Skip pushing to remote')
|
|
211
|
+
.option('--dry-run', 'Show what would be done without making changes')
|
|
212
|
+
.action(async (type, options) => {
|
|
213
|
+
try {
|
|
214
|
+
console.log(chalk.bold('\nš MUSUBI Release Manager\n'));
|
|
215
|
+
|
|
216
|
+
// Get current version
|
|
217
|
+
const currentVersion = await getCurrentVersion();
|
|
218
|
+
const newVersion = bumpVersion(currentVersion, type);
|
|
219
|
+
|
|
220
|
+
console.log(chalk.white(` Current version: ${chalk.gray(currentVersion)}`));
|
|
221
|
+
console.log(chalk.white(` New version: ${chalk.cyan(newVersion)}`));
|
|
222
|
+
console.log();
|
|
223
|
+
|
|
224
|
+
// Pre-release checks
|
|
225
|
+
console.log(chalk.bold('š Pre-release checks:\n'));
|
|
226
|
+
const checks = await preReleaseChecks(options);
|
|
227
|
+
|
|
228
|
+
let allPassed = true;
|
|
229
|
+
for (const check of checks) {
|
|
230
|
+
if (check.passed) {
|
|
231
|
+
console.log(chalk.green(` ā
${check.name}${check.message ? `: ${check.message}` : ''}`));
|
|
232
|
+
} else {
|
|
233
|
+
console.log(chalk.red(` ā ${check.name}: ${check.message}`));
|
|
234
|
+
allPassed = false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!allPassed && !options.dryRun) {
|
|
239
|
+
console.log(chalk.red('\nā Pre-release checks failed. Use --skip-* options to bypass.'));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (options.dryRun) {
|
|
244
|
+
console.log(chalk.yellow('\nš Dry run - no changes made'));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log();
|
|
249
|
+
|
|
250
|
+
// Update package.json
|
|
251
|
+
console.log(chalk.white(' š¦ Updating package.json...'));
|
|
252
|
+
const pkg = await readPackageJson();
|
|
253
|
+
if (pkg) {
|
|
254
|
+
pkg.version = newVersion;
|
|
255
|
+
await writePackageJson(pkg);
|
|
256
|
+
console.log(chalk.green(' Done'));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Update CHANGELOG
|
|
260
|
+
if (!options.skipChangelog) {
|
|
261
|
+
console.log(chalk.white(' š Updating CHANGELOG...'));
|
|
262
|
+
const generator = new ChangelogGenerator(projectRoot);
|
|
263
|
+
const result = await generator.update(newVersion);
|
|
264
|
+
console.log(chalk.green(` Added ${result.commitCount} commits`));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Git commit
|
|
268
|
+
console.log(chalk.white(' š¾ Committing changes...'));
|
|
269
|
+
execSync(`git add -A && git commit -m "chore(release): v${newVersion}"`, {
|
|
270
|
+
cwd: projectRoot,
|
|
271
|
+
encoding: 'utf8',
|
|
272
|
+
});
|
|
273
|
+
console.log(chalk.green(' Done'));
|
|
274
|
+
|
|
275
|
+
// Create tag
|
|
276
|
+
if (options.tag !== false) {
|
|
277
|
+
console.log(chalk.white(' š·ļø Creating git tag...'));
|
|
278
|
+
createGitTag(newVersion, `Release v${newVersion}`);
|
|
279
|
+
console.log(chalk.green(' Done'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Push
|
|
283
|
+
if (options.push !== false) {
|
|
284
|
+
console.log(chalk.white(' ā¬ļø Pushing to remote...'));
|
|
285
|
+
pushToRemote(options.tag !== false);
|
|
286
|
+
console.log(chalk.green(' Done'));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(chalk.green(`\nā
Released v${newVersion}!\n`));
|
|
290
|
+
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
program
|
|
298
|
+
.command('publish')
|
|
299
|
+
.description('Publish to npm')
|
|
300
|
+
.option('--access <access>', 'Package access level (public/restricted)')
|
|
301
|
+
.option('--tag <tag>', 'npm dist-tag')
|
|
302
|
+
.option('--dry-run', 'Run npm publish --dry-run')
|
|
303
|
+
.action(async (options) => {
|
|
304
|
+
console.log(chalk.bold('\nš¦ Publishing to npm...\n'));
|
|
305
|
+
|
|
306
|
+
const success = publishToNpm(options);
|
|
307
|
+
if (success) {
|
|
308
|
+
console.log(chalk.green('\nā
Published successfully!'));
|
|
309
|
+
} else {
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
program
|
|
315
|
+
.command('changelog')
|
|
316
|
+
.description('Generate or update CHANGELOG')
|
|
317
|
+
.option('-v, --version <version>', 'Version for the changelog entry')
|
|
318
|
+
.option('-f, --from <tag>', 'Starting tag for commit range')
|
|
319
|
+
.action(async (options) => {
|
|
320
|
+
try {
|
|
321
|
+
console.log(chalk.bold('\nš Generating CHANGELOG...\n'));
|
|
322
|
+
|
|
323
|
+
const generator = new ChangelogGenerator(projectRoot);
|
|
324
|
+
const version = options.version || (await getCurrentVersion());
|
|
325
|
+
const result = await generator.update(version, options.from);
|
|
326
|
+
|
|
327
|
+
console.log(chalk.white(` Version: ${result.version}`));
|
|
328
|
+
console.log(chalk.white(` Commits: ${result.commitCount}`));
|
|
329
|
+
console.log();
|
|
330
|
+
|
|
331
|
+
// Show category breakdown
|
|
332
|
+
for (const [category, commits] of Object.entries(result.categories)) {
|
|
333
|
+
if (commits.length > 0) {
|
|
334
|
+
console.log(chalk.gray(` ${category}: ${commits.length}`));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(chalk.green('\nā
CHANGELOG updated!'));
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
program
|
|
346
|
+
.command('notes')
|
|
347
|
+
.description('Generate release notes')
|
|
348
|
+
.option('-v, --version <version>', 'Version for the release notes')
|
|
349
|
+
.option('-f, --from <tag>', 'Starting tag for commit range')
|
|
350
|
+
.option('-o, --output <file>', 'Output file (default: stdout)')
|
|
351
|
+
.action(async (options) => {
|
|
352
|
+
try {
|
|
353
|
+
const generator = new ChangelogGenerator(projectRoot);
|
|
354
|
+
const version = options.version || (await getCurrentVersion());
|
|
355
|
+
const notes = generator.generateReleaseNotes(version, options.from);
|
|
356
|
+
|
|
357
|
+
if (options.output) {
|
|
358
|
+
await fs.writeFile(options.output, notes, 'utf8');
|
|
359
|
+
console.log(chalk.green(`ā
Release notes written to ${options.output}`));
|
|
360
|
+
} else {
|
|
361
|
+
console.log(notes);
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
program
|
|
370
|
+
.command('status')
|
|
371
|
+
.description('Show release status')
|
|
372
|
+
.action(async () => {
|
|
373
|
+
try {
|
|
374
|
+
console.log(chalk.bold('\nš Release Status\n'));
|
|
375
|
+
|
|
376
|
+
const currentVersion = await getCurrentVersion();
|
|
377
|
+
console.log(chalk.white(` Current version: ${chalk.cyan(currentVersion)}`));
|
|
378
|
+
|
|
379
|
+
// Get last tag
|
|
380
|
+
try {
|
|
381
|
+
const lastTag = execSync('git describe --tags --abbrev=0', {
|
|
382
|
+
cwd: projectRoot,
|
|
383
|
+
encoding: 'utf8',
|
|
384
|
+
}).trim();
|
|
385
|
+
console.log(chalk.white(` Last tag: ${chalk.gray(lastTag)}`));
|
|
386
|
+
|
|
387
|
+
// Count commits since last tag
|
|
388
|
+
const commitCount = execSync(`git rev-list ${lastTag}..HEAD --count`, {
|
|
389
|
+
cwd: projectRoot,
|
|
390
|
+
encoding: 'utf8',
|
|
391
|
+
}).trim();
|
|
392
|
+
console.log(chalk.white(` Commits since tag: ${chalk.yellow(commitCount)}`));
|
|
393
|
+
} catch {
|
|
394
|
+
console.log(chalk.gray(' No tags found'));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Check for uncommitted changes
|
|
398
|
+
const status = execSync('git status --porcelain', {
|
|
399
|
+
cwd: projectRoot,
|
|
400
|
+
encoding: 'utf8',
|
|
401
|
+
});
|
|
402
|
+
if (status.trim()) {
|
|
403
|
+
console.log(chalk.yellow(' Uncommitted changes: Yes'));
|
|
404
|
+
} else {
|
|
405
|
+
console.log(chalk.green(' Uncommitted changes: No'));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.log();
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
program.parse();
|
package/bin/musubi-workflow.js
CHANGED
|
@@ -58,16 +58,35 @@ async function showStatus() {
|
|
|
58
58
|
|
|
59
59
|
console.log(chalk.bold('\nš Workflow Status\n'));
|
|
60
60
|
console.log(chalk.white(`Feature: ${chalk.cyan(state.feature)}`));
|
|
61
|
+
console.log(chalk.white(`Mode: ${chalk.magenta(state.mode || 'default')} (${getModeDescription(state.mode || 'medium')})`));
|
|
61
62
|
console.log(chalk.white(`Current Stage: ${formatStage(state.currentStage)}`));
|
|
62
63
|
console.log(chalk.white(`Started: ${new Date(state.startedAt).toLocaleString()}`));
|
|
64
|
+
|
|
65
|
+
// Show mode config
|
|
66
|
+
if (state.config) {
|
|
67
|
+
console.log(chalk.bold('\nāļø Mode Configuration:'));
|
|
68
|
+
console.log(chalk.gray(` Coverage Threshold: ${state.config.coverageThreshold}%`));
|
|
69
|
+
console.log(chalk.gray(` EARS Required: ${state.config.earsRequired ? 'Yes' : 'No'}`));
|
|
70
|
+
console.log(chalk.gray(` ADR Required: ${state.config.adrRequired ? 'Yes' : 'No'}`));
|
|
71
|
+
if (state.config.skippedArtifacts?.length > 0) {
|
|
72
|
+
console.log(chalk.gray(` Skipped Artifacts: ${state.config.skippedArtifacts.join(', ')}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
63
75
|
|
|
64
|
-
// Show stage progress
|
|
76
|
+
// Show stage progress - use mode-specific stages if available
|
|
65
77
|
console.log(chalk.bold('\nš Stage Progress:\n'));
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
let stagesToShow;
|
|
80
|
+
if (state.mode) {
|
|
81
|
+
const modeManager = engine.getModeManager();
|
|
82
|
+
stagesToShow = await modeManager.getStages(state.mode);
|
|
83
|
+
} else {
|
|
84
|
+
stagesToShow = Object.keys(WORKFLOW_STAGES);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const currentIndex = stagesToShow.indexOf(state.currentStage);
|
|
69
88
|
|
|
70
|
-
|
|
89
|
+
stagesToShow.forEach((stage, index) => {
|
|
71
90
|
const data = state.stages[stage];
|
|
72
91
|
let status = '';
|
|
73
92
|
let color = chalk.gray;
|
|
@@ -180,18 +199,68 @@ program
|
|
|
180
199
|
program
|
|
181
200
|
.command('init <feature>')
|
|
182
201
|
.description('Initialize a new workflow for a feature')
|
|
183
|
-
.option('-s, --stage <stage>', 'Starting stage
|
|
202
|
+
.option('-s, --stage <stage>', 'Starting stage (auto-detected based on mode)')
|
|
203
|
+
.option('-m, --mode <mode>', 'Workflow mode: small, medium, or large (auto-detected from feature name)')
|
|
184
204
|
.action(async (feature, options) => {
|
|
185
205
|
try {
|
|
186
|
-
const
|
|
206
|
+
const initOptions = {};
|
|
207
|
+
if (options.stage) initOptions.startStage = options.stage;
|
|
208
|
+
if (options.mode) initOptions.mode = options.mode;
|
|
209
|
+
|
|
210
|
+
const state = await engine.initWorkflow(feature, initOptions);
|
|
187
211
|
console.log(chalk.green(`\nā
Workflow initialized for "${feature}"`));
|
|
188
|
-
console.log(chalk.cyan(`
|
|
212
|
+
console.log(chalk.cyan(` Mode: ${state.mode} (${getModeDescription(state.mode)})`));
|
|
213
|
+
console.log(chalk.cyan(` Starting: ${formatStage(state.currentStage)}`));
|
|
214
|
+
console.log(chalk.gray(` Coverage: ${state.config.coverageThreshold}%`));
|
|
215
|
+
|
|
216
|
+
// Show stages for this mode
|
|
217
|
+
const modeManager = engine.getModeManager();
|
|
218
|
+
const stages = await modeManager.getStages(state.mode);
|
|
219
|
+
console.log(chalk.bold('\nš Workflow stages:'));
|
|
220
|
+
stages.forEach((s, i) => {
|
|
221
|
+
const isCurrent = s === state.currentStage;
|
|
222
|
+
const prefix = isCurrent ? 'ā' : ' ';
|
|
223
|
+
const color = isCurrent ? chalk.blue : chalk.gray;
|
|
224
|
+
console.log(color(` ${prefix} ${i + 1}. ${formatStage(s)}`));
|
|
225
|
+
});
|
|
189
226
|
} catch (error) {
|
|
190
227
|
console.error(chalk.red(`\nā Error: ${error.message}`));
|
|
191
228
|
process.exit(1);
|
|
192
229
|
}
|
|
193
230
|
});
|
|
194
231
|
|
|
232
|
+
program
|
|
233
|
+
.command('modes')
|
|
234
|
+
.description('List available workflow modes')
|
|
235
|
+
.action(async () => {
|
|
236
|
+
const modeManager = engine.getModeManager();
|
|
237
|
+
const modes = await modeManager.compareModes();
|
|
238
|
+
|
|
239
|
+
console.log(chalk.bold('\nš Available Workflow Modes\n'));
|
|
240
|
+
|
|
241
|
+
modes.forEach(mode => {
|
|
242
|
+
console.log(chalk.bold.cyan(` ${mode.name.toUpperCase()}`));
|
|
243
|
+
console.log(chalk.white(` ${mode.description}`));
|
|
244
|
+
console.log(chalk.gray(` Stages: ${mode.stages.map(s => formatStage(s)).join(' ā ')}`));
|
|
245
|
+
console.log(chalk.gray(` Coverage: ${mode.coverageThreshold}%`));
|
|
246
|
+
console.log(chalk.gray(` EARS: ${mode.earsRequired ? 'Required' : 'Optional'}`));
|
|
247
|
+
console.log(chalk.gray(` ADR: ${mode.adrRequired ? 'Required' : 'Optional'}`));
|
|
248
|
+
console.log();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get mode description
|
|
254
|
+
*/
|
|
255
|
+
function getModeDescription(mode) {
|
|
256
|
+
const descriptions = {
|
|
257
|
+
small: '1-2ęé',
|
|
258
|
+
medium: '1-2ę„',
|
|
259
|
+
large: '1é±é+',
|
|
260
|
+
};
|
|
261
|
+
return descriptions[mode] || mode;
|
|
262
|
+
}
|
|
263
|
+
|
|
195
264
|
program.command('status').description('Show current workflow status').action(showStatus);
|
|
196
265
|
|
|
197
266
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musubi-sdd",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
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": {
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"musubi-convert": "bin/musubi-convert.js",
|
|
25
25
|
"musubi-browser": "bin/musubi-browser.js",
|
|
26
26
|
"musubi-gui": "bin/musubi-gui.js",
|
|
27
|
-
"musubi-orchestrate": "bin/musubi-orchestrate.js"
|
|
27
|
+
"musubi-orchestrate": "bin/musubi-orchestrate.js",
|
|
28
|
+
"musubi-release": "bin/musubi-release.js",
|
|
29
|
+
"musubi-config": "bin/musubi-config.js"
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
|
30
32
|
"test": "jest",
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
},
|
|
58
60
|
"dependencies": {
|
|
59
61
|
"@octokit/rest": "^22.0.1",
|
|
62
|
+
"ajv": "^8.12.0",
|
|
63
|
+
"ajv-formats": "^2.1.1",
|
|
60
64
|
"chalk": "^4.1.2",
|
|
61
65
|
"chokidar": "^3.5.3",
|
|
62
66
|
"commander": "^11.0.0",
|