musubi-sdd 2.2.0 → 3.0.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 +1 -1
- package/README.md +1 -1
- package/bin/musubi-browser.js +457 -0
- package/bin/musubi-convert.js +179 -0
- package/bin/musubi-gui.js +270 -0
- package/package.json +13 -3
- package/src/agents/browser/action-executor.js +255 -0
- package/src/agents/browser/ai-comparator.js +255 -0
- package/src/agents/browser/context-manager.js +207 -0
- package/src/agents/browser/index.js +265 -0
- package/src/agents/browser/nl-parser.js +408 -0
- package/src/agents/browser/screenshot.js +174 -0
- package/src/agents/browser/test-generator.js +271 -0
- package/src/converters/index.js +285 -0
- package/src/converters/ir/types.js +508 -0
- package/src/converters/parsers/musubi-parser.js +759 -0
- package/src/converters/parsers/speckit-parser.js +1001 -0
- package/src/converters/writers/musubi-writer.js +808 -0
- package/src/converters/writers/speckit-writer.js +718 -0
- package/src/gui/public/index.html +856 -0
- package/src/gui/server.js +352 -0
- package/src/gui/services/file-watcher.js +119 -0
- package/src/gui/services/index.js +16 -0
- package/src/gui/services/project-scanner.js +547 -0
- package/src/gui/services/traceability-service.js +372 -0
- package/src/gui/services/workflow-service.js +242 -0
- package/src/templates/skills/browser-agent.md +164 -0
- package/src/templates/skills/web-gui.md +188 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converters Module
|
|
3
|
+
*
|
|
4
|
+
* Cross-format conversion between MUSUBI and Spec Kit
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { parseMusubiProject } = require('./parsers/musubi-parser');
|
|
10
|
+
const { parseSpeckitProject } = require('./parsers/speckit-parser');
|
|
11
|
+
const { writeMusubiProject } = require('./writers/musubi-writer');
|
|
12
|
+
const { writeSpeckitProject } = require('./writers/speckit-writer');
|
|
13
|
+
const irTypes = require('./ir/types');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convert a Spec Kit project to MUSUBI format
|
|
17
|
+
* @param {string} sourcePath - Path to Spec Kit project
|
|
18
|
+
* @param {Object} options - Conversion options
|
|
19
|
+
* @returns {Promise<{filesConverted: number, warnings: string[], outputPath: string}>}
|
|
20
|
+
*/
|
|
21
|
+
async function convertFromSpeckit(sourcePath, options = {}) {
|
|
22
|
+
const { output = '.', dryRun = false, force = false, verbose = false, preserveRaw = false } = options;
|
|
23
|
+
|
|
24
|
+
if (verbose) console.log(`Converting Spec Kit project from: ${sourcePath}`);
|
|
25
|
+
|
|
26
|
+
// Parse Spec Kit project to IR
|
|
27
|
+
const ir = await parseSpeckitProject(sourcePath);
|
|
28
|
+
|
|
29
|
+
if (verbose) {
|
|
30
|
+
console.log(` Found ${ir.features.length} features`);
|
|
31
|
+
console.log(` Found ${ir.constitution.articles.length} constitution articles`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Write to MUSUBI format
|
|
35
|
+
const result = await writeMusubiProject(ir, output, { dryRun, force, preserveRaw, verbose });
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
filesConverted: result.filesWritten,
|
|
39
|
+
warnings: result.warnings,
|
|
40
|
+
outputPath: output,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert a MUSUBI project to Spec Kit format
|
|
46
|
+
* @param {Object} options - Conversion options
|
|
47
|
+
* @returns {Promise<{filesConverted: number, warnings: string[], outputPath: string}>}
|
|
48
|
+
*/
|
|
49
|
+
async function convertToSpeckit(options = {}) {
|
|
50
|
+
const {
|
|
51
|
+
source = '.',
|
|
52
|
+
output = './.specify',
|
|
53
|
+
dryRun = false,
|
|
54
|
+
force = false,
|
|
55
|
+
verbose = false,
|
|
56
|
+
preserveRaw = false
|
|
57
|
+
} = options;
|
|
58
|
+
|
|
59
|
+
if (verbose) console.log(`Converting MUSUBI project to Spec Kit format`);
|
|
60
|
+
|
|
61
|
+
// Parse MUSUBI project to IR
|
|
62
|
+
const ir = await parseMusubiProject(source);
|
|
63
|
+
|
|
64
|
+
if (verbose) {
|
|
65
|
+
console.log(` Found ${ir.features.length} features`);
|
|
66
|
+
console.log(` Found ${ir.constitution.articles.length} constitution articles`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Write to Spec Kit format
|
|
70
|
+
const result = await writeSpeckitProject(ir, output.replace('/.specify', ''), { dryRun, force, preserveRaw, verbose });
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
filesConverted: result.filesWritten,
|
|
74
|
+
warnings: result.warnings,
|
|
75
|
+
outputPath: output,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validate a project format
|
|
81
|
+
* @param {string} format - 'speckit' or 'musubi'
|
|
82
|
+
* @param {string} projectPath - Path to project
|
|
83
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
84
|
+
*/
|
|
85
|
+
async function validateFormat(format, projectPath) {
|
|
86
|
+
const errors = [];
|
|
87
|
+
const warnings = [];
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
if (format === 'speckit') {
|
|
91
|
+
await parseSpeckitProject(projectPath);
|
|
92
|
+
} else if (format === 'musubi') {
|
|
93
|
+
await parseMusubiProject(projectPath);
|
|
94
|
+
} else {
|
|
95
|
+
errors.push(`Unknown format: ${format}. Use 'speckit' or 'musubi'.`);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
errors.push(error.message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
valid: errors.length === 0,
|
|
103
|
+
errors,
|
|
104
|
+
warnings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Test roundtrip conversion (A → B → A')
|
|
110
|
+
* @param {string} projectPath - Path to project
|
|
111
|
+
* @param {Object} options - Test options
|
|
112
|
+
* @returns {Promise<{passed: boolean, similarity: number, differences: string[]}>}
|
|
113
|
+
*/
|
|
114
|
+
async function testRoundtrip(projectPath, options = {}) {
|
|
115
|
+
const { verbose = false } = options;
|
|
116
|
+
const differences = [];
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Detect format
|
|
120
|
+
const fs = require('fs-extra');
|
|
121
|
+
const path = require('path');
|
|
122
|
+
|
|
123
|
+
const isSpeckit = await fs.pathExists(path.join(projectPath, '.specify'));
|
|
124
|
+
const isMusubi = await fs.pathExists(path.join(projectPath, 'steering'));
|
|
125
|
+
|
|
126
|
+
if (!isSpeckit && !isMusubi) {
|
|
127
|
+
return {
|
|
128
|
+
passed: false,
|
|
129
|
+
similarity: 0,
|
|
130
|
+
differences: ['Could not detect project format (neither .specify nor steering directory found)'],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (verbose) {
|
|
135
|
+
console.log(`Detected format: ${isSpeckit ? 'Spec Kit' : 'MUSUBI'}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Parse original
|
|
139
|
+
const originalIR = isSpeckit
|
|
140
|
+
? await parseSpeckitProject(projectPath)
|
|
141
|
+
: await parseMusubiProject(projectPath);
|
|
142
|
+
|
|
143
|
+
// Convert to other format (in memory)
|
|
144
|
+
const tempDir = path.join(projectPath, '.roundtrip-temp');
|
|
145
|
+
await fs.ensureDir(tempDir);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
// Write to other format
|
|
149
|
+
if (isSpeckit) {
|
|
150
|
+
await writeMusubiProject(originalIR, tempDir, { force: true });
|
|
151
|
+
} else {
|
|
152
|
+
await writeSpeckitProject(originalIR, tempDir, { force: true });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Parse converted
|
|
156
|
+
const convertedIR = isSpeckit
|
|
157
|
+
? await parseMusubiProject(tempDir)
|
|
158
|
+
: await parseSpeckitProject(tempDir);
|
|
159
|
+
|
|
160
|
+
// Write back to original format
|
|
161
|
+
const tempDir2 = path.join(projectPath, '.roundtrip-temp2');
|
|
162
|
+
await fs.ensureDir(tempDir2);
|
|
163
|
+
|
|
164
|
+
if (isSpeckit) {
|
|
165
|
+
await writeSpeckitProject(convertedIR, tempDir2, { force: true });
|
|
166
|
+
} else {
|
|
167
|
+
await writeMusubiProject(convertedIR, tempDir2, { force: true });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse roundtrip result
|
|
171
|
+
const roundtripIR = isSpeckit
|
|
172
|
+
? await parseSpeckitProject(tempDir2)
|
|
173
|
+
: await parseMusubiProject(tempDir2);
|
|
174
|
+
|
|
175
|
+
// Compare
|
|
176
|
+
const similarity = compareIR(originalIR, roundtripIR, differences);
|
|
177
|
+
|
|
178
|
+
// Cleanup
|
|
179
|
+
await fs.remove(tempDir);
|
|
180
|
+
await fs.remove(tempDir2);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
passed: similarity >= 90,
|
|
184
|
+
similarity,
|
|
185
|
+
differences,
|
|
186
|
+
};
|
|
187
|
+
} finally {
|
|
188
|
+
// Ensure cleanup
|
|
189
|
+
await fs.remove(tempDir).catch(() => {});
|
|
190
|
+
await fs.remove(path.join(projectPath, '.roundtrip-temp2')).catch(() => {});
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return {
|
|
194
|
+
passed: false,
|
|
195
|
+
similarity: 0,
|
|
196
|
+
differences: [`Roundtrip test error: ${error.message}`],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Compare two IR structures and return similarity percentage
|
|
203
|
+
* @param {import('./ir/types').ProjectIR} original
|
|
204
|
+
* @param {import('./ir/types').ProjectIR} roundtrip
|
|
205
|
+
* @param {string[]} differences
|
|
206
|
+
* @returns {number} Similarity percentage (0-100)
|
|
207
|
+
*/
|
|
208
|
+
function compareIR(original, roundtrip, differences) {
|
|
209
|
+
let matches = 0;
|
|
210
|
+
let total = 0;
|
|
211
|
+
|
|
212
|
+
// Compare metadata
|
|
213
|
+
total++;
|
|
214
|
+
if (original.metadata.name === roundtrip.metadata.name) {
|
|
215
|
+
matches++;
|
|
216
|
+
} else {
|
|
217
|
+
differences.push(`Name mismatch: "${original.metadata.name}" vs "${roundtrip.metadata.name}"`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Compare features count
|
|
221
|
+
total++;
|
|
222
|
+
if (original.features.length === roundtrip.features.length) {
|
|
223
|
+
matches++;
|
|
224
|
+
} else {
|
|
225
|
+
differences.push(`Feature count mismatch: ${original.features.length} vs ${roundtrip.features.length}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Compare each feature
|
|
229
|
+
for (let i = 0; i < Math.min(original.features.length, roundtrip.features.length); i++) {
|
|
230
|
+
const origFeature = original.features[i];
|
|
231
|
+
const rtFeature = roundtrip.features[i];
|
|
232
|
+
|
|
233
|
+
// Compare feature name
|
|
234
|
+
total++;
|
|
235
|
+
if (origFeature.name === rtFeature.name) {
|
|
236
|
+
matches++;
|
|
237
|
+
} else {
|
|
238
|
+
differences.push(`Feature ${i} name mismatch: "${origFeature.name}" vs "${rtFeature.name}"`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Compare requirements count
|
|
242
|
+
total++;
|
|
243
|
+
const origReqs = origFeature.specification?.requirements?.length || 0;
|
|
244
|
+
const rtReqs = rtFeature.specification?.requirements?.length || 0;
|
|
245
|
+
if (origReqs === rtReqs) {
|
|
246
|
+
matches++;
|
|
247
|
+
} else {
|
|
248
|
+
differences.push(`Feature ${i} requirements count mismatch: ${origReqs} vs ${rtReqs}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Compare tasks count
|
|
252
|
+
total++;
|
|
253
|
+
const origTasks = origFeature.tasks?.length || 0;
|
|
254
|
+
const rtTasks = rtFeature.tasks?.length || 0;
|
|
255
|
+
if (origTasks === rtTasks) {
|
|
256
|
+
matches++;
|
|
257
|
+
} else {
|
|
258
|
+
differences.push(`Feature ${i} tasks count mismatch: ${origTasks} vs ${rtTasks}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Compare constitution articles
|
|
263
|
+
total++;
|
|
264
|
+
const origArticles = original.constitution?.articles?.length || 0;
|
|
265
|
+
const rtArticles = roundtrip.constitution?.articles?.length || 0;
|
|
266
|
+
if (origArticles === rtArticles) {
|
|
267
|
+
matches++;
|
|
268
|
+
} else {
|
|
269
|
+
differences.push(`Constitution articles count mismatch: ${origArticles} vs ${rtArticles}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return Math.round((matches / total) * 100);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = {
|
|
276
|
+
convertFromSpeckit,
|
|
277
|
+
convertToSpeckit,
|
|
278
|
+
validateFormat,
|
|
279
|
+
testRoundtrip,
|
|
280
|
+
parseMusubiProject,
|
|
281
|
+
parseSpeckitProject,
|
|
282
|
+
writeMusubiProject,
|
|
283
|
+
writeSpeckitProject,
|
|
284
|
+
ir: irTypes,
|
|
285
|
+
};
|