musubi-sdd 6.2.1 → 6.3.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.
Files changed (55) hide show
  1. package/README.ja.md +3 -3
  2. package/README.md +3 -3
  3. package/bin/musubi-dashboard.js +23 -14
  4. package/bin/musubi-design.js +3 -3
  5. package/bin/musubi-gaps.js +9 -9
  6. package/bin/musubi-init.js +14 -1310
  7. package/bin/musubi-requirements.js +1 -1
  8. package/bin/musubi-tasks.js +5 -5
  9. package/bin/musubi-trace.js +23 -23
  10. package/bin/musubi-upgrade.js +400 -0
  11. package/bin/musubi.js +16 -1
  12. package/package.json +4 -3
  13. package/src/analyzers/gap-detector.js +3 -3
  14. package/src/analyzers/traceability.js +17 -17
  15. package/src/cli/dashboard-cli.js +54 -60
  16. package/src/cli/init-generators.js +464 -0
  17. package/src/cli/init-helpers.js +884 -0
  18. package/src/constitutional/checker.js +67 -65
  19. package/src/constitutional/ci-reporter.js +58 -49
  20. package/src/constitutional/index.js +2 -2
  21. package/src/constitutional/phase-minus-one.js +24 -27
  22. package/src/constitutional/steering-sync.js +29 -40
  23. package/src/dashboard/index.js +2 -2
  24. package/src/dashboard/sprint-planner.js +17 -19
  25. package/src/dashboard/sprint-reporter.js +47 -38
  26. package/src/dashboard/transition-recorder.js +12 -18
  27. package/src/dashboard/workflow-dashboard.js +28 -39
  28. package/src/enterprise/error-recovery.js +109 -49
  29. package/src/enterprise/experiment-report.js +63 -37
  30. package/src/enterprise/index.js +5 -5
  31. package/src/enterprise/rollback-manager.js +28 -29
  32. package/src/enterprise/tech-article.js +41 -35
  33. package/src/generators/design.js +3 -3
  34. package/src/generators/requirements.js +5 -3
  35. package/src/generators/tasks.js +2 -2
  36. package/src/integrations/platforms.js +1 -1
  37. package/src/templates/agents/claude-code/CLAUDE.md +1 -1
  38. package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +132 -113
  39. package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +85 -56
  40. package/src/templates/agents/codex/AGENTS.md +2 -2
  41. package/src/templates/agents/cursor/AGENTS.md +2 -2
  42. package/src/templates/agents/gemini-cli/GEMINI.md +2 -2
  43. package/src/templates/agents/github-copilot/AGENTS.md +2 -2
  44. package/src/templates/agents/github-copilot/commands/sdd-requirements.prompt.md +23 -4
  45. package/src/templates/agents/qwen-code/QWEN.md +2 -2
  46. package/src/templates/agents/shared/AGENTS.md +1 -1
  47. package/src/templates/agents/windsurf/AGENTS.md +2 -2
  48. package/src/templates/skills/browser-agent.md +1 -1
  49. package/src/traceability/extractor.js +22 -21
  50. package/src/traceability/gap-detector.js +19 -17
  51. package/src/traceability/index.js +2 -2
  52. package/src/traceability/matrix-storage.js +20 -22
  53. package/src/validators/constitution.js +5 -2
  54. package/src/validators/critic-system.js +6 -6
  55. package/src/validators/traceability-validator.js +3 -3
@@ -0,0 +1,884 @@
1
+ /**
2
+ * MUSUBI Initialization Helpers
3
+ *
4
+ * Helper functions for musubi-init.js
5
+ * Extracted to reduce file size and improve maintainability
6
+ */
7
+
8
+ const fs = require('fs-extra');
9
+ const path = require('path');
10
+ const chalk = require('chalk');
11
+
12
+ /**
13
+ * External specification reference handler
14
+ * Supports: URL (http/https), local file path, Git repository
15
+ * @param {string} specSource - Specification source (URL, file path, or git URL)
16
+ * @returns {object} Parsed specification with metadata
17
+ */
18
+ async function fetchExternalSpec(specSource) {
19
+ const result = {
20
+ source: specSource,
21
+ type: 'unknown',
22
+ content: null,
23
+ metadata: {},
24
+ error: null,
25
+ };
26
+
27
+ try {
28
+ // Determine source type
29
+ if (specSource.startsWith('http://') || specSource.startsWith('https://')) {
30
+ result.type = 'url';
31
+ const https = require('https');
32
+ const http = require('http');
33
+ const protocol = specSource.startsWith('https://') ? https : http;
34
+
35
+ result.content = await new Promise((resolve, reject) => {
36
+ protocol
37
+ .get(specSource, res => {
38
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
39
+ // Handle redirect
40
+ fetchExternalSpec(res.headers.location).then(r => resolve(r.content));
41
+ return;
42
+ }
43
+ if (res.statusCode !== 200) {
44
+ reject(new Error(`HTTP ${res.statusCode}`));
45
+ return;
46
+ }
47
+ let data = '';
48
+ res.on('data', chunk => (data += chunk));
49
+ res.on('end', () => resolve(data));
50
+ })
51
+ .on('error', reject);
52
+ });
53
+
54
+ // Extract metadata from URL
55
+ result.metadata.url = specSource;
56
+ result.metadata.fetchedAt = new Date().toISOString();
57
+ } else if (specSource.startsWith('git://') || specSource.includes('.git')) {
58
+ result.type = 'git';
59
+ result.metadata.repository = specSource;
60
+ // For Git repos, we'll store the reference for later cloning
61
+ result.content = `# External Specification Reference\n\nRepository: ${specSource}\n\n> Clone this repository to access the full specification.\n`;
62
+ } else if (fs.existsSync(specSource)) {
63
+ result.type = 'file';
64
+ result.content = await fs.readFile(specSource, 'utf8');
65
+ result.metadata.path = path.resolve(specSource);
66
+ result.metadata.readAt = new Date().toISOString();
67
+ } else {
68
+ result.error = `Specification source not found: ${specSource}`;
69
+ }
70
+
71
+ // Try to parse specification format
72
+ if (result.content) {
73
+ result.metadata.format = detectSpecFormat(result.content, specSource);
74
+ result.metadata.summary = extractSpecSummary(result.content);
75
+ }
76
+ } catch (err) {
77
+ result.error = err.message;
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Parse GitHub repository reference
85
+ * Supports formats:
86
+ * - owner/repo
87
+ * - https://github.com/owner/repo
88
+ * - git@github.com:owner/repo.git
89
+ * @param {string} repoRef - Repository reference string
90
+ * @returns {object} Parsed repository info
91
+ */
92
+ function parseGitHubRepo(repoRef) {
93
+ let owner = '';
94
+ let repo = '';
95
+ let branch = 'main';
96
+ let repoPath = '';
97
+
98
+ // Handle owner/repo format
99
+ const simpleMatch = repoRef.match(/^([^/]+)\/([^/@#]+)(?:@([^#]+))?(?:#(.+))?$/);
100
+ if (simpleMatch) {
101
+ owner = simpleMatch[1];
102
+ repo = simpleMatch[2];
103
+ branch = simpleMatch[3] || 'main';
104
+ repoPath = simpleMatch[4] || '';
105
+ return { owner, repo, branch, path: repoPath, url: `https://github.com/${owner}/${repo}` };
106
+ }
107
+
108
+ // Handle https://github.com/owner/repo format
109
+ const httpsMatch = repoRef.match(
110
+ /github\.com\/([^/]+)\/([^/@#\s]+?)(?:\.git)?(?:@([^#]+))?(?:#(.+))?$/
111
+ );
112
+ if (httpsMatch) {
113
+ owner = httpsMatch[1];
114
+ repo = httpsMatch[2];
115
+ branch = httpsMatch[3] || 'main';
116
+ repoPath = httpsMatch[4] || '';
117
+ return { owner, repo, branch, path: repoPath, url: `https://github.com/${owner}/${repo}` };
118
+ }
119
+
120
+ // Handle git@github.com:owner/repo.git format
121
+ const sshMatch = repoRef.match(
122
+ /git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?(?:@([^#]+))?(?:#(.+))?$/
123
+ );
124
+ if (sshMatch) {
125
+ owner = sshMatch[1];
126
+ repo = sshMatch[2];
127
+ branch = sshMatch[3] || 'main';
128
+ repoPath = sshMatch[4] || '';
129
+ return { owner, repo, branch, path: repoPath, url: `https://github.com/${owner}/${repo}` };
130
+ }
131
+
132
+ return { error: `Invalid GitHub repository format: ${repoRef}` };
133
+ }
134
+
135
+ /**
136
+ * Fetch GitHub repository metadata and key files
137
+ * @param {string} repoRef - Repository reference (owner/repo, URL, etc.)
138
+ * @returns {object} Repository data with structure and key files
139
+ */
140
+ async function fetchGitHubRepo(repoRef) {
141
+ const parsed = parseGitHubRepo(repoRef);
142
+ if (parsed.error) {
143
+ return { source: repoRef, error: parsed.error };
144
+ }
145
+
146
+ const { owner, repo, branch, path: subPath } = parsed;
147
+ const https = require('https');
148
+
149
+ const result = {
150
+ source: repoRef,
151
+ owner,
152
+ repo,
153
+ branch,
154
+ url: parsed.url,
155
+ metadata: {},
156
+ files: {},
157
+ structure: [],
158
+ improvements: [],
159
+ error: null,
160
+ };
161
+
162
+ // Helper to fetch from GitHub API
163
+ const fetchGitHubAPI = endpoint =>
164
+ new Promise((resolve, reject) => {
165
+ const options = {
166
+ hostname: 'api.github.com',
167
+ path: endpoint,
168
+ headers: {
169
+ 'User-Agent': 'MUSUBI-SDD',
170
+ Accept: 'application/vnd.github.v3+json',
171
+ },
172
+ };
173
+
174
+ // Add GitHub token if available
175
+ if (process.env.GITHUB_TOKEN) {
176
+ options.headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
177
+ }
178
+
179
+ https
180
+ .get(options, res => {
181
+ let data = '';
182
+ res.on('data', chunk => (data += chunk));
183
+ res.on('end', () => {
184
+ if (res.statusCode === 200) {
185
+ try {
186
+ resolve(JSON.parse(data));
187
+ } catch {
188
+ reject(new Error('Invalid JSON response'));
189
+ }
190
+ } else if (res.statusCode === 404) {
191
+ reject(new Error(`Repository not found: ${owner}/${repo}`));
192
+ } else if (res.statusCode === 403) {
193
+ reject(
194
+ new Error('GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable.')
195
+ );
196
+ } else {
197
+ reject(new Error(`GitHub API error: ${res.statusCode}`));
198
+ }
199
+ });
200
+ })
201
+ .on('error', reject);
202
+ });
203
+
204
+ // Fetch raw file content
205
+ const fetchRawFile = filePath =>
206
+ new Promise((resolve, reject) => {
207
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
208
+ https
209
+ .get(rawUrl, res => {
210
+ if (res.statusCode === 302 || res.statusCode === 301) {
211
+ https
212
+ .get(res.headers.location, res2 => {
213
+ let data = '';
214
+ res2.on('data', chunk => (data += chunk));
215
+ res2.on('end', () => resolve(data));
216
+ })
217
+ .on('error', reject);
218
+ return;
219
+ }
220
+ if (res.statusCode !== 200) {
221
+ resolve(null); // File not found is OK
222
+ return;
223
+ }
224
+ let data = '';
225
+ res.on('data', chunk => (data += chunk));
226
+ res.on('end', () => resolve(data));
227
+ })
228
+ .on('error', reject);
229
+ });
230
+
231
+ try {
232
+ // Fetch repository metadata
233
+ const repoData = await fetchGitHubAPI(`/repos/${owner}/${repo}`);
234
+ result.metadata = {
235
+ name: repoData.name,
236
+ description: repoData.description,
237
+ language: repoData.language,
238
+ stars: repoData.stargazers_count,
239
+ topics: repoData.topics || [],
240
+ license: repoData.license?.spdx_id,
241
+ defaultBranch: repoData.default_branch,
242
+ updatedAt: repoData.updated_at,
243
+ };
244
+
245
+ // Fetch directory structure (root level)
246
+ const treePath = subPath
247
+ ? `/repos/${owner}/${repo}/contents/${subPath}`
248
+ : `/repos/${owner}/${repo}/contents`;
249
+ try {
250
+ const contents = await fetchGitHubAPI(treePath);
251
+ if (Array.isArray(contents)) {
252
+ result.structure = contents.map(item => ({
253
+ name: item.name,
254
+ type: item.type,
255
+ path: item.path,
256
+ }));
257
+ }
258
+ } catch {
259
+ // Ignore structure fetch errors
260
+ }
261
+
262
+ // Fetch key files for analysis
263
+ const keyFiles = [
264
+ 'README.md',
265
+ 'package.json',
266
+ 'Cargo.toml',
267
+ 'pyproject.toml',
268
+ 'go.mod',
269
+ 'pom.xml',
270
+ '.github/CODEOWNERS',
271
+ 'ARCHITECTURE.md',
272
+ 'CONTRIBUTING.md',
273
+ 'docs/architecture.md',
274
+ 'src/lib.rs',
275
+ 'src/index.ts',
276
+ 'src/main.ts',
277
+ ];
278
+
279
+ for (const file of keyFiles) {
280
+ const filePath = subPath ? `${subPath}/${file}` : file;
281
+ try {
282
+ const content = await fetchRawFile(filePath);
283
+ if (content) {
284
+ result.files[file] = content.slice(0, 10000); // Limit content size
285
+ }
286
+ } catch {
287
+ // Ignore individual file fetch errors
288
+ }
289
+ }
290
+ } catch (err) {
291
+ result.error = err.message;
292
+ }
293
+
294
+ return result;
295
+ }
296
+
297
+ /**
298
+ * Fetch multiple GitHub repositories
299
+ * @param {string[]} repos - Array of repository references
300
+ * @returns {object[]} Array of repository data
301
+ */
302
+ async function fetchGitHubRepos(repos) {
303
+ const results = [];
304
+
305
+ for (const repoRef of repos) {
306
+ console.log(chalk.cyan(` 📦 Fetching ${repoRef}...`));
307
+ const repoData = await fetchGitHubRepo(repoRef);
308
+
309
+ if (repoData.error) {
310
+ console.log(chalk.yellow(` ⚠️ ${repoData.error}`));
311
+ } else {
312
+ console.log(
313
+ chalk.green(
314
+ ` ✓ ${repoData.metadata.name || repoData.repo} (${repoData.metadata.language || 'unknown'})`
315
+ )
316
+ );
317
+ if (repoData.metadata.description) {
318
+ console.log(chalk.gray(` ${repoData.metadata.description.slice(0, 80)}`));
319
+ }
320
+ }
321
+
322
+ results.push(repoData);
323
+ }
324
+
325
+ return results;
326
+ }
327
+
328
+ /**
329
+ * Analyze repositories for improvement suggestions
330
+ * @param {object[]} repos - Array of fetched repository data
331
+ * @returns {object} Analysis results with patterns and suggestions
332
+ */
333
+ function analyzeReposForImprovements(repos) {
334
+ const analysis = {
335
+ patterns: [],
336
+ architectures: [],
337
+ technologies: [],
338
+ configurations: [],
339
+ suggestions: [],
340
+ };
341
+
342
+ for (const repo of repos) {
343
+ if (repo.error) continue;
344
+
345
+ // Detect architecture patterns from structure
346
+ const dirs = repo.structure.filter(s => s.type === 'dir').map(s => s.name);
347
+ const files = repo.structure.filter(s => s.type === 'file').map(s => s.name);
348
+
349
+ // Check for Clean Architecture
350
+ if (dirs.some(d => ['domain', 'application', 'infrastructure', 'interface'].includes(d))) {
351
+ analysis.architectures.push({
352
+ repo: repo.repo,
353
+ pattern: 'clean-architecture',
354
+ evidence: dirs.filter(d =>
355
+ ['domain', 'application', 'infrastructure', 'interface'].includes(d)
356
+ ),
357
+ });
358
+ }
359
+
360
+ // Check for Hexagonal Architecture
361
+ if (dirs.some(d => ['adapters', 'ports', 'core', 'hexagon'].includes(d))) {
362
+ analysis.architectures.push({
363
+ repo: repo.repo,
364
+ pattern: 'hexagonal',
365
+ evidence: dirs.filter(d => ['adapters', 'ports', 'core', 'hexagon'].includes(d)),
366
+ });
367
+ }
368
+
369
+ // Check for DDD patterns
370
+ if (
371
+ dirs.some(d =>
372
+ ['aggregates', 'entities', 'valueobjects', 'repositories', 'services'].includes(
373
+ d.toLowerCase()
374
+ )
375
+ )
376
+ ) {
377
+ analysis.patterns.push({
378
+ repo: repo.repo,
379
+ pattern: 'domain-driven-design',
380
+ evidence: dirs,
381
+ });
382
+ }
383
+
384
+ // Check for monorepo patterns
385
+ if (
386
+ dirs.includes('packages') ||
387
+ dirs.includes('apps') ||
388
+ files.includes('pnpm-workspace.yaml')
389
+ ) {
390
+ analysis.patterns.push({
391
+ repo: repo.repo,
392
+ pattern: 'monorepo',
393
+ evidence: dirs.filter(d => ['packages', 'apps', 'libs'].includes(d)),
394
+ });
395
+ }
396
+
397
+ // Analyze package.json for technologies
398
+ if (repo.files['package.json']) {
399
+ try {
400
+ const pkg = JSON.parse(repo.files['package.json']);
401
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
402
+
403
+ // Detect frameworks
404
+ if (deps['react']) analysis.technologies.push({ repo: repo.repo, tech: 'react' });
405
+ if (deps['vue']) analysis.technologies.push({ repo: repo.repo, tech: 'vue' });
406
+ if (deps['@angular/core']) analysis.technologies.push({ repo: repo.repo, tech: 'angular' });
407
+ if (deps['express']) analysis.technologies.push({ repo: repo.repo, tech: 'express' });
408
+ if (deps['fastify']) analysis.technologies.push({ repo: repo.repo, tech: 'fastify' });
409
+ if (deps['next']) analysis.technologies.push({ repo: repo.repo, tech: 'nextjs' });
410
+ if (deps['typescript']) analysis.technologies.push({ repo: repo.repo, tech: 'typescript' });
411
+
412
+ // Detect testing frameworks
413
+ if (deps['jest']) analysis.configurations.push({ repo: repo.repo, config: 'jest' });
414
+ if (deps['vitest']) analysis.configurations.push({ repo: repo.repo, config: 'vitest' });
415
+ if (deps['mocha']) analysis.configurations.push({ repo: repo.repo, config: 'mocha' });
416
+
417
+ // Detect linting/formatting
418
+ if (deps['eslint']) analysis.configurations.push({ repo: repo.repo, config: 'eslint' });
419
+ if (deps['prettier']) analysis.configurations.push({ repo: repo.repo, config: 'prettier' });
420
+ if (deps['biome']) analysis.configurations.push({ repo: repo.repo, config: 'biome' });
421
+ } catch {
422
+ // Ignore JSON parse errors
423
+ }
424
+ }
425
+
426
+ // Analyze Cargo.toml for Rust patterns
427
+ if (repo.files['Cargo.toml']) {
428
+ const cargo = repo.files['Cargo.toml'];
429
+ if (cargo.includes('[workspace]')) {
430
+ analysis.patterns.push({ repo: repo.repo, pattern: 'rust-workspace' });
431
+ }
432
+ if (cargo.includes('tokio')) analysis.technologies.push({ repo: repo.repo, tech: 'tokio' });
433
+ if (cargo.includes('actix')) analysis.technologies.push({ repo: repo.repo, tech: 'actix' });
434
+ if (cargo.includes('axum')) analysis.technologies.push({ repo: repo.repo, tech: 'axum' });
435
+ }
436
+
437
+ // Analyze pyproject.toml for Python patterns
438
+ if (repo.files['pyproject.toml']) {
439
+ const pyproj = repo.files['pyproject.toml'];
440
+ if (pyproj.includes('fastapi'))
441
+ analysis.technologies.push({ repo: repo.repo, tech: 'fastapi' });
442
+ if (pyproj.includes('django'))
443
+ analysis.technologies.push({ repo: repo.repo, tech: 'django' });
444
+ if (pyproj.includes('flask')) analysis.technologies.push({ repo: repo.repo, tech: 'flask' });
445
+ if (pyproj.includes('pytest'))
446
+ analysis.configurations.push({ repo: repo.repo, config: 'pytest' });
447
+ }
448
+
449
+ // Extract README insights
450
+ if (repo.files['README.md']) {
451
+ const readme = repo.files['README.md'];
452
+
453
+ // Check for badges that indicate good practices
454
+ if (readme.includes('coverage')) {
455
+ analysis.suggestions.push({
456
+ repo: repo.repo,
457
+ suggestion: 'code-coverage',
458
+ description: 'Implements code coverage tracking',
459
+ });
460
+ }
461
+ if (readme.includes('CI/CD') || readme.includes('Actions')) {
462
+ analysis.suggestions.push({
463
+ repo: repo.repo,
464
+ suggestion: 'ci-cd',
465
+ description: 'Has CI/CD pipeline configured',
466
+ });
467
+ }
468
+ }
469
+ }
470
+
471
+ // Generate improvement suggestions based on analysis
472
+ if (analysis.architectures.length > 0) {
473
+ const archCounts = {};
474
+ for (const arch of analysis.architectures) {
475
+ archCounts[arch.pattern] = (archCounts[arch.pattern] || 0) + 1;
476
+ }
477
+ const mostCommon = Object.entries(archCounts).sort((a, b) => b[1] - a[1])[0];
478
+ if (mostCommon) {
479
+ analysis.suggestions.push({
480
+ type: 'architecture',
481
+ suggestion: `Consider using ${mostCommon[0]} pattern`,
482
+ count: mostCommon[1],
483
+ repos: analysis.architectures.filter(a => a.pattern === mostCommon[0]).map(a => a.repo),
484
+ });
485
+ }
486
+ }
487
+
488
+ if (analysis.technologies.length > 0) {
489
+ const techCounts = {};
490
+ for (const tech of analysis.technologies) {
491
+ techCounts[tech.tech] = (techCounts[tech.tech] || 0) + 1;
492
+ }
493
+ const popular = Object.entries(techCounts)
494
+ .sort((a, b) => b[1] - a[1])
495
+ .slice(0, 3);
496
+ for (const [tech, count] of popular) {
497
+ analysis.suggestions.push({
498
+ type: 'technology',
499
+ suggestion: `Consider using ${tech}`,
500
+ count,
501
+ repos: analysis.technologies.filter(t => t.tech === tech).map(t => t.repo),
502
+ });
503
+ }
504
+ }
505
+
506
+ return analysis;
507
+ }
508
+
509
+ /**
510
+ * Save reference repositories analysis to steering/references/
511
+ * @param {object[]} repos - Fetched repository data
512
+ * @param {object} analysis - Analysis results
513
+ * @param {string} projectPath - Target project path
514
+ * @returns {string} Created file path
515
+ */
516
+ async function saveReferenceRepos(repos, analysis, projectPath) {
517
+ const refsDir = path.join(projectPath, 'steering', 'references');
518
+ await fs.ensureDir(refsDir);
519
+
520
+ const timestamp = new Date().toISOString().split('T')[0];
521
+ const filename = `github-references-${timestamp}.md`;
522
+
523
+ // Build markdown content
524
+ let content = `# GitHub Reference Repositories
525
+
526
+ > Analyzed on ${new Date().toISOString()}
527
+
528
+ ## Referenced Repositories
529
+
530
+ `;
531
+
532
+ for (const repo of repos) {
533
+ if (repo.error) {
534
+ content += `### ❌ ${repo.source}\n\n`;
535
+ content += `Error: ${repo.error}\n\n`;
536
+ continue;
537
+ }
538
+
539
+ content += `### ${repo.metadata.name || repo.repo}\n\n`;
540
+ content += `- **URL**: ${repo.url}\n`;
541
+ content += `- **Language**: ${repo.metadata.language || 'Unknown'}\n`;
542
+ content += `- **Stars**: ${repo.metadata.stars || 0}\n`;
543
+ if (repo.metadata.description) {
544
+ content += `- **Description**: ${repo.metadata.description}\n`;
545
+ }
546
+ if (repo.metadata.topics && repo.metadata.topics.length > 0) {
547
+ content += `- **Topics**: ${repo.metadata.topics.join(', ')}\n`;
548
+ }
549
+ if (repo.metadata.license) {
550
+ content += `- **License**: ${repo.metadata.license}\n`;
551
+ }
552
+ content += '\n';
553
+
554
+ // Structure
555
+ if (repo.structure.length > 0) {
556
+ content += '**Directory Structure:**\n\n';
557
+ content += '```\n';
558
+ for (const item of repo.structure.slice(0, 20)) {
559
+ content += `${item.type === 'dir' ? '📁' : '📄'} ${item.name}\n`;
560
+ }
561
+ if (repo.structure.length > 20) {
562
+ content += `... and ${repo.structure.length - 20} more items\n`;
563
+ }
564
+ content += '```\n\n';
565
+ }
566
+ }
567
+
568
+ // Analysis section
569
+ content += `## Analysis Results
570
+
571
+ ### Architecture Patterns Detected
572
+
573
+ `;
574
+
575
+ if (analysis.architectures.length > 0) {
576
+ for (const arch of analysis.architectures) {
577
+ content += `- **${arch.pattern}** in \`${arch.repo}\`\n`;
578
+ content += ` - Evidence: ${arch.evidence.join(', ')}\n`;
579
+ }
580
+ } else {
581
+ content += '_No specific architecture patterns detected_\n';
582
+ }
583
+
584
+ content += `\n### Design Patterns
585
+
586
+ `;
587
+
588
+ if (analysis.patterns.length > 0) {
589
+ for (const pattern of analysis.patterns) {
590
+ content += `- **${pattern.pattern}** in \`${pattern.repo}\`\n`;
591
+ }
592
+ } else {
593
+ content += '_No specific design patterns detected_\n';
594
+ }
595
+
596
+ content += `\n### Technologies Used
597
+
598
+ `;
599
+
600
+ if (analysis.technologies.length > 0) {
601
+ const techByRepo = {};
602
+ for (const tech of analysis.technologies) {
603
+ if (!techByRepo[tech.repo]) techByRepo[tech.repo] = [];
604
+ techByRepo[tech.repo].push(tech.tech);
605
+ }
606
+ for (const [repoName, techs] of Object.entries(techByRepo)) {
607
+ content += `- **${repoName}**: ${techs.join(', ')}\n`;
608
+ }
609
+ } else {
610
+ content += '_No specific technologies detected_\n';
611
+ }
612
+
613
+ content += `\n### Configurations
614
+
615
+ `;
616
+
617
+ if (analysis.configurations.length > 0) {
618
+ const configByRepo = {};
619
+ for (const config of analysis.configurations) {
620
+ if (!configByRepo[config.repo]) configByRepo[config.repo] = [];
621
+ configByRepo[config.repo].push(config.config);
622
+ }
623
+ for (const [repoName, configs] of Object.entries(configByRepo)) {
624
+ content += `- **${repoName}**: ${configs.join(', ')}\n`;
625
+ }
626
+ } else {
627
+ content += '_No specific configurations detected_\n';
628
+ }
629
+
630
+ content += `\n## Improvement Suggestions
631
+
632
+ Based on the referenced repositories, consider the following improvements:
633
+
634
+ `;
635
+
636
+ if (analysis.suggestions.length > 0) {
637
+ let i = 1;
638
+ for (const suggestion of analysis.suggestions) {
639
+ if (suggestion.type === 'architecture') {
640
+ content += `${i}. **Architecture**: ${suggestion.suggestion}\n`;
641
+ content += ` - Found in ${suggestion.count} repository(ies): ${suggestion.repos.join(', ')}\n\n`;
642
+ } else if (suggestion.type === 'technology') {
643
+ content += `${i}. **Technology**: ${suggestion.suggestion}\n`;
644
+ content += ` - Used by ${suggestion.count} repository(ies): ${suggestion.repos.join(', ')}\n\n`;
645
+ } else {
646
+ content += `${i}. **${suggestion.suggestion}**: ${suggestion.description}\n`;
647
+ content += ` - Found in: ${suggestion.repo}\n\n`;
648
+ }
649
+ i++;
650
+ }
651
+ } else {
652
+ content += '_No specific suggestions generated_\n';
653
+ }
654
+
655
+ content += `
656
+ ---
657
+ *Generated by MUSUBI SDD - GitHub Reference Analysis*
658
+ `;
659
+
660
+ await fs.writeFile(path.join(refsDir, filename), content);
661
+ return filename;
662
+ }
663
+
664
+ /**
665
+ * Detect specification format from content and filename
666
+ */
667
+ function detectSpecFormat(content, source) {
668
+ const ext = path.extname(source).toLowerCase();
669
+ if (ext === '.json') return 'json';
670
+ if (ext === '.yaml' || ext === '.yml') return 'yaml';
671
+ if (ext === '.md') return 'markdown';
672
+ if (ext === '.rst') return 'rst';
673
+ if (ext === '.html') return 'html';
674
+
675
+ // Try to detect from content
676
+ if (content.trim().startsWith('{')) return 'json';
677
+ if (content.includes('openapi:') || content.includes('swagger:')) return 'openapi';
678
+ if (content.includes('asyncapi:')) return 'asyncapi';
679
+ if (content.includes('# ')) return 'markdown';
680
+
681
+ return 'text';
682
+ }
683
+
684
+ /**
685
+ * Extract summary from specification content
686
+ */
687
+ function extractSpecSummary(content) {
688
+ // Extract first heading and description
689
+ const lines = content.split('\n').slice(0, 50);
690
+ let title = '';
691
+ let description = '';
692
+
693
+ for (const line of lines) {
694
+ if (!title && line.startsWith('# ')) {
695
+ title = line.replace('# ', '').trim();
696
+ } else if (title && !description && line.trim() && !line.startsWith('#')) {
697
+ description = line.trim().slice(0, 200);
698
+ break;
699
+ }
700
+ }
701
+
702
+ return { title, description };
703
+ }
704
+
705
+ /**
706
+ * Save external specification reference to steering/specs/
707
+ */
708
+ async function saveSpecReference(specResult, projectPath) {
709
+ const specsDir = path.join(projectPath, 'steering', 'specs');
710
+ await fs.ensureDir(specsDir);
711
+
712
+ // Create spec reference file
713
+ const timestamp = new Date().toISOString().split('T')[0];
714
+ const safeName = specResult.metadata.summary?.title
715
+ ? specResult.metadata.summary.title
716
+ .toLowerCase()
717
+ .replace(/[^a-z0-9]+/g, '-')
718
+ .slice(0, 50)
719
+ : 'external-spec';
720
+ const filename = `${safeName}-${timestamp}.md`;
721
+
722
+ const refContent = `# External Specification Reference
723
+
724
+ ## Source Information
725
+
726
+ - **Type**: ${specResult.type}
727
+ - **Source**: ${specResult.source}
728
+ - **Format**: ${specResult.metadata.format || 'unknown'}
729
+ - **Fetched**: ${specResult.metadata.fetchedAt || specResult.metadata.readAt || 'N/A'}
730
+
731
+ ## Summary
732
+
733
+ ${specResult.metadata.summary?.title ? `**Title**: ${specResult.metadata.summary.title}` : ''}
734
+ ${specResult.metadata.summary?.description ? `\n**Description**: ${specResult.metadata.summary.description}` : ''}
735
+
736
+ ## Integration Notes
737
+
738
+ This specification is used as a reference for:
739
+ - Requirements analysis
740
+ - Architecture design
741
+ - API design
742
+ - Compliance validation
743
+
744
+ ## Original Content
745
+
746
+ \`\`\`${specResult.metadata.format || 'text'}
747
+ ${specResult.content?.slice(0, 5000) || 'Content not available'}${specResult.content?.length > 5000 ? '\n\n... (truncated, see original source)' : ''}
748
+ \`\`\`
749
+
750
+ ---
751
+ *Generated by MUSUBI SDD - External Specification Reference*
752
+ `;
753
+
754
+ await fs.writeFile(path.join(specsDir, filename), refContent);
755
+ return filename;
756
+ }
757
+
758
+ /**
759
+ * Language recommendation engine
760
+ * @param {object} requirements - User's answers about app types, performance, expertise
761
+ * @returns {Array} Recommended languages with reasons
762
+ */
763
+ function recommendLanguages(requirements) {
764
+ const { appTypes, performanceNeeds, teamExpertise } = requirements;
765
+ const scores = {};
766
+ const reasons = {};
767
+
768
+ // Initialize scores
769
+ const allLangs = [
770
+ 'javascript',
771
+ 'python',
772
+ 'rust',
773
+ 'go',
774
+ 'java',
775
+ 'csharp',
776
+ 'cpp',
777
+ 'swift',
778
+ 'ruby',
779
+ 'php',
780
+ ];
781
+ for (const lang of allLangs) {
782
+ scores[lang] = 0;
783
+ reasons[lang] = [];
784
+ }
785
+
786
+ // Score by application type
787
+ const appTypeScores = {
788
+ 'web-frontend': { javascript: 10, reason: 'Best ecosystem for web frontend' },
789
+ 'web-backend': {
790
+ javascript: 6,
791
+ python: 7,
792
+ go: 8,
793
+ rust: 7,
794
+ java: 7,
795
+ csharp: 6,
796
+ ruby: 5,
797
+ php: 5,
798
+ reason: 'Strong backend frameworks',
799
+ },
800
+ cli: { rust: 9, go: 9, python: 6, reason: 'Fast startup, single binary' },
801
+ desktop: { rust: 7, csharp: 8, cpp: 7, swift: 6, java: 6, reason: 'Native GUI support' },
802
+ mobile: { swift: 9, java: 8, javascript: 6, reason: 'Mobile platform support' },
803
+ data: { python: 10, rust: 6, reason: 'Rich data science ecosystem' },
804
+ ml: { python: 10, rust: 5, cpp: 5, reason: 'ML/AI libraries and frameworks' },
805
+ embedded: { rust: 10, cpp: 9, reason: 'Memory safety, no runtime' },
806
+ game: { cpp: 9, csharp: 8, rust: 6, reason: 'Game engine support' },
807
+ systems: { rust: 10, go: 8, cpp: 9, reason: 'Systems programming' },
808
+ };
809
+
810
+ for (const appType of appTypes || []) {
811
+ const typeScores = appTypeScores[appType];
812
+ if (typeScores) {
813
+ for (const [lang, score] of Object.entries(typeScores)) {
814
+ if (typeof score === 'number') {
815
+ scores[lang] += score;
816
+ if (!reasons[lang].includes(typeScores.reason)) {
817
+ reasons[lang].push(typeScores.reason);
818
+ }
819
+ }
820
+ }
821
+ }
822
+ }
823
+
824
+ // Score by performance needs
825
+ if (performanceNeeds === 'high') {
826
+ scores.rust += 8;
827
+ scores.go += 6;
828
+ scores.cpp += 7;
829
+ reasons.rust.push('High performance, zero-cost abstractions');
830
+ reasons.go.push('Fast compilation, efficient runtime');
831
+ } else if (performanceNeeds === 'rapid') {
832
+ scores.python += 5;
833
+ scores.javascript += 5;
834
+ scores.ruby += 4;
835
+ reasons.python.push('Rapid development, extensive libraries');
836
+ reasons.javascript.push('Fast iteration, universal runtime');
837
+ }
838
+
839
+ // Boost by team expertise
840
+ for (const lang of teamExpertise || []) {
841
+ scores[lang] += 5;
842
+ reasons[lang].push('Team has expertise');
843
+ }
844
+
845
+ // Sort and return top recommendations
846
+ const sorted = Object.entries(scores)
847
+ .filter(([, score]) => score > 0)
848
+ .sort((a, b) => b[1] - a[1])
849
+ .slice(0, 3);
850
+
851
+ const langInfo = {
852
+ javascript: { name: 'JavaScript/TypeScript', emoji: '🟨' },
853
+ python: { name: 'Python', emoji: '🐍' },
854
+ rust: { name: 'Rust', emoji: '🦀' },
855
+ go: { name: 'Go', emoji: '🐹' },
856
+ java: { name: 'Java/Kotlin', emoji: '☕' },
857
+ csharp: { name: 'C#/.NET', emoji: '💜' },
858
+ cpp: { name: 'C/C++', emoji: '⚙️' },
859
+ swift: { name: 'Swift', emoji: '🍎' },
860
+ ruby: { name: 'Ruby', emoji: '💎' },
861
+ php: { name: 'PHP', emoji: '🐘' },
862
+ };
863
+
864
+ return sorted.map(([lang]) => ({
865
+ value: lang,
866
+ name: langInfo[lang].name,
867
+ emoji: langInfo[lang].emoji,
868
+ reason: reasons[lang].slice(0, 2).join('; ') || 'General purpose',
869
+ score: scores[lang],
870
+ }));
871
+ }
872
+
873
+ module.exports = {
874
+ fetchExternalSpec,
875
+ parseGitHubRepo,
876
+ fetchGitHubRepo,
877
+ fetchGitHubRepos,
878
+ analyzeReposForImprovements,
879
+ saveReferenceRepos,
880
+ detectSpecFormat,
881
+ extractSpecSummary,
882
+ saveSpecReference,
883
+ recommendLanguages,
884
+ };