musubi-sdd 3.6.1 → 3.7.1

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.
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview MUSUBI Checkpoint CLI
5
+ * @description Manage development checkpoints
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { Command } = require('commander');
11
+ const chalk = require('chalk');
12
+ const path = require('path');
13
+ const { CheckpointManager, CheckpointState } = require('../src/managers/checkpoint-manager');
14
+
15
+ const program = new Command();
16
+
17
+ // Initialize checkpoint manager
18
+ function getManager(options = {}) {
19
+ return new CheckpointManager({
20
+ workspaceDir: options.workspace || process.cwd(),
21
+ maxCheckpoints: options.maxCheckpoints || 50,
22
+ });
23
+ }
24
+
25
+ program
26
+ .name('musubi-checkpoint')
27
+ .description('Manage development state checkpoints')
28
+ .version('1.0.0');
29
+
30
+ // Create checkpoint
31
+ program
32
+ .command('create')
33
+ .alias('save')
34
+ .description('Create a new checkpoint')
35
+ .option('-n, --name <name>', 'Checkpoint name')
36
+ .option('-d, --description <description>', 'Checkpoint description')
37
+ .option('-t, --tags <tags>', 'Comma-separated tags')
38
+ .option('-w, --workspace <dir>', 'Workspace directory')
39
+ .action(async (options) => {
40
+ try {
41
+ const manager = getManager(options);
42
+ await manager.initialize();
43
+
44
+ const checkpoint = await manager.create({
45
+ name: options.name,
46
+ description: options.description,
47
+ tags: options.tags ? options.tags.split(',').map(t => t.trim()) : [],
48
+ });
49
+
50
+ console.log(chalk.green('✓ Checkpoint created successfully'));
51
+ console.log();
52
+ console.log(chalk.bold('ID:'), checkpoint.id);
53
+ console.log(chalk.bold('Name:'), checkpoint.name);
54
+ console.log(chalk.bold('Files:'), checkpoint.stats.filesCount);
55
+ console.log(chalk.bold('Size:'), formatSize(checkpoint.stats.totalSize));
56
+ console.log(chalk.bold('Timestamp:'), new Date(checkpoint.timestamp).toLocaleString());
57
+
58
+ manager.stopAutoCheckpoint();
59
+ } catch (error) {
60
+ console.error(chalk.red('Error:'), error.message);
61
+ process.exit(1);
62
+ }
63
+ });
64
+
65
+ // List checkpoints
66
+ program
67
+ .command('list')
68
+ .alias('ls')
69
+ .description('List all checkpoints')
70
+ .option('-t, --tags <tags>', 'Filter by tags (comma-separated)')
71
+ .option('-s, --state <state>', 'Filter by state')
72
+ .option('-l, --limit <n>', 'Maximum results', parseInt)
73
+ .option('-w, --workspace <dir>', 'Workspace directory')
74
+ .option('--json', 'Output as JSON')
75
+ .action(async (options) => {
76
+ try {
77
+ const manager = getManager(options);
78
+ await manager.initialize();
79
+
80
+ const checkpoints = manager.list({
81
+ tags: options.tags ? options.tags.split(',').map(t => t.trim()) : undefined,
82
+ state: options.state,
83
+ limit: options.limit,
84
+ });
85
+
86
+ if (options.json) {
87
+ console.log(JSON.stringify(checkpoints, null, 2));
88
+ manager.stopAutoCheckpoint();
89
+ return;
90
+ }
91
+
92
+ if (checkpoints.length === 0) {
93
+ console.log(chalk.yellow('No checkpoints found'));
94
+ manager.stopAutoCheckpoint();
95
+ return;
96
+ }
97
+
98
+ console.log(chalk.bold(`Checkpoints (${checkpoints.length}):`));
99
+ console.log();
100
+
101
+ for (const cp of checkpoints) {
102
+ const stateColor = getStateColor(cp.state);
103
+ const current = cp.id === manager.currentCheckpoint ? chalk.cyan(' [current]') : '';
104
+
105
+ console.log(`${chalk.bold(cp.id)}${current}`);
106
+ console.log(` Name: ${cp.name}`);
107
+ console.log(` State: ${stateColor(cp.state)}`);
108
+ console.log(` Files: ${cp.stats.filesCount} | Size: ${formatSize(cp.stats.totalSize)}`);
109
+ console.log(` Time: ${new Date(cp.timestamp).toLocaleString()}`);
110
+ if (cp.tags.length > 0) {
111
+ console.log(` Tags: ${cp.tags.map(t => chalk.blue(`#${t}`)).join(' ')}`);
112
+ }
113
+ console.log();
114
+ }
115
+
116
+ manager.stopAutoCheckpoint();
117
+ } catch (error) {
118
+ console.error(chalk.red('Error:'), error.message);
119
+ process.exit(1);
120
+ }
121
+ });
122
+
123
+ // Show checkpoint details
124
+ program
125
+ .command('show <id>')
126
+ .description('Show checkpoint details')
127
+ .option('-w, --workspace <dir>', 'Workspace directory')
128
+ .option('--json', 'Output as JSON')
129
+ .action(async (id, options) => {
130
+ try {
131
+ const manager = getManager(options);
132
+ await manager.initialize();
133
+
134
+ const checkpoint = manager.get(id);
135
+ if (!checkpoint) {
136
+ console.error(chalk.red('Checkpoint not found:'), id);
137
+ process.exit(1);
138
+ }
139
+
140
+ if (options.json) {
141
+ console.log(JSON.stringify(checkpoint, null, 2));
142
+ manager.stopAutoCheckpoint();
143
+ return;
144
+ }
145
+
146
+ console.log(chalk.bold('Checkpoint Details:'));
147
+ console.log();
148
+ console.log(chalk.bold('ID:'), checkpoint.id);
149
+ console.log(chalk.bold('Name:'), checkpoint.name);
150
+ console.log(chalk.bold('Description:'), checkpoint.description || '(none)');
151
+ console.log(chalk.bold('State:'), getStateColor(checkpoint.state)(checkpoint.state));
152
+ console.log(chalk.bold('Files:'), checkpoint.stats.filesCount);
153
+ console.log(chalk.bold('Size:'), formatSize(checkpoint.stats.totalSize));
154
+ console.log(chalk.bold('Created:'), new Date(checkpoint.timestamp).toLocaleString());
155
+ console.log(chalk.bold('Tags:'), checkpoint.tags.length > 0
156
+ ? checkpoint.tags.map(t => chalk.blue(`#${t}`)).join(' ')
157
+ : '(none)');
158
+
159
+ if (Object.keys(checkpoint.context).length > 0) {
160
+ console.log(chalk.bold('Context:'));
161
+ console.log(JSON.stringify(checkpoint.context, null, 2));
162
+ }
163
+
164
+ manager.stopAutoCheckpoint();
165
+ } catch (error) {
166
+ console.error(chalk.red('Error:'), error.message);
167
+ process.exit(1);
168
+ }
169
+ });
170
+
171
+ // Restore checkpoint
172
+ program
173
+ .command('restore <id>')
174
+ .description('Restore a checkpoint')
175
+ .option('--no-backup', 'Skip creating backup before restore')
176
+ .option('-w, --workspace <dir>', 'Workspace directory')
177
+ .action(async (id, options) => {
178
+ try {
179
+ const manager = getManager(options);
180
+ await manager.initialize();
181
+
182
+ console.log(chalk.yellow('⚠ Restoring checkpoint will overwrite current files'));
183
+
184
+ const checkpoint = await manager.restore(id, {
185
+ backup: options.backup !== false,
186
+ });
187
+
188
+ console.log(chalk.green('✓ Checkpoint restored successfully'));
189
+ console.log();
190
+ console.log(chalk.bold('ID:'), checkpoint.id);
191
+ console.log(chalk.bold('Name:'), checkpoint.name);
192
+ console.log(chalk.bold('Files restored:'), checkpoint.stats.filesCount);
193
+
194
+ if (options.backup !== false) {
195
+ console.log(chalk.cyan('ℹ Backup checkpoint created before restore'));
196
+ }
197
+
198
+ manager.stopAutoCheckpoint();
199
+ } catch (error) {
200
+ console.error(chalk.red('Error:'), error.message);
201
+ process.exit(1);
202
+ }
203
+ });
204
+
205
+ // Delete checkpoint
206
+ program
207
+ .command('delete <id>')
208
+ .alias('rm')
209
+ .description('Delete a checkpoint')
210
+ .option('-w, --workspace <dir>', 'Workspace directory')
211
+ .action(async (id, options) => {
212
+ try {
213
+ const manager = getManager(options);
214
+ await manager.initialize();
215
+
216
+ const deleted = await manager.delete(id);
217
+ if (deleted) {
218
+ console.log(chalk.green('✓ Checkpoint deleted:'), id);
219
+ } else {
220
+ console.error(chalk.red('Checkpoint not found:'), id);
221
+ process.exit(1);
222
+ }
223
+
224
+ manager.stopAutoCheckpoint();
225
+ } catch (error) {
226
+ console.error(chalk.red('Error:'), error.message);
227
+ process.exit(1);
228
+ }
229
+ });
230
+
231
+ // Archive checkpoint
232
+ program
233
+ .command('archive <id>')
234
+ .description('Archive a checkpoint')
235
+ .option('-w, --workspace <dir>', 'Workspace directory')
236
+ .action(async (id, options) => {
237
+ try {
238
+ const manager = getManager(options);
239
+ await manager.initialize();
240
+
241
+ const checkpoint = await manager.archive(id);
242
+ console.log(chalk.green('✓ Checkpoint archived:'), checkpoint.name);
243
+
244
+ manager.stopAutoCheckpoint();
245
+ } catch (error) {
246
+ console.error(chalk.red('Error:'), error.message);
247
+ process.exit(1);
248
+ }
249
+ });
250
+
251
+ // Compare checkpoints
252
+ program
253
+ .command('compare <id1> <id2>')
254
+ .alias('diff')
255
+ .description('Compare two checkpoints')
256
+ .option('-w, --workspace <dir>', 'Workspace directory')
257
+ .option('--json', 'Output as JSON')
258
+ .action(async (id1, id2, options) => {
259
+ try {
260
+ const manager = getManager(options);
261
+ await manager.initialize();
262
+
263
+ const comparison = await manager.compare(id1, id2);
264
+
265
+ if (options.json) {
266
+ console.log(JSON.stringify(comparison, null, 2));
267
+ manager.stopAutoCheckpoint();
268
+ return;
269
+ }
270
+
271
+ console.log(chalk.bold('Checkpoint Comparison:'));
272
+ console.log();
273
+ console.log(chalk.bold('Checkpoint 1:'), comparison.checkpoint1.name);
274
+ console.log(` ID: ${comparison.checkpoint1.id}`);
275
+ console.log(` Time: ${new Date(comparison.checkpoint1.timestamp).toLocaleString()}`);
276
+ console.log();
277
+ console.log(chalk.bold('Checkpoint 2:'), comparison.checkpoint2.name);
278
+ console.log(` ID: ${comparison.checkpoint2.id}`);
279
+ console.log(` Time: ${new Date(comparison.checkpoint2.timestamp).toLocaleString()}`);
280
+ console.log();
281
+
282
+ console.log(chalk.bold('Changes:'));
283
+ console.log(` ${chalk.green('Added:')} ${comparison.changes.added} files`);
284
+ console.log(` ${chalk.red('Removed:')} ${comparison.changes.removed} files`);
285
+ console.log(` ${chalk.yellow('Modified:')} ${comparison.changes.modified} files`);
286
+ console.log(` ${chalk.gray('Unchanged:')} ${comparison.changes.unchanged} files`);
287
+
288
+ if (comparison.files.added.length > 0) {
289
+ console.log();
290
+ console.log(chalk.green('Added files:'));
291
+ comparison.files.added.forEach(f => console.log(` + ${f}`));
292
+ }
293
+
294
+ if (comparison.files.removed.length > 0) {
295
+ console.log();
296
+ console.log(chalk.red('Removed files:'));
297
+ comparison.files.removed.forEach(f => console.log(` - ${f}`));
298
+ }
299
+
300
+ if (comparison.files.modified.length > 0) {
301
+ console.log();
302
+ console.log(chalk.yellow('Modified files:'));
303
+ comparison.files.modified.forEach(f => console.log(` ~ ${f}`));
304
+ }
305
+
306
+ manager.stopAutoCheckpoint();
307
+ } catch (error) {
308
+ console.error(chalk.red('Error:'), error.message);
309
+ process.exit(1);
310
+ }
311
+ });
312
+
313
+ // Tag checkpoint
314
+ program
315
+ .command('tag <id> <tags...>')
316
+ .description('Add tags to a checkpoint')
317
+ .option('-w, --workspace <dir>', 'Workspace directory')
318
+ .action(async (id, tags, options) => {
319
+ try {
320
+ const manager = getManager(options);
321
+ await manager.initialize();
322
+
323
+ const checkpoint = await manager.addTags(id, tags);
324
+ console.log(chalk.green('✓ Tags added to checkpoint:'), checkpoint.name);
325
+ console.log(chalk.bold('Tags:'), checkpoint.tags.map(t => chalk.blue(`#${t}`)).join(' '));
326
+
327
+ manager.stopAutoCheckpoint();
328
+ } catch (error) {
329
+ console.error(chalk.red('Error:'), error.message);
330
+ process.exit(1);
331
+ }
332
+ });
333
+
334
+ // Current checkpoint
335
+ program
336
+ .command('current')
337
+ .description('Show current checkpoint')
338
+ .option('-w, --workspace <dir>', 'Workspace directory')
339
+ .action(async (options) => {
340
+ try {
341
+ const manager = getManager(options);
342
+ await manager.initialize();
343
+
344
+ const current = manager.getCurrent();
345
+ if (!current) {
346
+ console.log(chalk.yellow('No current checkpoint'));
347
+ manager.stopAutoCheckpoint();
348
+ return;
349
+ }
350
+
351
+ console.log(chalk.bold('Current Checkpoint:'));
352
+ console.log(chalk.bold('ID:'), current.id);
353
+ console.log(chalk.bold('Name:'), current.name);
354
+ console.log(chalk.bold('Created:'), new Date(current.timestamp).toLocaleString());
355
+
356
+ manager.stopAutoCheckpoint();
357
+ } catch (error) {
358
+ console.error(chalk.red('Error:'), error.message);
359
+ process.exit(1);
360
+ }
361
+ });
362
+
363
+ // Helper functions
364
+ function formatSize(bytes) {
365
+ const units = ['B', 'KB', 'MB', 'GB'];
366
+ let i = 0;
367
+ let size = bytes;
368
+ while (size >= 1024 && i < units.length - 1) {
369
+ size /= 1024;
370
+ i++;
371
+ }
372
+ return `${size.toFixed(2)} ${units[i]}`;
373
+ }
374
+
375
+ function getStateColor(state) {
376
+ switch (state) {
377
+ case CheckpointState.CREATED:
378
+ return chalk.green;
379
+ case CheckpointState.ACTIVE:
380
+ return chalk.cyan;
381
+ case CheckpointState.RESTORED:
382
+ return chalk.yellow;
383
+ case CheckpointState.ARCHIVED:
384
+ return chalk.gray;
385
+ default:
386
+ return chalk.white;
387
+ }
388
+ }
389
+
390
+ // Parse and execute
391
+ program.parse(process.argv);
392
+
393
+ // Show help if no command
394
+ if (process.argv.length <= 2) {
395
+ program.help();
396
+ }
@@ -19,7 +19,8 @@ const {
19
19
  convertFromSpeckit,
20
20
  convertToSpeckit,
21
21
  validateFormat,
22
- testRoundtrip
22
+ testRoundtrip,
23
+ convertFromOpenAPI,
23
24
  } = require('../src/converters');
24
25
  const packageJson = require('../package.json');
25
26
 
@@ -137,6 +138,44 @@ program
137
138
  }
138
139
  });
139
140
 
141
+ program
142
+ .command('from-openapi <specPath>')
143
+ .description('Convert OpenAPI/Swagger specification to MUSUBI requirements')
144
+ .option('-o, --output <dir>', 'Output directory', '.')
145
+ .option('--dry-run', 'Preview changes without writing')
146
+ .option('-v, --verbose', 'Verbose output')
147
+ .option('-f, --force', 'Overwrite existing files')
148
+ .option('--feature <name>', 'Create as single feature with given name')
149
+ .action(async (specPath, options) => {
150
+ try {
151
+ console.log('🔄 Converting OpenAPI → MUSUBI...\n');
152
+
153
+ const result = await convertFromOpenAPI(specPath, {
154
+ output: options.output,
155
+ dryRun: options.dryRun,
156
+ force: options.force,
157
+ verbose: options.verbose,
158
+ featureName: options.feature,
159
+ });
160
+
161
+ console.log(`\n✅ Conversion complete!`);
162
+ console.log(` Features created: ${result.featuresCreated}`);
163
+ console.log(` Requirements: ${result.requirementsCreated}`);
164
+ console.log(` Output: ${result.outputPath}`);
165
+
166
+ if (result.warnings.length > 0) {
167
+ console.log(`\n⚠️ Warnings (${result.warnings.length}):`);
168
+ result.warnings.forEach(w => console.log(` - ${w}`));
169
+ }
170
+ } catch (error) {
171
+ console.error(`\n❌ Conversion failed: ${error.message}`);
172
+ if (options.verbose) {
173
+ console.error(error.stack);
174
+ }
175
+ process.exit(1);
176
+ }
177
+ });
178
+
140
179
  program
141
180
  .command('roundtrip <path>')
142
181
  .description('Test roundtrip conversion (A → B → A\')')