musubi-sdd 5.1.0 → 5.6.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.
- package/README.ja.md +106 -48
- package/README.md +110 -32
- package/bin/musubi-analyze.js +74 -67
- package/bin/musubi-browser.js +27 -26
- package/bin/musubi-change.js +48 -47
- package/bin/musubi-checkpoint.js +10 -7
- package/bin/musubi-convert.js +25 -25
- package/bin/musubi-costs.js +27 -10
- package/bin/musubi-gui.js +52 -46
- package/bin/musubi-init.js +1952 -10
- package/bin/musubi-orchestrate.js +327 -239
- package/bin/musubi-remember.js +69 -56
- package/bin/musubi-resolve.js +53 -45
- package/bin/musubi-trace.js +51 -22
- package/bin/musubi-validate.js +39 -30
- package/bin/musubi-workflow.js +33 -34
- package/bin/musubi.js +39 -2
- package/package.json +1 -1
- package/src/agents/agent-loop.js +94 -95
- package/src/agents/agentic/code-generator.js +119 -109
- package/src/agents/agentic/code-reviewer.js +105 -108
- package/src/agents/agentic/index.js +4 -4
- package/src/agents/browser/action-executor.js +13 -13
- package/src/agents/browser/ai-comparator.js +11 -10
- package/src/agents/browser/context-manager.js +6 -6
- package/src/agents/browser/index.js +5 -5
- package/src/agents/browser/nl-parser.js +31 -46
- package/src/agents/browser/screenshot.js +2 -2
- package/src/agents/browser/test-generator.js +6 -4
- package/src/agents/function-tool.js +71 -65
- package/src/agents/index.js +7 -7
- package/src/agents/schema-generator.js +98 -94
- package/src/analyzers/ast-extractor.js +158 -146
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +241 -126
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +77 -81
- package/src/analyzers/security-analyzer.js +19 -11
- package/src/analyzers/stuck-detector.js +19 -17
- package/src/converters/index.js +78 -57
- package/src/converters/ir/types.js +12 -12
- package/src/converters/parsers/musubi-parser.js +134 -126
- package/src/converters/parsers/openapi-parser.js +70 -53
- package/src/converters/parsers/speckit-parser.js +239 -175
- package/src/converters/writers/musubi-writer.js +123 -118
- package/src/converters/writers/speckit-writer.js +124 -113
- package/src/generators/rust-migration-generator.js +512 -0
- package/src/gui/public/index.html +1365 -1211
- package/src/gui/server.js +41 -40
- package/src/gui/services/file-watcher.js +23 -8
- package/src/gui/services/project-scanner.js +26 -20
- package/src/gui/services/replanning-service.js +27 -23
- package/src/gui/services/traceability-service.js +8 -8
- package/src/gui/services/workflow-service.js +14 -7
- package/src/index.js +151 -0
- package/src/integrations/cicd.js +90 -104
- package/src/integrations/codegraph-mcp.js +643 -0
- package/src/integrations/documentation.js +142 -103
- package/src/integrations/examples.js +95 -80
- package/src/integrations/github-client.js +17 -17
- package/src/integrations/index.js +5 -5
- package/src/integrations/mcp/index.js +21 -21
- package/src/integrations/mcp/mcp-context-provider.js +76 -78
- package/src/integrations/mcp/mcp-discovery.js +74 -72
- package/src/integrations/mcp/mcp-tool-registry.js +99 -94
- package/src/integrations/mcp-connector.js +70 -66
- package/src/integrations/platforms.js +50 -49
- package/src/integrations/tool-discovery.js +37 -31
- package/src/llm-providers/anthropic-provider.js +11 -11
- package/src/llm-providers/base-provider.js +16 -18
- package/src/llm-providers/copilot-provider.js +22 -19
- package/src/llm-providers/index.js +26 -25
- package/src/llm-providers/ollama-provider.js +11 -11
- package/src/llm-providers/openai-provider.js +12 -12
- package/src/managers/agent-memory.js +36 -24
- package/src/managers/checkpoint-manager.js +4 -8
- package/src/managers/delta-spec.js +19 -19
- package/src/managers/index.js +13 -4
- package/src/managers/memory-condenser.js +35 -45
- package/src/managers/repo-skill-manager.js +57 -31
- package/src/managers/skill-loader.js +25 -22
- package/src/managers/skill-tools.js +36 -72
- package/src/managers/workflow.js +30 -22
- package/src/monitoring/cost-tracker.js +48 -46
- package/src/monitoring/incident-manager.js +116 -106
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +75 -62
- package/src/monitoring/quality-dashboard.js +45 -41
- package/src/monitoring/release-manager.js +63 -53
- package/src/orchestration/agent-skill-binding.js +39 -47
- package/src/orchestration/error-handler.js +65 -107
- package/src/orchestration/guardrails/base-guardrail.js +26 -24
- package/src/orchestration/guardrails/guardrail-rules.js +50 -64
- package/src/orchestration/guardrails/index.js +5 -5
- package/src/orchestration/guardrails/input-guardrail.js +58 -45
- package/src/orchestration/guardrails/output-guardrail.js +104 -81
- package/src/orchestration/guardrails/safety-check.js +79 -79
- package/src/orchestration/index.js +38 -55
- package/src/orchestration/mcp-tool-adapters.js +96 -99
- package/src/orchestration/orchestration-engine.js +21 -21
- package/src/orchestration/pattern-registry.js +60 -45
- package/src/orchestration/patterns/auto.js +34 -47
- package/src/orchestration/patterns/group-chat.js +59 -65
- package/src/orchestration/patterns/handoff.js +67 -65
- package/src/orchestration/patterns/human-in-loop.js +51 -72
- package/src/orchestration/patterns/nested.js +25 -40
- package/src/orchestration/patterns/sequential.js +35 -34
- package/src/orchestration/patterns/swarm.js +63 -56
- package/src/orchestration/patterns/triage.js +150 -109
- package/src/orchestration/reasoning/index.js +9 -9
- package/src/orchestration/reasoning/planning-engine.js +143 -140
- package/src/orchestration/reasoning/reasoning-engine.js +206 -144
- package/src/orchestration/reasoning/self-correction.js +121 -128
- package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
- package/src/orchestration/replanning/alternative-generator.js +37 -42
- package/src/orchestration/replanning/config.js +63 -59
- package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
- package/src/orchestration/replanning/index.js +24 -20
- package/src/orchestration/replanning/plan-evaluator.js +49 -50
- package/src/orchestration/replanning/plan-monitor.js +32 -28
- package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
- package/src/orchestration/replanning/replan-history.js +33 -26
- package/src/orchestration/replanning/replanning-engine.js +106 -108
- package/src/orchestration/skill-executor.js +107 -109
- package/src/orchestration/skill-registry.js +85 -89
- package/src/orchestration/workflow-examples.js +228 -231
- package/src/orchestration/workflow-executor.js +65 -68
- package/src/orchestration/workflow-orchestrator.js +72 -73
- package/src/phase4-integration.js +47 -40
- package/src/phase5-integration.js +89 -30
- package/src/reporters/coverage-report.js +82 -30
- package/src/reporters/hierarchical-reporter.js +498 -0
- package/src/reporters/traceability-matrix-report.js +29 -20
- package/src/resolvers/issue-resolver.js +43 -31
- package/src/steering/advanced-validation.js +133 -124
- package/src/steering/auto-updater.js +60 -73
- package/src/steering/index.js +6 -6
- package/src/steering/quality-metrics.js +41 -35
- package/src/steering/steering-auto-update.js +83 -86
- package/src/steering/steering-validator.js +98 -106
- package/src/steering/template-constraints.js +53 -54
- package/src/templates/agents/claude-code/CLAUDE.md +32 -32
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
- package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
- package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
- package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
- package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
- package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
- package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
- package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
- package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
- package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
- package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
- package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
- package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
- package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
- package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
- package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
- package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
- package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
- package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
- package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
- package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
- package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
- package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
- package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
- package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
- package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
- package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
- package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
- package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
- package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
- package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
- package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
- package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
- package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
- package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
- package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
- package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
- package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
- package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
- package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
- package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
- package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
- package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
- package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
- package/src/templates/agents/codex/AGENTS.md +74 -42
- package/src/templates/agents/cursor/AGENTS.md +74 -42
- package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
- package/src/templates/agents/github-copilot/AGENTS.md +83 -51
- package/src/templates/agents/qwen-code/QWEN.md +74 -42
- package/src/templates/agents/windsurf/AGENTS.md +74 -42
- package/src/templates/architectures/README.md +41 -0
- package/src/templates/architectures/clean-architecture/README.md +113 -0
- package/src/templates/architectures/event-driven/README.md +162 -0
- package/src/templates/architectures/hexagonal/README.md +130 -0
- package/src/templates/index.js +6 -1
- package/src/templates/locale-manager.js +16 -16
- package/src/templates/shared/delta-spec-template.md +20 -13
- package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
- package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
- package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
- package/src/templates/shared/steering/structure.md +95 -0
- package/src/templates/skills/browser-agent.md +21 -16
- package/src/templates/skills/web-gui.md +8 -0
- package/src/templates/template-constraints.js +50 -53
- package/src/validators/advanced-validation.js +30 -36
- package/src/validators/constitutional-validator.js +77 -73
- package/src/validators/critic-system.js +49 -59
- package/src/validators/delta-format.js +59 -55
- package/src/validators/traceability-validator.js +7 -11
package/bin/musubi-init.js
CHANGED
|
@@ -26,12 +26,874 @@ const TEMPLATE_DIR = path.join(__dirname, '..', 'src', 'templates');
|
|
|
26
26
|
const SHARED_TEMPLATE_DIR = path.join(TEMPLATE_DIR, 'shared');
|
|
27
27
|
const AGENTS_TEMPLATE_DIR = path.join(TEMPLATE_DIR, 'agents');
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* External specification reference handler
|
|
31
|
+
* Supports: URL (http/https), local file path, Git repository
|
|
32
|
+
* @param {string} specSource - Specification source (URL, file path, or git URL)
|
|
33
|
+
* @returns {object} Parsed specification with metadata
|
|
34
|
+
*/
|
|
35
|
+
async function fetchExternalSpec(specSource) {
|
|
36
|
+
const result = {
|
|
37
|
+
source: specSource,
|
|
38
|
+
type: 'unknown',
|
|
39
|
+
content: null,
|
|
40
|
+
metadata: {},
|
|
41
|
+
error: null,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Determine source type
|
|
46
|
+
if (specSource.startsWith('http://') || specSource.startsWith('https://')) {
|
|
47
|
+
result.type = 'url';
|
|
48
|
+
const https = require('https');
|
|
49
|
+
const http = require('http');
|
|
50
|
+
const protocol = specSource.startsWith('https://') ? https : http;
|
|
51
|
+
|
|
52
|
+
result.content = await new Promise((resolve, reject) => {
|
|
53
|
+
protocol
|
|
54
|
+
.get(specSource, res => {
|
|
55
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
56
|
+
// Handle redirect
|
|
57
|
+
fetchExternalSpec(res.headers.location).then(r => resolve(r.content));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let data = '';
|
|
65
|
+
res.on('data', chunk => (data += chunk));
|
|
66
|
+
res.on('end', () => resolve(data));
|
|
67
|
+
})
|
|
68
|
+
.on('error', reject);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Extract metadata from URL
|
|
72
|
+
result.metadata.url = specSource;
|
|
73
|
+
result.metadata.fetchedAt = new Date().toISOString();
|
|
74
|
+
} else if (specSource.startsWith('git://') || specSource.includes('.git')) {
|
|
75
|
+
result.type = 'git';
|
|
76
|
+
result.metadata.repository = specSource;
|
|
77
|
+
// For Git repos, we'll store the reference for later cloning
|
|
78
|
+
result.content = `# External Specification Reference\n\nRepository: ${specSource}\n\n> Clone this repository to access the full specification.\n`;
|
|
79
|
+
} else if (fs.existsSync(specSource)) {
|
|
80
|
+
result.type = 'file';
|
|
81
|
+
result.content = await fs.readFile(specSource, 'utf8');
|
|
82
|
+
result.metadata.path = path.resolve(specSource);
|
|
83
|
+
result.metadata.readAt = new Date().toISOString();
|
|
84
|
+
} else {
|
|
85
|
+
result.error = `Specification source not found: ${specSource}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try to parse specification format
|
|
89
|
+
if (result.content) {
|
|
90
|
+
result.metadata.format = detectSpecFormat(result.content, specSource);
|
|
91
|
+
result.metadata.summary = extractSpecSummary(result.content);
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
result.error = err.message;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse GitHub repository reference
|
|
102
|
+
* Supports formats:
|
|
103
|
+
* - owner/repo
|
|
104
|
+
* - https://github.com/owner/repo
|
|
105
|
+
* - git@github.com:owner/repo.git
|
|
106
|
+
* @param {string} repoRef - Repository reference string
|
|
107
|
+
* @returns {object} Parsed repository info
|
|
108
|
+
*/
|
|
109
|
+
function parseGitHubRepo(repoRef) {
|
|
110
|
+
let owner = '';
|
|
111
|
+
let repo = '';
|
|
112
|
+
let branch = 'main';
|
|
113
|
+
let path = '';
|
|
114
|
+
|
|
115
|
+
// Handle owner/repo format
|
|
116
|
+
const simpleMatch = repoRef.match(/^([^/]+)\/([^/@#]+)(?:@([^#]+))?(?:#(.+))?$/);
|
|
117
|
+
if (simpleMatch) {
|
|
118
|
+
owner = simpleMatch[1];
|
|
119
|
+
repo = simpleMatch[2];
|
|
120
|
+
branch = simpleMatch[3] || 'main';
|
|
121
|
+
path = simpleMatch[4] || '';
|
|
122
|
+
return { owner, repo, branch, path, url: `https://github.com/${owner}/${repo}` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Handle https://github.com/owner/repo format
|
|
126
|
+
const httpsMatch = repoRef.match(
|
|
127
|
+
/github\.com\/([^/]+)\/([^/@#\s]+?)(?:\.git)?(?:@([^#]+))?(?:#(.+))?$/
|
|
128
|
+
);
|
|
129
|
+
if (httpsMatch) {
|
|
130
|
+
owner = httpsMatch[1];
|
|
131
|
+
repo = httpsMatch[2];
|
|
132
|
+
branch = httpsMatch[3] || 'main';
|
|
133
|
+
path = httpsMatch[4] || '';
|
|
134
|
+
return { owner, repo, branch, path, url: `https://github.com/${owner}/${repo}` };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle git@github.com:owner/repo.git format
|
|
138
|
+
const sshMatch = repoRef.match(
|
|
139
|
+
/git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?(?:@([^#]+))?(?:#(.+))?$/
|
|
140
|
+
);
|
|
141
|
+
if (sshMatch) {
|
|
142
|
+
owner = sshMatch[1];
|
|
143
|
+
repo = sshMatch[2];
|
|
144
|
+
branch = sshMatch[3] || 'main';
|
|
145
|
+
path = sshMatch[4] || '';
|
|
146
|
+
return { owner, repo, branch, path, url: `https://github.com/${owner}/${repo}` };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { error: `Invalid GitHub repository format: ${repoRef}` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Fetch GitHub repository metadata and key files
|
|
154
|
+
* @param {string} repoRef - Repository reference (owner/repo, URL, etc.)
|
|
155
|
+
* @returns {object} Repository data with structure and key files
|
|
156
|
+
*/
|
|
157
|
+
async function fetchGitHubRepo(repoRef) {
|
|
158
|
+
const parsed = parseGitHubRepo(repoRef);
|
|
159
|
+
if (parsed.error) {
|
|
160
|
+
return { source: repoRef, error: parsed.error };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { owner, repo, branch, path: subPath } = parsed;
|
|
164
|
+
const https = require('https');
|
|
165
|
+
|
|
166
|
+
const result = {
|
|
167
|
+
source: repoRef,
|
|
168
|
+
owner,
|
|
169
|
+
repo,
|
|
170
|
+
branch,
|
|
171
|
+
url: parsed.url,
|
|
172
|
+
metadata: {},
|
|
173
|
+
files: {},
|
|
174
|
+
structure: [],
|
|
175
|
+
improvements: [],
|
|
176
|
+
error: null,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Helper to fetch from GitHub API
|
|
180
|
+
const fetchGitHubAPI = endpoint =>
|
|
181
|
+
new Promise((resolve, reject) => {
|
|
182
|
+
const options = {
|
|
183
|
+
hostname: 'api.github.com',
|
|
184
|
+
path: endpoint,
|
|
185
|
+
headers: {
|
|
186
|
+
'User-Agent': 'MUSUBI-SDD',
|
|
187
|
+
Accept: 'application/vnd.github.v3+json',
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Add GitHub token if available
|
|
192
|
+
if (process.env.GITHUB_TOKEN) {
|
|
193
|
+
options.headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
https
|
|
197
|
+
.get(options, res => {
|
|
198
|
+
let data = '';
|
|
199
|
+
res.on('data', chunk => (data += chunk));
|
|
200
|
+
res.on('end', () => {
|
|
201
|
+
if (res.statusCode === 200) {
|
|
202
|
+
try {
|
|
203
|
+
resolve(JSON.parse(data));
|
|
204
|
+
} catch {
|
|
205
|
+
reject(new Error('Invalid JSON response'));
|
|
206
|
+
}
|
|
207
|
+
} else if (res.statusCode === 404) {
|
|
208
|
+
reject(new Error(`Repository not found: ${owner}/${repo}`));
|
|
209
|
+
} else if (res.statusCode === 403) {
|
|
210
|
+
reject(
|
|
211
|
+
new Error('GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable.')
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
reject(new Error(`GitHub API error: ${res.statusCode}`));
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
})
|
|
218
|
+
.on('error', reject);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Fetch raw file content
|
|
222
|
+
const fetchRawFile = filePath =>
|
|
223
|
+
new Promise((resolve, reject) => {
|
|
224
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
|
|
225
|
+
https
|
|
226
|
+
.get(rawUrl, res => {
|
|
227
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
228
|
+
https
|
|
229
|
+
.get(res.headers.location, res2 => {
|
|
230
|
+
let data = '';
|
|
231
|
+
res2.on('data', chunk => (data += chunk));
|
|
232
|
+
res2.on('end', () => resolve(data));
|
|
233
|
+
})
|
|
234
|
+
.on('error', reject);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (res.statusCode !== 200) {
|
|
238
|
+
resolve(null); // File not found is OK
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
let data = '';
|
|
242
|
+
res.on('data', chunk => (data += chunk));
|
|
243
|
+
res.on('end', () => resolve(data));
|
|
244
|
+
})
|
|
245
|
+
.on('error', reject);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
// Fetch repository metadata
|
|
250
|
+
const repoData = await fetchGitHubAPI(`/repos/${owner}/${repo}`);
|
|
251
|
+
result.metadata = {
|
|
252
|
+
name: repoData.name,
|
|
253
|
+
description: repoData.description,
|
|
254
|
+
language: repoData.language,
|
|
255
|
+
stars: repoData.stargazers_count,
|
|
256
|
+
topics: repoData.topics || [],
|
|
257
|
+
license: repoData.license?.spdx_id,
|
|
258
|
+
defaultBranch: repoData.default_branch,
|
|
259
|
+
updatedAt: repoData.updated_at,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Fetch directory structure (root level)
|
|
263
|
+
const treePath = subPath
|
|
264
|
+
? `/repos/${owner}/${repo}/contents/${subPath}`
|
|
265
|
+
: `/repos/${owner}/${repo}/contents`;
|
|
266
|
+
try {
|
|
267
|
+
const contents = await fetchGitHubAPI(treePath);
|
|
268
|
+
if (Array.isArray(contents)) {
|
|
269
|
+
result.structure = contents.map(item => ({
|
|
270
|
+
name: item.name,
|
|
271
|
+
type: item.type,
|
|
272
|
+
path: item.path,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// Ignore structure fetch errors
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Fetch key files for analysis
|
|
280
|
+
const keyFiles = [
|
|
281
|
+
'README.md',
|
|
282
|
+
'package.json',
|
|
283
|
+
'Cargo.toml',
|
|
284
|
+
'pyproject.toml',
|
|
285
|
+
'go.mod',
|
|
286
|
+
'pom.xml',
|
|
287
|
+
'.github/CODEOWNERS',
|
|
288
|
+
'ARCHITECTURE.md',
|
|
289
|
+
'CONTRIBUTING.md',
|
|
290
|
+
'docs/architecture.md',
|
|
291
|
+
'src/lib.rs',
|
|
292
|
+
'src/index.ts',
|
|
293
|
+
'src/main.ts',
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
for (const file of keyFiles) {
|
|
297
|
+
const filePath = subPath ? `${subPath}/${file}` : file;
|
|
298
|
+
try {
|
|
299
|
+
const content = await fetchRawFile(filePath);
|
|
300
|
+
if (content) {
|
|
301
|
+
result.files[file] = content.slice(0, 10000); // Limit content size
|
|
302
|
+
}
|
|
303
|
+
} catch {
|
|
304
|
+
// Ignore individual file fetch errors
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
result.error = err.message;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Fetch multiple GitHub repositories
|
|
316
|
+
* @param {string[]} repos - Array of repository references
|
|
317
|
+
* @returns {object[]} Array of repository data
|
|
318
|
+
*/
|
|
319
|
+
async function fetchGitHubRepos(repos) {
|
|
320
|
+
const results = [];
|
|
321
|
+
|
|
322
|
+
for (const repoRef of repos) {
|
|
323
|
+
console.log(chalk.cyan(` 📦 Fetching ${repoRef}...`));
|
|
324
|
+
const repoData = await fetchGitHubRepo(repoRef);
|
|
325
|
+
|
|
326
|
+
if (repoData.error) {
|
|
327
|
+
console.log(chalk.yellow(` ⚠️ ${repoData.error}`));
|
|
328
|
+
} else {
|
|
329
|
+
console.log(
|
|
330
|
+
chalk.green(
|
|
331
|
+
` ✓ ${repoData.metadata.name || repoData.repo} (${repoData.metadata.language || 'unknown'})`
|
|
332
|
+
)
|
|
333
|
+
);
|
|
334
|
+
if (repoData.metadata.description) {
|
|
335
|
+
console.log(chalk.gray(` ${repoData.metadata.description.slice(0, 80)}`));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
results.push(repoData);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Analyze repositories for improvement suggestions
|
|
347
|
+
* @param {object[]} repos - Array of fetched repository data
|
|
348
|
+
* @returns {object} Analysis results with patterns and suggestions
|
|
349
|
+
*/
|
|
350
|
+
function analyzeReposForImprovements(repos) {
|
|
351
|
+
const analysis = {
|
|
352
|
+
patterns: [],
|
|
353
|
+
architectures: [],
|
|
354
|
+
technologies: [],
|
|
355
|
+
configurations: [],
|
|
356
|
+
suggestions: [],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
for (const repo of repos) {
|
|
360
|
+
if (repo.error) continue;
|
|
361
|
+
|
|
362
|
+
// Detect architecture patterns from structure
|
|
363
|
+
const dirs = repo.structure.filter(s => s.type === 'dir').map(s => s.name);
|
|
364
|
+
const files = repo.structure.filter(s => s.type === 'file').map(s => s.name);
|
|
365
|
+
|
|
366
|
+
// Check for Clean Architecture
|
|
367
|
+
if (dirs.some(d => ['domain', 'application', 'infrastructure', 'interface'].includes(d))) {
|
|
368
|
+
analysis.architectures.push({
|
|
369
|
+
repo: repo.repo,
|
|
370
|
+
pattern: 'clean-architecture',
|
|
371
|
+
evidence: dirs.filter(d =>
|
|
372
|
+
['domain', 'application', 'infrastructure', 'interface'].includes(d)
|
|
373
|
+
),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check for Hexagonal Architecture
|
|
378
|
+
if (dirs.some(d => ['adapters', 'ports', 'core', 'hexagon'].includes(d))) {
|
|
379
|
+
analysis.architectures.push({
|
|
380
|
+
repo: repo.repo,
|
|
381
|
+
pattern: 'hexagonal',
|
|
382
|
+
evidence: dirs.filter(d => ['adapters', 'ports', 'core', 'hexagon'].includes(d)),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check for DDD patterns
|
|
387
|
+
if (
|
|
388
|
+
dirs.some(d =>
|
|
389
|
+
['aggregates', 'entities', 'valueobjects', 'repositories', 'services'].includes(
|
|
390
|
+
d.toLowerCase()
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
) {
|
|
394
|
+
analysis.patterns.push({
|
|
395
|
+
repo: repo.repo,
|
|
396
|
+
pattern: 'domain-driven-design',
|
|
397
|
+
evidence: dirs,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Check for monorepo patterns
|
|
402
|
+
if (
|
|
403
|
+
dirs.includes('packages') ||
|
|
404
|
+
dirs.includes('apps') ||
|
|
405
|
+
files.includes('pnpm-workspace.yaml')
|
|
406
|
+
) {
|
|
407
|
+
analysis.patterns.push({
|
|
408
|
+
repo: repo.repo,
|
|
409
|
+
pattern: 'monorepo',
|
|
410
|
+
evidence: dirs.filter(d => ['packages', 'apps', 'libs'].includes(d)),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Analyze package.json for technologies
|
|
415
|
+
if (repo.files['package.json']) {
|
|
416
|
+
try {
|
|
417
|
+
const pkg = JSON.parse(repo.files['package.json']);
|
|
418
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
419
|
+
|
|
420
|
+
// Detect frameworks
|
|
421
|
+
if (deps['react']) analysis.technologies.push({ repo: repo.repo, tech: 'react' });
|
|
422
|
+
if (deps['vue']) analysis.technologies.push({ repo: repo.repo, tech: 'vue' });
|
|
423
|
+
if (deps['@angular/core']) analysis.technologies.push({ repo: repo.repo, tech: 'angular' });
|
|
424
|
+
if (deps['express']) analysis.technologies.push({ repo: repo.repo, tech: 'express' });
|
|
425
|
+
if (deps['fastify']) analysis.technologies.push({ repo: repo.repo, tech: 'fastify' });
|
|
426
|
+
if (deps['next']) analysis.technologies.push({ repo: repo.repo, tech: 'nextjs' });
|
|
427
|
+
if (deps['typescript']) analysis.technologies.push({ repo: repo.repo, tech: 'typescript' });
|
|
428
|
+
|
|
429
|
+
// Detect testing frameworks
|
|
430
|
+
if (deps['jest']) analysis.configurations.push({ repo: repo.repo, config: 'jest' });
|
|
431
|
+
if (deps['vitest']) analysis.configurations.push({ repo: repo.repo, config: 'vitest' });
|
|
432
|
+
if (deps['mocha']) analysis.configurations.push({ repo: repo.repo, config: 'mocha' });
|
|
433
|
+
|
|
434
|
+
// Detect linting/formatting
|
|
435
|
+
if (deps['eslint']) analysis.configurations.push({ repo: repo.repo, config: 'eslint' });
|
|
436
|
+
if (deps['prettier']) analysis.configurations.push({ repo: repo.repo, config: 'prettier' });
|
|
437
|
+
if (deps['biome']) analysis.configurations.push({ repo: repo.repo, config: 'biome' });
|
|
438
|
+
} catch {
|
|
439
|
+
// Ignore JSON parse errors
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Analyze Cargo.toml for Rust patterns
|
|
444
|
+
if (repo.files['Cargo.toml']) {
|
|
445
|
+
const cargo = repo.files['Cargo.toml'];
|
|
446
|
+
if (cargo.includes('[workspace]')) {
|
|
447
|
+
analysis.patterns.push({ repo: repo.repo, pattern: 'rust-workspace' });
|
|
448
|
+
}
|
|
449
|
+
if (cargo.includes('tokio')) analysis.technologies.push({ repo: repo.repo, tech: 'tokio' });
|
|
450
|
+
if (cargo.includes('actix')) analysis.technologies.push({ repo: repo.repo, tech: 'actix' });
|
|
451
|
+
if (cargo.includes('axum')) analysis.technologies.push({ repo: repo.repo, tech: 'axum' });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Analyze pyproject.toml for Python patterns
|
|
455
|
+
if (repo.files['pyproject.toml']) {
|
|
456
|
+
const pyproj = repo.files['pyproject.toml'];
|
|
457
|
+
if (pyproj.includes('fastapi'))
|
|
458
|
+
analysis.technologies.push({ repo: repo.repo, tech: 'fastapi' });
|
|
459
|
+
if (pyproj.includes('django'))
|
|
460
|
+
analysis.technologies.push({ repo: repo.repo, tech: 'django' });
|
|
461
|
+
if (pyproj.includes('flask')) analysis.technologies.push({ repo: repo.repo, tech: 'flask' });
|
|
462
|
+
if (pyproj.includes('pytest'))
|
|
463
|
+
analysis.configurations.push({ repo: repo.repo, config: 'pytest' });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Extract README insights
|
|
467
|
+
if (repo.files['README.md']) {
|
|
468
|
+
const readme = repo.files['README.md'];
|
|
469
|
+
|
|
470
|
+
// Check for badges that indicate good practices
|
|
471
|
+
if (readme.includes('coverage')) {
|
|
472
|
+
analysis.suggestions.push({
|
|
473
|
+
repo: repo.repo,
|
|
474
|
+
suggestion: 'code-coverage',
|
|
475
|
+
description: 'Implements code coverage tracking',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (readme.includes('CI/CD') || readme.includes('Actions')) {
|
|
479
|
+
analysis.suggestions.push({
|
|
480
|
+
repo: repo.repo,
|
|
481
|
+
suggestion: 'ci-cd',
|
|
482
|
+
description: 'Has CI/CD pipeline configured',
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Generate improvement suggestions based on analysis
|
|
489
|
+
if (analysis.architectures.length > 0) {
|
|
490
|
+
const archCounts = {};
|
|
491
|
+
for (const arch of analysis.architectures) {
|
|
492
|
+
archCounts[arch.pattern] = (archCounts[arch.pattern] || 0) + 1;
|
|
493
|
+
}
|
|
494
|
+
const mostCommon = Object.entries(archCounts).sort((a, b) => b[1] - a[1])[0];
|
|
495
|
+
if (mostCommon) {
|
|
496
|
+
analysis.suggestions.push({
|
|
497
|
+
type: 'architecture',
|
|
498
|
+
suggestion: `Consider using ${mostCommon[0]} pattern`,
|
|
499
|
+
count: mostCommon[1],
|
|
500
|
+
repos: analysis.architectures.filter(a => a.pattern === mostCommon[0]).map(a => a.repo),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (analysis.technologies.length > 0) {
|
|
506
|
+
const techCounts = {};
|
|
507
|
+
for (const tech of analysis.technologies) {
|
|
508
|
+
techCounts[tech.tech] = (techCounts[tech.tech] || 0) + 1;
|
|
509
|
+
}
|
|
510
|
+
const popular = Object.entries(techCounts)
|
|
511
|
+
.sort((a, b) => b[1] - a[1])
|
|
512
|
+
.slice(0, 3);
|
|
513
|
+
for (const [tech, count] of popular) {
|
|
514
|
+
analysis.suggestions.push({
|
|
515
|
+
type: 'technology',
|
|
516
|
+
suggestion: `Consider using ${tech}`,
|
|
517
|
+
count,
|
|
518
|
+
repos: analysis.technologies.filter(t => t.tech === tech).map(t => t.repo),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return analysis;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Save reference repositories analysis to steering/references/
|
|
528
|
+
* @param {object[]} repos - Fetched repository data
|
|
529
|
+
* @param {object} analysis - Analysis results
|
|
530
|
+
* @param {string} projectPath - Target project path
|
|
531
|
+
* @returns {string} Created file path
|
|
532
|
+
*/
|
|
533
|
+
async function saveReferenceRepos(repos, analysis, projectPath) {
|
|
534
|
+
const refsDir = path.join(projectPath, 'steering', 'references');
|
|
535
|
+
await fs.ensureDir(refsDir);
|
|
536
|
+
|
|
537
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
538
|
+
const filename = `github-references-${timestamp}.md`;
|
|
539
|
+
|
|
540
|
+
// Build markdown content
|
|
541
|
+
let content = `# GitHub Reference Repositories
|
|
542
|
+
|
|
543
|
+
> Analyzed on ${new Date().toISOString()}
|
|
544
|
+
|
|
545
|
+
## Referenced Repositories
|
|
546
|
+
|
|
547
|
+
`;
|
|
548
|
+
|
|
549
|
+
for (const repo of repos) {
|
|
550
|
+
if (repo.error) {
|
|
551
|
+
content += `### ❌ ${repo.source}\n\n`;
|
|
552
|
+
content += `Error: ${repo.error}\n\n`;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
content += `### ${repo.metadata.name || repo.repo}\n\n`;
|
|
557
|
+
content += `- **URL**: ${repo.url}\n`;
|
|
558
|
+
content += `- **Language**: ${repo.metadata.language || 'Unknown'}\n`;
|
|
559
|
+
content += `- **Stars**: ${repo.metadata.stars || 0}\n`;
|
|
560
|
+
if (repo.metadata.description) {
|
|
561
|
+
content += `- **Description**: ${repo.metadata.description}\n`;
|
|
562
|
+
}
|
|
563
|
+
if (repo.metadata.topics && repo.metadata.topics.length > 0) {
|
|
564
|
+
content += `- **Topics**: ${repo.metadata.topics.join(', ')}\n`;
|
|
565
|
+
}
|
|
566
|
+
if (repo.metadata.license) {
|
|
567
|
+
content += `- **License**: ${repo.metadata.license}\n`;
|
|
568
|
+
}
|
|
569
|
+
content += '\n';
|
|
570
|
+
|
|
571
|
+
// Structure
|
|
572
|
+
if (repo.structure.length > 0) {
|
|
573
|
+
content += '**Directory Structure:**\n\n';
|
|
574
|
+
content += '```\n';
|
|
575
|
+
for (const item of repo.structure.slice(0, 20)) {
|
|
576
|
+
content += `${item.type === 'dir' ? '📁' : '📄'} ${item.name}\n`;
|
|
577
|
+
}
|
|
578
|
+
if (repo.structure.length > 20) {
|
|
579
|
+
content += `... and ${repo.structure.length - 20} more items\n`;
|
|
580
|
+
}
|
|
581
|
+
content += '```\n\n';
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Analysis section
|
|
586
|
+
content += `## Analysis Results
|
|
587
|
+
|
|
588
|
+
### Architecture Patterns Detected
|
|
589
|
+
|
|
590
|
+
`;
|
|
591
|
+
|
|
592
|
+
if (analysis.architectures.length > 0) {
|
|
593
|
+
for (const arch of analysis.architectures) {
|
|
594
|
+
content += `- **${arch.pattern}** in \`${arch.repo}\`\n`;
|
|
595
|
+
content += ` - Evidence: ${arch.evidence.join(', ')}\n`;
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
content += '_No specific architecture patterns detected_\n';
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
content += `\n### Design Patterns
|
|
602
|
+
|
|
603
|
+
`;
|
|
604
|
+
|
|
605
|
+
if (analysis.patterns.length > 0) {
|
|
606
|
+
for (const pattern of analysis.patterns) {
|
|
607
|
+
content += `- **${pattern.pattern}** in \`${pattern.repo}\`\n`;
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
content += '_No specific design patterns detected_\n';
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
content += `\n### Technologies Used
|
|
614
|
+
|
|
615
|
+
`;
|
|
616
|
+
|
|
617
|
+
if (analysis.technologies.length > 0) {
|
|
618
|
+
const techByRepo = {};
|
|
619
|
+
for (const tech of analysis.technologies) {
|
|
620
|
+
if (!techByRepo[tech.repo]) techByRepo[tech.repo] = [];
|
|
621
|
+
techByRepo[tech.repo].push(tech.tech);
|
|
622
|
+
}
|
|
623
|
+
for (const [repo, techs] of Object.entries(techByRepo)) {
|
|
624
|
+
content += `- **${repo}**: ${techs.join(', ')}\n`;
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
content += '_No specific technologies detected_\n';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
content += `\n### Configurations
|
|
631
|
+
|
|
632
|
+
`;
|
|
633
|
+
|
|
634
|
+
if (analysis.configurations.length > 0) {
|
|
635
|
+
const configByRepo = {};
|
|
636
|
+
for (const config of analysis.configurations) {
|
|
637
|
+
if (!configByRepo[config.repo]) configByRepo[config.repo] = [];
|
|
638
|
+
configByRepo[config.repo].push(config.config);
|
|
639
|
+
}
|
|
640
|
+
for (const [repo, configs] of Object.entries(configByRepo)) {
|
|
641
|
+
content += `- **${repo}**: ${configs.join(', ')}\n`;
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
content += '_No specific configurations detected_\n';
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
content += `\n## Improvement Suggestions
|
|
648
|
+
|
|
649
|
+
Based on the referenced repositories, consider the following improvements:
|
|
650
|
+
|
|
651
|
+
`;
|
|
652
|
+
|
|
653
|
+
if (analysis.suggestions.length > 0) {
|
|
654
|
+
let i = 1;
|
|
655
|
+
for (const suggestion of analysis.suggestions) {
|
|
656
|
+
if (suggestion.type === 'architecture') {
|
|
657
|
+
content += `${i}. **Architecture**: ${suggestion.suggestion}\n`;
|
|
658
|
+
content += ` - Found in ${suggestion.count} repository(ies): ${suggestion.repos.join(', ')}\n\n`;
|
|
659
|
+
} else if (suggestion.type === 'technology') {
|
|
660
|
+
content += `${i}. **Technology**: ${suggestion.suggestion}\n`;
|
|
661
|
+
content += ` - Used by ${suggestion.count} repository(ies): ${suggestion.repos.join(', ')}\n\n`;
|
|
662
|
+
} else {
|
|
663
|
+
content += `${i}. **${suggestion.suggestion}**: ${suggestion.description}\n`;
|
|
664
|
+
content += ` - Found in: ${suggestion.repo}\n\n`;
|
|
665
|
+
}
|
|
666
|
+
i++;
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
content += '_No specific suggestions generated_\n';
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
content += `
|
|
673
|
+
---
|
|
674
|
+
*Generated by MUSUBI SDD - GitHub Reference Analysis*
|
|
675
|
+
`;
|
|
676
|
+
|
|
677
|
+
await fs.writeFile(path.join(refsDir, filename), content);
|
|
678
|
+
return filename;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Detect specification format from content and filename
|
|
683
|
+
*/
|
|
684
|
+
function detectSpecFormat(content, source) {
|
|
685
|
+
const ext = path.extname(source).toLowerCase();
|
|
686
|
+
if (ext === '.json') return 'json';
|
|
687
|
+
if (ext === '.yaml' || ext === '.yml') return 'yaml';
|
|
688
|
+
if (ext === '.md') return 'markdown';
|
|
689
|
+
if (ext === '.rst') return 'rst';
|
|
690
|
+
if (ext === '.html') return 'html';
|
|
691
|
+
|
|
692
|
+
// Try to detect from content
|
|
693
|
+
if (content.trim().startsWith('{')) return 'json';
|
|
694
|
+
if (content.includes('openapi:') || content.includes('swagger:')) return 'openapi';
|
|
695
|
+
if (content.includes('asyncapi:')) return 'asyncapi';
|
|
696
|
+
if (content.includes('# ')) return 'markdown';
|
|
697
|
+
|
|
698
|
+
return 'text';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Extract summary from specification content
|
|
703
|
+
*/
|
|
704
|
+
function extractSpecSummary(content) {
|
|
705
|
+
// Extract first heading and description
|
|
706
|
+
const lines = content.split('\n').slice(0, 50);
|
|
707
|
+
let title = '';
|
|
708
|
+
let description = '';
|
|
709
|
+
|
|
710
|
+
for (const line of lines) {
|
|
711
|
+
if (!title && line.startsWith('# ')) {
|
|
712
|
+
title = line.replace('# ', '').trim();
|
|
713
|
+
} else if (title && !description && line.trim() && !line.startsWith('#')) {
|
|
714
|
+
description = line.trim().slice(0, 200);
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { title, description };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Save external specification reference to steering/specs/
|
|
724
|
+
*/
|
|
725
|
+
async function saveSpecReference(specResult, projectPath) {
|
|
726
|
+
const specsDir = path.join(projectPath, 'steering', 'specs');
|
|
727
|
+
await fs.ensureDir(specsDir);
|
|
728
|
+
|
|
729
|
+
// Create spec reference file
|
|
730
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
731
|
+
const safeName = specResult.metadata.summary?.title
|
|
732
|
+
? specResult.metadata.summary.title
|
|
733
|
+
.toLowerCase()
|
|
734
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
735
|
+
.slice(0, 50)
|
|
736
|
+
: 'external-spec';
|
|
737
|
+
const filename = `${safeName}-${timestamp}.md`;
|
|
738
|
+
|
|
739
|
+
const refContent = `# External Specification Reference
|
|
740
|
+
|
|
741
|
+
## Source Information
|
|
742
|
+
|
|
743
|
+
- **Type**: ${specResult.type}
|
|
744
|
+
- **Source**: ${specResult.source}
|
|
745
|
+
- **Format**: ${specResult.metadata.format || 'unknown'}
|
|
746
|
+
- **Fetched**: ${specResult.metadata.fetchedAt || specResult.metadata.readAt || 'N/A'}
|
|
747
|
+
|
|
748
|
+
## Summary
|
|
749
|
+
|
|
750
|
+
${specResult.metadata.summary?.title ? `**Title**: ${specResult.metadata.summary.title}` : ''}
|
|
751
|
+
${specResult.metadata.summary?.description ? `\n**Description**: ${specResult.metadata.summary.description}` : ''}
|
|
752
|
+
|
|
753
|
+
## Integration Notes
|
|
754
|
+
|
|
755
|
+
This specification is used as a reference for:
|
|
756
|
+
- Requirements analysis
|
|
757
|
+
- Architecture design
|
|
758
|
+
- API design
|
|
759
|
+
- Compliance validation
|
|
760
|
+
|
|
761
|
+
## Original Content
|
|
762
|
+
|
|
763
|
+
\`\`\`${specResult.metadata.format || 'text'}
|
|
764
|
+
${specResult.content?.slice(0, 5000) || 'Content not available'}${specResult.content?.length > 5000 ? '\n\n... (truncated, see original source)' : ''}
|
|
765
|
+
\`\`\`
|
|
766
|
+
|
|
767
|
+
---
|
|
768
|
+
*Generated by MUSUBI SDD - External Specification Reference*
|
|
769
|
+
`;
|
|
770
|
+
|
|
771
|
+
await fs.writeFile(path.join(specsDir, filename), refContent);
|
|
772
|
+
return filename;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Language recommendation engine
|
|
777
|
+
* @param {object} requirements - User's answers about app types, performance, expertise
|
|
778
|
+
* @returns {Array} Recommended languages with reasons
|
|
779
|
+
*/
|
|
780
|
+
function recommendLanguages(requirements) {
|
|
781
|
+
const { appTypes, performanceNeeds, teamExpertise } = requirements;
|
|
782
|
+
const scores = {};
|
|
783
|
+
const reasons = {};
|
|
784
|
+
|
|
785
|
+
// Initialize scores
|
|
786
|
+
const allLangs = [
|
|
787
|
+
'javascript',
|
|
788
|
+
'python',
|
|
789
|
+
'rust',
|
|
790
|
+
'go',
|
|
791
|
+
'java',
|
|
792
|
+
'csharp',
|
|
793
|
+
'cpp',
|
|
794
|
+
'swift',
|
|
795
|
+
'ruby',
|
|
796
|
+
'php',
|
|
797
|
+
];
|
|
798
|
+
for (const lang of allLangs) {
|
|
799
|
+
scores[lang] = 0;
|
|
800
|
+
reasons[lang] = [];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Score by application type
|
|
804
|
+
const appTypeScores = {
|
|
805
|
+
'web-frontend': { javascript: 10, reason: 'Best ecosystem for web frontend' },
|
|
806
|
+
'web-backend': {
|
|
807
|
+
javascript: 6,
|
|
808
|
+
python: 7,
|
|
809
|
+
go: 8,
|
|
810
|
+
rust: 7,
|
|
811
|
+
java: 7,
|
|
812
|
+
csharp: 6,
|
|
813
|
+
ruby: 5,
|
|
814
|
+
php: 5,
|
|
815
|
+
reason: 'Strong backend frameworks',
|
|
816
|
+
},
|
|
817
|
+
cli: { rust: 9, go: 9, python: 6, reason: 'Fast startup, single binary' },
|
|
818
|
+
desktop: { rust: 7, csharp: 8, cpp: 7, swift: 6, java: 6, reason: 'Native GUI support' },
|
|
819
|
+
mobile: { swift: 9, java: 8, javascript: 6, reason: 'Mobile platform support' },
|
|
820
|
+
data: { python: 10, rust: 6, reason: 'Rich data science ecosystem' },
|
|
821
|
+
ml: { python: 10, rust: 5, cpp: 5, reason: 'ML/AI libraries and frameworks' },
|
|
822
|
+
embedded: { rust: 10, cpp: 9, reason: 'Memory safety, no runtime' },
|
|
823
|
+
game: { cpp: 9, csharp: 8, rust: 6, reason: 'Game engine support' },
|
|
824
|
+
systems: { rust: 10, go: 8, cpp: 9, reason: 'Systems programming' },
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
for (const appType of appTypes || []) {
|
|
828
|
+
const typeScores = appTypeScores[appType];
|
|
829
|
+
if (typeScores) {
|
|
830
|
+
for (const [lang, score] of Object.entries(typeScores)) {
|
|
831
|
+
if (typeof score === 'number') {
|
|
832
|
+
scores[lang] += score;
|
|
833
|
+
if (!reasons[lang].includes(typeScores.reason)) {
|
|
834
|
+
reasons[lang].push(typeScores.reason);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Score by performance needs
|
|
842
|
+
if (performanceNeeds === 'high') {
|
|
843
|
+
scores.rust += 8;
|
|
844
|
+
scores.go += 6;
|
|
845
|
+
scores.cpp += 7;
|
|
846
|
+
reasons.rust.push('High performance, zero-cost abstractions');
|
|
847
|
+
reasons.go.push('Fast compilation, efficient runtime');
|
|
848
|
+
} else if (performanceNeeds === 'rapid') {
|
|
849
|
+
scores.python += 5;
|
|
850
|
+
scores.javascript += 5;
|
|
851
|
+
scores.ruby += 4;
|
|
852
|
+
reasons.python.push('Rapid development, extensive libraries');
|
|
853
|
+
reasons.javascript.push('Fast iteration, universal runtime');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Boost by team expertise
|
|
857
|
+
for (const lang of teamExpertise || []) {
|
|
858
|
+
scores[lang] += 5;
|
|
859
|
+
reasons[lang].push('Team has expertise');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Sort and return top recommendations
|
|
863
|
+
const sorted = Object.entries(scores)
|
|
864
|
+
.filter(([, score]) => score > 0)
|
|
865
|
+
.sort((a, b) => b[1] - a[1])
|
|
866
|
+
.slice(0, 3);
|
|
867
|
+
|
|
868
|
+
const langInfo = {
|
|
869
|
+
javascript: { name: 'JavaScript/TypeScript', emoji: '🟨' },
|
|
870
|
+
python: { name: 'Python', emoji: '🐍' },
|
|
871
|
+
rust: { name: 'Rust', emoji: '🦀' },
|
|
872
|
+
go: { name: 'Go', emoji: '🐹' },
|
|
873
|
+
java: { name: 'Java/Kotlin', emoji: '☕' },
|
|
874
|
+
csharp: { name: 'C#/.NET', emoji: '💜' },
|
|
875
|
+
cpp: { name: 'C/C++', emoji: '⚙️' },
|
|
876
|
+
swift: { name: 'Swift', emoji: '🍎' },
|
|
877
|
+
ruby: { name: 'Ruby', emoji: '💎' },
|
|
878
|
+
php: { name: 'PHP', emoji: '🐘' },
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
return sorted.map(([lang]) => ({
|
|
882
|
+
value: lang,
|
|
883
|
+
name: langInfo[lang].name,
|
|
884
|
+
emoji: langInfo[lang].emoji,
|
|
885
|
+
reason: reasons[lang].slice(0, 2).join('; ') || 'General purpose',
|
|
886
|
+
score: scores[lang],
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
|
|
29
890
|
/**
|
|
30
891
|
* Main initialization function
|
|
31
892
|
* @param {object} agent - Agent definition from registry
|
|
32
893
|
* @param {string} agentKey - Agent key (e.g., 'claude-code', 'cursor')
|
|
894
|
+
* @param {object} options - Command options (spec, workspace, etc.)
|
|
33
895
|
*/
|
|
34
|
-
async function main(agent, agentKey) {
|
|
896
|
+
async function main(agent, agentKey, options = {}) {
|
|
35
897
|
// Dynamic import for inquirer (ESM module)
|
|
36
898
|
const inquirer = await import('inquirer');
|
|
37
899
|
|
|
@@ -45,6 +907,60 @@ async function main(agent, agentKey) {
|
|
|
45
907
|
console.log(chalk.blue.bold('\n🎯 MUSUBI - Ultimate Specification Driven Development\n'));
|
|
46
908
|
console.log(chalk.white(`Initializing for: ${chalk.bold(agent.label)}\n`));
|
|
47
909
|
|
|
910
|
+
// Handle external specification reference
|
|
911
|
+
let externalSpec = null;
|
|
912
|
+
if (options.spec) {
|
|
913
|
+
console.log(chalk.cyan('📄 Fetching external specification...'));
|
|
914
|
+
externalSpec = await fetchExternalSpec(options.spec);
|
|
915
|
+
if (externalSpec.error) {
|
|
916
|
+
console.log(chalk.yellow(`⚠️ Warning: ${externalSpec.error}`));
|
|
917
|
+
} else {
|
|
918
|
+
console.log(
|
|
919
|
+
chalk.green(
|
|
920
|
+
`✓ Loaded specification: ${externalSpec.metadata.summary?.title || externalSpec.source}`
|
|
921
|
+
)
|
|
922
|
+
);
|
|
923
|
+
console.log(
|
|
924
|
+
chalk.gray(` Format: ${externalSpec.metadata.format}, Type: ${externalSpec.type}\n`)
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Handle GitHub repository references
|
|
930
|
+
let referenceRepos = null;
|
|
931
|
+
let repoAnalysis = null;
|
|
932
|
+
if (options.references && options.references.length > 0) {
|
|
933
|
+
console.log(chalk.cyan(`\n📚 Fetching ${options.references.length} GitHub reference(s)...`));
|
|
934
|
+
referenceRepos = await fetchGitHubRepos(options.references);
|
|
935
|
+
|
|
936
|
+
// Analyze repositories for improvements
|
|
937
|
+
const validRepos = referenceRepos.filter(r => !r.error);
|
|
938
|
+
if (validRepos.length > 0) {
|
|
939
|
+
console.log(chalk.cyan('\n🔍 Analyzing repositories for patterns and improvements...'));
|
|
940
|
+
repoAnalysis = analyzeReposForImprovements(validRepos);
|
|
941
|
+
|
|
942
|
+
if (repoAnalysis.suggestions.length > 0) {
|
|
943
|
+
console.log(
|
|
944
|
+
chalk.green(`\n💡 Found ${repoAnalysis.suggestions.length} improvement suggestion(s):`)
|
|
945
|
+
);
|
|
946
|
+
for (const suggestion of repoAnalysis.suggestions.slice(0, 5)) {
|
|
947
|
+
if (suggestion.type === 'architecture') {
|
|
948
|
+
console.log(
|
|
949
|
+
chalk.white(` • ${suggestion.suggestion} (from ${suggestion.repos.join(', ')})`)
|
|
950
|
+
);
|
|
951
|
+
} else if (suggestion.type === 'technology') {
|
|
952
|
+
console.log(
|
|
953
|
+
chalk.white(` • ${suggestion.suggestion} (used by ${suggestion.count} repo(s))`)
|
|
954
|
+
);
|
|
955
|
+
} else {
|
|
956
|
+
console.log(chalk.white(` • ${suggestion.suggestion}`));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
console.log('');
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
48
964
|
// Check if already initialized for this agent
|
|
49
965
|
const agentDir = agent.layout.agentDir;
|
|
50
966
|
if (fs.existsSync(agentDir)) {
|
|
@@ -92,17 +1008,179 @@ async function main(agent, agentKey) {
|
|
|
92
1008
|
],
|
|
93
1009
|
default: 'en',
|
|
94
1010
|
},
|
|
1011
|
+
{
|
|
1012
|
+
type: 'list',
|
|
1013
|
+
name: 'projectStructure',
|
|
1014
|
+
message: 'Project structure:',
|
|
1015
|
+
choices: [
|
|
1016
|
+
{ name: 'Single package', value: 'single' },
|
|
1017
|
+
{ name: 'Workspace / Monorepo', value: 'workspace' },
|
|
1018
|
+
{ name: 'Microservices', value: 'microservices' },
|
|
1019
|
+
],
|
|
1020
|
+
default: options.workspace ? 'workspace' : 'single',
|
|
1021
|
+
},
|
|
95
1022
|
{
|
|
96
1023
|
type: 'list',
|
|
97
1024
|
name: 'projectType',
|
|
98
1025
|
message: 'Project type:',
|
|
99
1026
|
choices: ['Greenfield (0→1)', 'Brownfield (1→n)', 'Both'],
|
|
100
1027
|
},
|
|
1028
|
+
{
|
|
1029
|
+
type: 'list',
|
|
1030
|
+
name: 'techStackApproach',
|
|
1031
|
+
message: 'Technology stack approach:',
|
|
1032
|
+
choices: [
|
|
1033
|
+
{ name: 'Single language', value: 'single' },
|
|
1034
|
+
{ name: 'Multiple languages', value: 'multiple' },
|
|
1035
|
+
{ name: 'Undecided (decide later)', value: 'undecided' },
|
|
1036
|
+
{ name: 'Help me decide (recommend based on requirements)', value: 'recommend' },
|
|
1037
|
+
],
|
|
1038
|
+
default: 'single',
|
|
1039
|
+
},
|
|
1040
|
+
];
|
|
1041
|
+
|
|
1042
|
+
// Template selection if project structure is workspace or microservices
|
|
1043
|
+
const templatePrompts = [
|
|
1044
|
+
{
|
|
1045
|
+
type: 'list',
|
|
1046
|
+
name: 'archTemplate',
|
|
1047
|
+
message: 'Select architecture template:',
|
|
1048
|
+
choices: answers => {
|
|
1049
|
+
if (answers.projectStructure === 'workspace') {
|
|
1050
|
+
return [
|
|
1051
|
+
{ name: 'Basic Workspace (packages/)', value: 'workspace-basic' },
|
|
1052
|
+
{ name: 'Layered (core/, api/, web/)', value: 'workspace-layered' },
|
|
1053
|
+
{ name: 'Domain-Driven (domains/, shared/)', value: 'workspace-ddd' },
|
|
1054
|
+
{ name: 'Full Stack (frontend/, backend/, shared/)', value: 'workspace-fullstack' },
|
|
1055
|
+
];
|
|
1056
|
+
} else if (answers.projectStructure === 'microservices') {
|
|
1057
|
+
return [
|
|
1058
|
+
{ name: 'Basic Services (services/)', value: 'microservices-basic' },
|
|
1059
|
+
{ name: 'Gateway + Services', value: 'microservices-gateway' },
|
|
1060
|
+
{ name: 'Event-Driven (services/, events/)', value: 'microservices-event' },
|
|
1061
|
+
];
|
|
1062
|
+
}
|
|
1063
|
+
return [{ name: 'Standard', value: 'standard' }];
|
|
1064
|
+
},
|
|
1065
|
+
when: answers =>
|
|
1066
|
+
answers.projectStructure === 'workspace' || answers.projectStructure === 'microservices',
|
|
1067
|
+
},
|
|
1068
|
+
];
|
|
1069
|
+
|
|
1070
|
+
// Language selection based on approach
|
|
1071
|
+
const languageChoices = [
|
|
1072
|
+
{ name: 'JavaScript/TypeScript', value: 'javascript' },
|
|
1073
|
+
{ name: 'Python', value: 'python' },
|
|
1074
|
+
{ name: 'Rust', value: 'rust' },
|
|
1075
|
+
{ name: 'Go', value: 'go' },
|
|
1076
|
+
{ name: 'Java/Kotlin', value: 'java' },
|
|
1077
|
+
{ name: 'C#/.NET', value: 'csharp' },
|
|
1078
|
+
{ name: 'C/C++', value: 'cpp' },
|
|
1079
|
+
{ name: 'Swift', value: 'swift' },
|
|
1080
|
+
{ name: 'Ruby', value: 'ruby' },
|
|
1081
|
+
{ name: 'PHP', value: 'php' },
|
|
1082
|
+
{ name: 'Other', value: 'other' },
|
|
1083
|
+
];
|
|
1084
|
+
|
|
1085
|
+
// Recommendation questions for 'Help me decide' mode
|
|
1086
|
+
const recommendationPrompts = [
|
|
1087
|
+
{
|
|
1088
|
+
type: 'checkbox',
|
|
1089
|
+
name: 'appTypes',
|
|
1090
|
+
message: 'What type of application(s) are you building?',
|
|
1091
|
+
choices: [
|
|
1092
|
+
{ name: 'Web Frontend (SPA, SSR)', value: 'web-frontend' },
|
|
1093
|
+
{ name: 'Web Backend / API', value: 'web-backend' },
|
|
1094
|
+
{ name: 'CLI Tool', value: 'cli' },
|
|
1095
|
+
{ name: 'Desktop Application', value: 'desktop' },
|
|
1096
|
+
{ name: 'Mobile App', value: 'mobile' },
|
|
1097
|
+
{ name: 'Data Pipeline / ETL', value: 'data' },
|
|
1098
|
+
{ name: 'AI/ML Application', value: 'ml' },
|
|
1099
|
+
{ name: 'Embedded / IoT', value: 'embedded' },
|
|
1100
|
+
{ name: 'Game Development', value: 'game' },
|
|
1101
|
+
{ name: 'Systems / Infrastructure', value: 'systems' },
|
|
1102
|
+
],
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
type: 'list',
|
|
1106
|
+
name: 'performanceNeeds',
|
|
1107
|
+
message: 'Performance requirements:',
|
|
1108
|
+
choices: [
|
|
1109
|
+
{ name: 'High performance / Low latency critical', value: 'high' },
|
|
1110
|
+
{ name: 'Moderate (typical web app)', value: 'moderate' },
|
|
1111
|
+
{ name: 'Rapid development prioritized', value: 'rapid' },
|
|
1112
|
+
],
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
type: 'checkbox',
|
|
1116
|
+
name: 'teamExpertise',
|
|
1117
|
+
message: 'Team expertise (select all that apply):',
|
|
1118
|
+
choices: languageChoices.filter(c => c.value !== 'other'),
|
|
1119
|
+
},
|
|
101
1120
|
];
|
|
102
1121
|
|
|
103
|
-
//
|
|
1122
|
+
// Get initial answers to determine language prompts
|
|
1123
|
+
const initialAnswers = await inquirer.default.prompt(prompts);
|
|
1124
|
+
let answers = { ...initialAnswers };
|
|
1125
|
+
|
|
1126
|
+
// Handle tech stack approach
|
|
1127
|
+
if (answers.techStackApproach === 'single') {
|
|
1128
|
+
const langAnswer = await inquirer.default.prompt([
|
|
1129
|
+
{
|
|
1130
|
+
type: 'list',
|
|
1131
|
+
name: 'primaryLanguage',
|
|
1132
|
+
message: 'Select primary language:',
|
|
1133
|
+
choices: languageChoices,
|
|
1134
|
+
},
|
|
1135
|
+
]);
|
|
1136
|
+
answers.languages = [langAnswer.primaryLanguage];
|
|
1137
|
+
} else if (answers.techStackApproach === 'multiple') {
|
|
1138
|
+
const langAnswer = await inquirer.default.prompt([
|
|
1139
|
+
{
|
|
1140
|
+
type: 'checkbox',
|
|
1141
|
+
name: 'languages',
|
|
1142
|
+
message: 'Select languages (check all that apply):',
|
|
1143
|
+
choices: languageChoices,
|
|
1144
|
+
validate: input => (input.length > 0 ? true : 'Select at least one language'),
|
|
1145
|
+
},
|
|
1146
|
+
]);
|
|
1147
|
+
answers.languages = langAnswer.languages;
|
|
1148
|
+
} else if (answers.techStackApproach === 'recommend') {
|
|
1149
|
+
// Ask recommendation questions
|
|
1150
|
+
const recAnswers = await inquirer.default.prompt(recommendationPrompts);
|
|
1151
|
+
const recommended = recommendLanguages(recAnswers);
|
|
1152
|
+
|
|
1153
|
+
console.log(chalk.cyan('\n📊 Recommended languages based on your requirements:\n'));
|
|
1154
|
+
for (const rec of recommended) {
|
|
1155
|
+
console.log(chalk.white(` ${rec.emoji} ${chalk.bold(rec.name)}: ${rec.reason}`));
|
|
1156
|
+
}
|
|
1157
|
+
console.log('');
|
|
1158
|
+
|
|
1159
|
+
const confirmAnswer = await inquirer.default.prompt([
|
|
1160
|
+
{
|
|
1161
|
+
type: 'checkbox',
|
|
1162
|
+
name: 'languages',
|
|
1163
|
+
message: 'Confirm languages to use:',
|
|
1164
|
+
choices: recommended.map(r => ({ name: r.name, value: r.value, checked: true })),
|
|
1165
|
+
},
|
|
1166
|
+
]);
|
|
1167
|
+
answers.languages = confirmAnswer.languages;
|
|
1168
|
+
answers.recommendationContext = recAnswers;
|
|
1169
|
+
} else {
|
|
1170
|
+
// undecided
|
|
1171
|
+
answers.languages = ['undecided'];
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Ask template questions if workspace or microservices
|
|
1175
|
+
if (answers.projectStructure === 'workspace' || answers.projectStructure === 'microservices') {
|
|
1176
|
+
const templateAnswer = await inquirer.default.prompt(templatePrompts);
|
|
1177
|
+
answers = { ...answers, ...templateAnswer };
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// Continue with remaining prompts
|
|
1181
|
+
const remainingPrompts = [];
|
|
104
1182
|
if (agentKey === 'claude-code' && agent.layout.skillsDir) {
|
|
105
|
-
|
|
1183
|
+
remainingPrompts.push({
|
|
106
1184
|
type: 'checkbox',
|
|
107
1185
|
name: 'skills',
|
|
108
1186
|
message: 'Select skills to install (all recommended):',
|
|
@@ -147,7 +1225,7 @@ async function main(agent, agentKey) {
|
|
|
147
1225
|
});
|
|
148
1226
|
}
|
|
149
1227
|
|
|
150
|
-
|
|
1228
|
+
remainingPrompts.push(
|
|
151
1229
|
{
|
|
152
1230
|
type: 'confirm',
|
|
153
1231
|
name: 'createSteering',
|
|
@@ -162,7 +1240,8 @@ async function main(agent, agentKey) {
|
|
|
162
1240
|
}
|
|
163
1241
|
);
|
|
164
1242
|
|
|
165
|
-
const
|
|
1243
|
+
const finalAnswers = await inquirer.default.prompt(remainingPrompts);
|
|
1244
|
+
answers = { ...answers, ...finalAnswers };
|
|
166
1245
|
|
|
167
1246
|
console.log(chalk.green('\n✨ Initializing MUSUBI...\n'));
|
|
168
1247
|
|
|
@@ -253,16 +1332,44 @@ async function main(agent, agentKey) {
|
|
|
253
1332
|
|
|
254
1333
|
// Generate steering context
|
|
255
1334
|
if (answers.createSteering) {
|
|
256
|
-
await generateSteering(answers);
|
|
1335
|
+
await generateSteering(answers, externalSpec);
|
|
257
1336
|
console.log(chalk.green(' Generated steering context'));
|
|
258
1337
|
}
|
|
259
1338
|
|
|
1339
|
+
// Save external specification reference
|
|
1340
|
+
if (externalSpec && !externalSpec.error) {
|
|
1341
|
+
const specFilename = await saveSpecReference(externalSpec, process.cwd());
|
|
1342
|
+
console.log(chalk.green(` Saved specification reference: steering/specs/${specFilename}`));
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// Save GitHub repository references and analysis
|
|
1346
|
+
if (referenceRepos && referenceRepos.length > 0 && repoAnalysis) {
|
|
1347
|
+
const refFilename = await saveReferenceRepos(referenceRepos, repoAnalysis, process.cwd());
|
|
1348
|
+
console.log(chalk.green(` Saved GitHub references: steering/references/${refFilename}`));
|
|
1349
|
+
}
|
|
1350
|
+
|
|
260
1351
|
// Create constitution
|
|
261
1352
|
if (answers.createConstitution) {
|
|
262
1353
|
await createConstitution();
|
|
263
1354
|
console.log(chalk.green(' Created constitutional governance'));
|
|
264
1355
|
}
|
|
265
1356
|
|
|
1357
|
+
// Generate language-specific dependency files (for single-package projects)
|
|
1358
|
+
if (answers.projectStructure !== 'workspace' && answers.projectStructure !== 'microservices') {
|
|
1359
|
+
const primaryLang =
|
|
1360
|
+
answers.languages && answers.languages[0] !== 'undecided' ? answers.languages[0] : null;
|
|
1361
|
+
if (primaryLang) {
|
|
1362
|
+
await generateDependencyFiles(primaryLang, answers);
|
|
1363
|
+
console.log(chalk.green(` Generated ${primaryLang} project files`));
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Generate reference architecture template if specified
|
|
1368
|
+
if (options.template) {
|
|
1369
|
+
await generateArchitectureTemplate(options.template, answers);
|
|
1370
|
+
console.log(chalk.green(` Applied ${options.template} architecture template`));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
266
1373
|
// Create README
|
|
267
1374
|
await createReadme(answers, agent, agentKey);
|
|
268
1375
|
console.log(chalk.green(` Created ${agent.layout.docFile || 'MUSUBI.md'} guide`));
|
|
@@ -338,12 +1445,13 @@ async function copyAgentsFile(agent) {
|
|
|
338
1445
|
}
|
|
339
1446
|
}
|
|
340
1447
|
|
|
341
|
-
async function generateSteering(answers) {
|
|
1448
|
+
async function generateSteering(answers, externalSpec = null) {
|
|
342
1449
|
const steeringTemplates = path.join(SHARED_TEMPLATE_DIR, 'steering');
|
|
343
1450
|
const locale = answers.locale || 'en';
|
|
1451
|
+
const languages = answers.languages || ['undecided'];
|
|
344
1452
|
|
|
345
1453
|
// Copy and customize steering files
|
|
346
|
-
const files = ['structure.md', '
|
|
1454
|
+
const files = ['structure.md', 'product.md'];
|
|
347
1455
|
for (const file of files) {
|
|
348
1456
|
// Try locale-specific file first (e.g., structure.ja.md)
|
|
349
1457
|
let templatePath = path.join(steeringTemplates, file.replace('.md', `.${locale}.md`));
|
|
@@ -371,15 +1479,849 @@ async function generateSteering(answers) {
|
|
|
371
1479
|
await fs.writeFile(path.join('steering', outputFile), content);
|
|
372
1480
|
}
|
|
373
1481
|
|
|
374
|
-
//
|
|
1482
|
+
// Generate tech.md based on selected languages
|
|
1483
|
+
const techContent = generateTechMd(languages, answers, locale);
|
|
1484
|
+
const techFile = locale !== 'en' ? `tech.${locale}.md` : 'tech.md';
|
|
1485
|
+
await fs.writeFile(path.join('steering', techFile), techContent);
|
|
1486
|
+
|
|
1487
|
+
// Build external specification section for project.yml
|
|
1488
|
+
let externalSpecYml = '';
|
|
1489
|
+
if (externalSpec && !externalSpec.error) {
|
|
1490
|
+
externalSpecYml = `
|
|
1491
|
+
# External Specification Reference
|
|
1492
|
+
external_specs:
|
|
1493
|
+
- source: "${externalSpec.source}"
|
|
1494
|
+
type: ${externalSpec.type}
|
|
1495
|
+
format: ${externalSpec.metadata.format || 'unknown'}
|
|
1496
|
+
title: "${externalSpec.metadata.summary?.title || 'External Specification'}"
|
|
1497
|
+
fetched_at: ${externalSpec.metadata.fetchedAt || externalSpec.metadata.readAt || 'N/A'}
|
|
1498
|
+
`;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Create project.yml with locale, language settings, and external spec
|
|
375
1502
|
const projectYml = `# MUSUBI Project Configuration
|
|
376
1503
|
name: ${answers.projectName}
|
|
377
1504
|
description: ${answers.description}
|
|
378
1505
|
locale: ${locale}
|
|
379
1506
|
version: "0.1.0"
|
|
380
1507
|
created: ${new Date().toISOString().split('T')[0]}
|
|
381
|
-
|
|
1508
|
+
|
|
1509
|
+
# Technology Stack
|
|
1510
|
+
tech_stack:
|
|
1511
|
+
approach: ${answers.techStackApproach}
|
|
1512
|
+
languages:
|
|
1513
|
+
${languages[0] === 'undecided' ? ' - undecided # To be determined' : languages.map(l => ` - ${l}`).join('\n')}
|
|
1514
|
+
${externalSpecYml}`;
|
|
382
1515
|
await fs.writeFile(path.join('steering', 'project.yml'), projectYml);
|
|
1516
|
+
|
|
1517
|
+
// Generate workspace structure if applicable
|
|
1518
|
+
if (answers.projectStructure === 'workspace' || answers.projectStructure === 'microservices') {
|
|
1519
|
+
await generateWorkspaceStructure(answers);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Generate workspace/monorepo structure based on template
|
|
1525
|
+
*/
|
|
1526
|
+
async function generateWorkspaceStructure(answers) {
|
|
1527
|
+
const template = answers.archTemplate || 'workspace-basic';
|
|
1528
|
+
const languages = answers.languages || ['javascript'];
|
|
1529
|
+
const primaryLang = languages[0];
|
|
1530
|
+
|
|
1531
|
+
const structures = {
|
|
1532
|
+
'workspace-basic': {
|
|
1533
|
+
dirs: ['packages/', 'packages/core/', 'packages/cli/', 'packages/web/'],
|
|
1534
|
+
files: {
|
|
1535
|
+
'packages/README.md': '# Packages\n\nThis workspace contains multiple packages.\n',
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
'workspace-layered': {
|
|
1539
|
+
dirs: ['core/', 'api/', 'web/', 'shared/', 'tools/'],
|
|
1540
|
+
files: {
|
|
1541
|
+
'core/README.md': '# Core\n\nBusiness logic and domain models.\n',
|
|
1542
|
+
'api/README.md': '# API\n\nREST/GraphQL API layer.\n',
|
|
1543
|
+
'web/README.md': '# Web\n\nFrontend application.\n',
|
|
1544
|
+
'shared/README.md': '# Shared\n\nShared utilities and types.\n',
|
|
1545
|
+
},
|
|
1546
|
+
},
|
|
1547
|
+
'workspace-ddd': {
|
|
1548
|
+
dirs: [
|
|
1549
|
+
'domains/',
|
|
1550
|
+
'domains/identity/',
|
|
1551
|
+
'domains/catalog/',
|
|
1552
|
+
'shared/',
|
|
1553
|
+
'shared/kernel/',
|
|
1554
|
+
'infrastructure/',
|
|
1555
|
+
],
|
|
1556
|
+
files: {
|
|
1557
|
+
'domains/README.md': '# Domains\n\nDomain-driven design bounded contexts.\n',
|
|
1558
|
+
'shared/kernel/README.md': '# Shared Kernel\n\nCore abstractions shared across domains.\n',
|
|
1559
|
+
'infrastructure/README.md': '# Infrastructure\n\nCross-cutting infrastructure concerns.\n',
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1562
|
+
'workspace-fullstack': {
|
|
1563
|
+
dirs: ['frontend/', 'backend/', 'shared/', 'e2e/', 'docs/'],
|
|
1564
|
+
files: {
|
|
1565
|
+
'frontend/README.md': '# Frontend\n\nClient-side application.\n',
|
|
1566
|
+
'backend/README.md': '# Backend\n\nServer-side application.\n',
|
|
1567
|
+
'shared/README.md': '# Shared\n\nShared types and utilities.\n',
|
|
1568
|
+
'e2e/README.md': '# E2E Tests\n\nEnd-to-end test suite.\n',
|
|
1569
|
+
},
|
|
1570
|
+
},
|
|
1571
|
+
'microservices-basic': {
|
|
1572
|
+
dirs: ['services/', 'services/auth/', 'services/api/', 'services/worker/', 'libs/'],
|
|
1573
|
+
files: {
|
|
1574
|
+
'services/README.md': '# Services\n\nMicroservices directory.\n',
|
|
1575
|
+
'libs/README.md': '# Libraries\n\nShared libraries across services.\n',
|
|
1576
|
+
},
|
|
1577
|
+
},
|
|
1578
|
+
'microservices-gateway': {
|
|
1579
|
+
dirs: ['gateway/', 'services/', 'services/users/', 'services/products/', 'shared/'],
|
|
1580
|
+
files: {
|
|
1581
|
+
'gateway/README.md': '# API Gateway\n\nEntry point for all API requests.\n',
|
|
1582
|
+
'services/README.md': '# Services\n\nBackend microservices.\n',
|
|
1583
|
+
},
|
|
1584
|
+
},
|
|
1585
|
+
'microservices-event': {
|
|
1586
|
+
dirs: [
|
|
1587
|
+
'services/',
|
|
1588
|
+
'services/order/',
|
|
1589
|
+
'services/inventory/',
|
|
1590
|
+
'events/',
|
|
1591
|
+
'events/schemas/',
|
|
1592
|
+
'infrastructure/',
|
|
1593
|
+
],
|
|
1594
|
+
files: {
|
|
1595
|
+
'services/README.md': '# Services\n\nEvent-driven microservices.\n',
|
|
1596
|
+
'events/README.md': '# Events\n\nEvent schemas and contracts.\n',
|
|
1597
|
+
'events/schemas/README.md': '# Event Schemas\n\nAsyncAPI/CloudEvents schemas.\n',
|
|
1598
|
+
},
|
|
1599
|
+
},
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
const structure = structures[template] || structures['workspace-basic'];
|
|
1603
|
+
|
|
1604
|
+
// Create directories
|
|
1605
|
+
for (const dir of structure.dirs) {
|
|
1606
|
+
await fs.ensureDir(dir);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// Create files
|
|
1610
|
+
for (const [file, content] of Object.entries(structure.files)) {
|
|
1611
|
+
await fs.writeFile(file, content);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Generate language-specific workspace config
|
|
1615
|
+
await generateWorkspaceConfig(primaryLang, template, answers);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Generate language-specific workspace configuration files
|
|
1620
|
+
*/
|
|
1621
|
+
async function generateWorkspaceConfig(primaryLang, template, answers) {
|
|
1622
|
+
const projectName = answers.projectName || 'my-project';
|
|
1623
|
+
|
|
1624
|
+
if (primaryLang === 'javascript') {
|
|
1625
|
+
// Generate pnpm-workspace.yaml or npm workspaces in package.json
|
|
1626
|
+
const workspaceConfig =
|
|
1627
|
+
template.startsWith('workspace') || template.startsWith('microservices')
|
|
1628
|
+
? `packages:
|
|
1629
|
+
- 'packages/*'
|
|
1630
|
+
- 'services/*'
|
|
1631
|
+
- 'shared'
|
|
1632
|
+
- 'libs/*'
|
|
1633
|
+
`
|
|
1634
|
+
: '';
|
|
1635
|
+
if (workspaceConfig) {
|
|
1636
|
+
await fs.writeFile('pnpm-workspace.yaml', workspaceConfig);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Root package.json with workspaces
|
|
1640
|
+
const rootPackageJson = {
|
|
1641
|
+
name: projectName,
|
|
1642
|
+
version: '0.0.0',
|
|
1643
|
+
private: true,
|
|
1644
|
+
workspaces: ['packages/*', 'services/*', 'shared', 'libs/*'],
|
|
1645
|
+
scripts: {
|
|
1646
|
+
build: 'pnpm -r build',
|
|
1647
|
+
test: 'pnpm -r test',
|
|
1648
|
+
lint: 'pnpm -r lint',
|
|
1649
|
+
},
|
|
1650
|
+
devDependencies: {
|
|
1651
|
+
typescript: '^5.0.0',
|
|
1652
|
+
},
|
|
1653
|
+
};
|
|
1654
|
+
await fs.writeFile('package.json', JSON.stringify(rootPackageJson, null, 2) + '\n');
|
|
1655
|
+
} else if (primaryLang === 'rust') {
|
|
1656
|
+
// Generate Cargo workspace
|
|
1657
|
+
const members =
|
|
1658
|
+
template === 'workspace-basic'
|
|
1659
|
+
? ['packages/*']
|
|
1660
|
+
: template === 'workspace-layered'
|
|
1661
|
+
? ['core', 'api', 'shared']
|
|
1662
|
+
: template === 'microservices-basic'
|
|
1663
|
+
? ['services/*', 'libs/*']
|
|
1664
|
+
: ['crates/*'];
|
|
1665
|
+
|
|
1666
|
+
const cargoToml = `[workspace]
|
|
1667
|
+
resolver = "2"
|
|
1668
|
+
members = [
|
|
1669
|
+
${members.map(m => ` "${m}"`).join(',\n')}
|
|
1670
|
+
]
|
|
1671
|
+
|
|
1672
|
+
[workspace.package]
|
|
1673
|
+
version = "0.1.0"
|
|
1674
|
+
edition = "2021"
|
|
1675
|
+
authors = ["${projectName} Team"]
|
|
1676
|
+
license = "MIT"
|
|
1677
|
+
|
|
1678
|
+
[workspace.dependencies]
|
|
1679
|
+
# Add shared dependencies here
|
|
1680
|
+
tokio = { version = "1", features = ["full"] }
|
|
1681
|
+
serde = { version = "1", features = ["derive"] }
|
|
1682
|
+
`;
|
|
1683
|
+
await fs.writeFile('Cargo.toml', cargoToml);
|
|
1684
|
+
} else if (primaryLang === 'python') {
|
|
1685
|
+
// Generate pyproject.toml for monorepo
|
|
1686
|
+
const pyprojectToml = `[project]
|
|
1687
|
+
name = "${projectName}"
|
|
1688
|
+
version = "0.1.0"
|
|
1689
|
+
description = "${answers.description || ''}"
|
|
1690
|
+
requires-python = ">=3.11"
|
|
1691
|
+
|
|
1692
|
+
[tool.ruff]
|
|
1693
|
+
line-length = 100
|
|
1694
|
+
|
|
1695
|
+
[tool.pytest.ini_options]
|
|
1696
|
+
testpaths = ["tests"]
|
|
1697
|
+
`;
|
|
1698
|
+
await fs.writeFile('pyproject.toml', pyprojectToml);
|
|
1699
|
+
} else if (primaryLang === 'go') {
|
|
1700
|
+
// Generate go.work for Go workspaces
|
|
1701
|
+
const goWork = `go 1.21
|
|
1702
|
+
|
|
1703
|
+
use (
|
|
1704
|
+
./cmd
|
|
1705
|
+
./internal
|
|
1706
|
+
./pkg
|
|
1707
|
+
)
|
|
1708
|
+
`;
|
|
1709
|
+
await fs.writeFile('go.work', goWork);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* Generate reference architecture template structure
|
|
1715
|
+
*/
|
|
1716
|
+
async function generateArchitectureTemplate(templateName, answers) {
|
|
1717
|
+
const ARCH_TEMPLATE_DIR = path.join(__dirname, '..', 'src', 'templates', 'architectures');
|
|
1718
|
+
const languages = answers.languages || ['javascript'];
|
|
1719
|
+
const primaryLang = languages[0];
|
|
1720
|
+
|
|
1721
|
+
const architectures = {
|
|
1722
|
+
'clean-architecture': {
|
|
1723
|
+
dirs: [
|
|
1724
|
+
'src/domain/entities/',
|
|
1725
|
+
'src/domain/value-objects/',
|
|
1726
|
+
'src/domain/services/',
|
|
1727
|
+
'src/domain/errors/',
|
|
1728
|
+
'src/application/use-cases/',
|
|
1729
|
+
'src/application/ports/input/',
|
|
1730
|
+
'src/application/ports/output/',
|
|
1731
|
+
'src/application/dtos/',
|
|
1732
|
+
'src/infrastructure/persistence/repositories/',
|
|
1733
|
+
'src/infrastructure/persistence/mappers/',
|
|
1734
|
+
'src/infrastructure/external/',
|
|
1735
|
+
'src/interface/controllers/',
|
|
1736
|
+
'src/interface/presenters/',
|
|
1737
|
+
'src/interface/cli/',
|
|
1738
|
+
],
|
|
1739
|
+
readme: 'clean-architecture/README.md',
|
|
1740
|
+
},
|
|
1741
|
+
hexagonal: {
|
|
1742
|
+
dirs: [
|
|
1743
|
+
'src/core/domain/models/',
|
|
1744
|
+
'src/core/domain/events/',
|
|
1745
|
+
'src/core/domain/services/',
|
|
1746
|
+
'src/core/ports/inbound/',
|
|
1747
|
+
'src/core/ports/outbound/',
|
|
1748
|
+
'src/core/application/commands/',
|
|
1749
|
+
'src/core/application/queries/',
|
|
1750
|
+
'src/adapters/inbound/http/',
|
|
1751
|
+
'src/adapters/inbound/cli/',
|
|
1752
|
+
'src/adapters/outbound/persistence/',
|
|
1753
|
+
'src/adapters/outbound/messaging/',
|
|
1754
|
+
],
|
|
1755
|
+
readme: 'hexagonal/README.md',
|
|
1756
|
+
},
|
|
1757
|
+
'event-driven': {
|
|
1758
|
+
dirs: [
|
|
1759
|
+
'src/domain/events/',
|
|
1760
|
+
'src/domain/aggregates/',
|
|
1761
|
+
'src/domain/commands/',
|
|
1762
|
+
'src/application/command-handlers/',
|
|
1763
|
+
'src/application/event-handlers/',
|
|
1764
|
+
'src/application/sagas/',
|
|
1765
|
+
'src/application/projections/',
|
|
1766
|
+
'src/infrastructure/messaging/',
|
|
1767
|
+
'src/infrastructure/event-store/',
|
|
1768
|
+
'src/interface/api/',
|
|
1769
|
+
'src/interface/consumers/',
|
|
1770
|
+
'src/interface/publishers/',
|
|
1771
|
+
],
|
|
1772
|
+
readme: 'event-driven/README.md',
|
|
1773
|
+
},
|
|
1774
|
+
layered: {
|
|
1775
|
+
dirs: [
|
|
1776
|
+
'src/presentation/controllers/',
|
|
1777
|
+
'src/presentation/views/',
|
|
1778
|
+
'src/business/services/',
|
|
1779
|
+
'src/business/models/',
|
|
1780
|
+
'src/data/repositories/',
|
|
1781
|
+
'src/data/entities/',
|
|
1782
|
+
],
|
|
1783
|
+
readme: null,
|
|
1784
|
+
},
|
|
1785
|
+
'modular-monolith': {
|
|
1786
|
+
dirs: [
|
|
1787
|
+
'src/modules/users/',
|
|
1788
|
+
'src/modules/users/domain/',
|
|
1789
|
+
'src/modules/users/application/',
|
|
1790
|
+
'src/modules/users/infrastructure/',
|
|
1791
|
+
'src/modules/orders/',
|
|
1792
|
+
'src/modules/orders/domain/',
|
|
1793
|
+
'src/modules/orders/application/',
|
|
1794
|
+
'src/modules/orders/infrastructure/',
|
|
1795
|
+
'src/shared/kernel/',
|
|
1796
|
+
'src/shared/infrastructure/',
|
|
1797
|
+
],
|
|
1798
|
+
readme: null,
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
|
|
1802
|
+
const arch = architectures[templateName];
|
|
1803
|
+
if (!arch) {
|
|
1804
|
+
console.log(chalk.yellow(` Unknown architecture template: ${templateName}`));
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// Create directories
|
|
1809
|
+
for (const dir of arch.dirs) {
|
|
1810
|
+
await fs.ensureDir(dir);
|
|
1811
|
+
// Create .gitkeep to preserve empty directories
|
|
1812
|
+
await fs.writeFile(path.join(dir, '.gitkeep'), '');
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// Copy architecture README if available
|
|
1816
|
+
if (arch.readme) {
|
|
1817
|
+
const readmePath = path.join(ARCH_TEMPLATE_DIR, arch.readme);
|
|
1818
|
+
if (await fs.pathExists(readmePath)) {
|
|
1819
|
+
const destPath = path.join('docs', 'architecture', 'README.md');
|
|
1820
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
1821
|
+
await fs.copy(readmePath, destPath);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// Generate language-specific entry files
|
|
1826
|
+
await generateArchitectureEntryFiles(templateName, primaryLang, answers);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Generate entry files for architecture template
|
|
1831
|
+
*/
|
|
1832
|
+
async function generateArchitectureEntryFiles(templateName, primaryLang, answers) {
|
|
1833
|
+
const projectName = answers.projectName || 'my-project';
|
|
1834
|
+
|
|
1835
|
+
// Create a basic entry file based on language
|
|
1836
|
+
if (primaryLang === 'javascript' || primaryLang === 'typescript') {
|
|
1837
|
+
const entryFile = `// ${projectName} - ${templateName}
|
|
1838
|
+
// Entry point for the application
|
|
1839
|
+
|
|
1840
|
+
export function main(): void {
|
|
1841
|
+
console.log('Hello from ${projectName}!');
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
main();
|
|
1845
|
+
`;
|
|
1846
|
+
await fs.ensureDir('src');
|
|
1847
|
+
await fs.writeFile('src/index.ts', entryFile);
|
|
1848
|
+
} else if (primaryLang === 'rust') {
|
|
1849
|
+
const mainRs = `//! ${projectName} - ${templateName}
|
|
1850
|
+
//!
|
|
1851
|
+
//! Entry point for the application
|
|
1852
|
+
|
|
1853
|
+
fn main() {
|
|
1854
|
+
println!("Hello from ${projectName}!");
|
|
1855
|
+
}
|
|
1856
|
+
`;
|
|
1857
|
+
await fs.ensureDir('src');
|
|
1858
|
+
await fs.writeFile('src/main.rs', mainRs);
|
|
1859
|
+
} else if (primaryLang === 'python') {
|
|
1860
|
+
const safeName = projectName.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
1861
|
+
const mainPy = `"""${projectName} - ${templateName}
|
|
1862
|
+
|
|
1863
|
+
Entry point for the application
|
|
1864
|
+
"""
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
def main() -> None:
|
|
1868
|
+
print(f"Hello from ${projectName}!")
|
|
1869
|
+
|
|
1870
|
+
|
|
1871
|
+
if __name__ == "__main__":
|
|
1872
|
+
main()
|
|
1873
|
+
`;
|
|
1874
|
+
const srcDir = `src/${safeName}`;
|
|
1875
|
+
await fs.ensureDir(srcDir);
|
|
1876
|
+
await fs.writeFile(`${srcDir}/__main__.py`, mainPy);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* Generate language-specific dependency files for single-package projects
|
|
1882
|
+
*/
|
|
1883
|
+
async function generateDependencyFiles(primaryLang, answers) {
|
|
1884
|
+
const projectName = answers.projectName || 'my-project';
|
|
1885
|
+
const safeName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
1886
|
+
|
|
1887
|
+
if (primaryLang === 'javascript') {
|
|
1888
|
+
// Check if package.json already exists
|
|
1889
|
+
if (!(await fs.pathExists('package.json'))) {
|
|
1890
|
+
const packageJson = {
|
|
1891
|
+
name: safeName,
|
|
1892
|
+
version: '0.1.0',
|
|
1893
|
+
description: answers.description || '',
|
|
1894
|
+
type: 'module',
|
|
1895
|
+
main: 'dist/index.js',
|
|
1896
|
+
types: 'dist/index.d.ts',
|
|
1897
|
+
scripts: {
|
|
1898
|
+
build: 'tsc',
|
|
1899
|
+
test: 'jest',
|
|
1900
|
+
lint: 'eslint src/',
|
|
1901
|
+
format: 'prettier --write .',
|
|
1902
|
+
},
|
|
1903
|
+
devDependencies: {
|
|
1904
|
+
typescript: '^5.0.0',
|
|
1905
|
+
'@types/node': '^20.0.0',
|
|
1906
|
+
jest: '^29.0.0',
|
|
1907
|
+
'@types/jest': '^29.0.0',
|
|
1908
|
+
eslint: '^9.0.0',
|
|
1909
|
+
prettier: '^3.0.0',
|
|
1910
|
+
},
|
|
1911
|
+
};
|
|
1912
|
+
await fs.writeFile('package.json', JSON.stringify(packageJson, null, 2) + '\n');
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// Generate tsconfig.json
|
|
1916
|
+
if (!(await fs.pathExists('tsconfig.json'))) {
|
|
1917
|
+
const tsconfig = {
|
|
1918
|
+
compilerOptions: {
|
|
1919
|
+
target: 'ES2022',
|
|
1920
|
+
module: 'NodeNext',
|
|
1921
|
+
moduleResolution: 'NodeNext',
|
|
1922
|
+
declaration: true,
|
|
1923
|
+
outDir: './dist',
|
|
1924
|
+
rootDir: './src',
|
|
1925
|
+
strict: true,
|
|
1926
|
+
esModuleInterop: true,
|
|
1927
|
+
skipLibCheck: true,
|
|
1928
|
+
forceConsistentCasingInFileNames: true,
|
|
1929
|
+
},
|
|
1930
|
+
include: ['src/**/*'],
|
|
1931
|
+
exclude: ['node_modules', 'dist'],
|
|
1932
|
+
};
|
|
1933
|
+
await fs.writeFile('tsconfig.json', JSON.stringify(tsconfig, null, 2) + '\n');
|
|
1934
|
+
}
|
|
1935
|
+
} else if (primaryLang === 'rust') {
|
|
1936
|
+
// Check if Cargo.toml already exists
|
|
1937
|
+
if (!(await fs.pathExists('Cargo.toml'))) {
|
|
1938
|
+
const cargoToml = `[package]
|
|
1939
|
+
name = "${safeName}"
|
|
1940
|
+
version = "0.1.0"
|
|
1941
|
+
edition = "2021"
|
|
1942
|
+
description = "${answers.description || ''}"
|
|
1943
|
+
license = "MIT"
|
|
1944
|
+
|
|
1945
|
+
[dependencies]
|
|
1946
|
+
tokio = { version = "1", features = ["full"] }
|
|
1947
|
+
serde = { version = "1", features = ["derive"] }
|
|
1948
|
+
serde_json = "1"
|
|
1949
|
+
thiserror = "1"
|
|
1950
|
+
tracing = "0.1"
|
|
1951
|
+
|
|
1952
|
+
[dev-dependencies]
|
|
1953
|
+
tokio-test = "0.4"
|
|
1954
|
+
`;
|
|
1955
|
+
await fs.writeFile('Cargo.toml', cargoToml);
|
|
1956
|
+
|
|
1957
|
+
// Create src/main.rs or src/lib.rs
|
|
1958
|
+
await fs.ensureDir('src');
|
|
1959
|
+
if (!(await fs.pathExists('src/main.rs')) && !(await fs.pathExists('src/lib.rs'))) {
|
|
1960
|
+
const mainRs = `//! ${answers.description || projectName}
|
|
1961
|
+
|
|
1962
|
+
fn main() {
|
|
1963
|
+
println!("Hello from ${projectName}!");
|
|
1964
|
+
}
|
|
1965
|
+
`;
|
|
1966
|
+
await fs.writeFile('src/main.rs', mainRs);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
} else if (primaryLang === 'python') {
|
|
1970
|
+
// Check if pyproject.toml already exists
|
|
1971
|
+
if (!(await fs.pathExists('pyproject.toml'))) {
|
|
1972
|
+
const pyprojectToml = `[project]
|
|
1973
|
+
name = "${safeName}"
|
|
1974
|
+
version = "0.1.0"
|
|
1975
|
+
description = "${answers.description || ''}"
|
|
1976
|
+
requires-python = ">=3.11"
|
|
1977
|
+
dependencies = []
|
|
1978
|
+
|
|
1979
|
+
[project.optional-dependencies]
|
|
1980
|
+
dev = [
|
|
1981
|
+
"pytest>=7.0",
|
|
1982
|
+
"ruff>=0.1",
|
|
1983
|
+
"mypy>=1.0",
|
|
1984
|
+
]
|
|
1985
|
+
|
|
1986
|
+
[tool.ruff]
|
|
1987
|
+
line-length = 100
|
|
1988
|
+
target-version = "py311"
|
|
1989
|
+
|
|
1990
|
+
[tool.ruff.lint]
|
|
1991
|
+
select = ["E", "F", "I", "N", "W"]
|
|
1992
|
+
|
|
1993
|
+
[tool.mypy]
|
|
1994
|
+
python_version = "3.11"
|
|
1995
|
+
strict = true
|
|
1996
|
+
|
|
1997
|
+
[tool.pytest.ini_options]
|
|
1998
|
+
testpaths = ["tests"]
|
|
1999
|
+
`;
|
|
2000
|
+
await fs.writeFile('pyproject.toml', pyprojectToml);
|
|
2001
|
+
|
|
2002
|
+
// Create src directory and __init__.py
|
|
2003
|
+
const srcDir = `src/${safeName.replace(/-/g, '_')}`;
|
|
2004
|
+
await fs.ensureDir(srcDir);
|
|
2005
|
+
if (!(await fs.pathExists(`${srcDir}/__init__.py`))) {
|
|
2006
|
+
await fs.writeFile(
|
|
2007
|
+
`${srcDir}/__init__.py`,
|
|
2008
|
+
`"""${answers.description || projectName}"""\n\n__version__ = "0.1.0"\n`
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
} else if (primaryLang === 'go') {
|
|
2013
|
+
// Check if go.mod already exists
|
|
2014
|
+
if (!(await fs.pathExists('go.mod'))) {
|
|
2015
|
+
const goMod = `module github.com/${safeName}
|
|
2016
|
+
|
|
2017
|
+
go 1.21
|
|
2018
|
+
|
|
2019
|
+
require (
|
|
2020
|
+
// Add dependencies here
|
|
2021
|
+
)
|
|
2022
|
+
`;
|
|
2023
|
+
await fs.writeFile('go.mod', goMod);
|
|
2024
|
+
|
|
2025
|
+
// Create main.go
|
|
2026
|
+
await fs.ensureDir('cmd');
|
|
2027
|
+
if (!(await fs.pathExists('cmd/main.go'))) {
|
|
2028
|
+
const mainGo = `package main
|
|
2029
|
+
|
|
2030
|
+
import "fmt"
|
|
2031
|
+
|
|
2032
|
+
func main() {
|
|
2033
|
+
fmt.Println("Hello from ${projectName}!")
|
|
2034
|
+
}
|
|
2035
|
+
`;
|
|
2036
|
+
await fs.writeFile('cmd/main.go', mainGo);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
} else if (primaryLang === 'java') {
|
|
2040
|
+
// Generate pom.xml for Maven
|
|
2041
|
+
if (!(await fs.pathExists('pom.xml'))) {
|
|
2042
|
+
const pomXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
2043
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
2044
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
2045
|
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
2046
|
+
<modelVersion>4.0.0</modelVersion>
|
|
2047
|
+
|
|
2048
|
+
<groupId>com.example</groupId>
|
|
2049
|
+
<artifactId>${safeName}</artifactId>
|
|
2050
|
+
<version>0.1.0</version>
|
|
2051
|
+
<packaging>jar</packaging>
|
|
2052
|
+
|
|
2053
|
+
<name>${projectName}</name>
|
|
2054
|
+
<description>${answers.description || ''}</description>
|
|
2055
|
+
|
|
2056
|
+
<properties>
|
|
2057
|
+
<maven.compiler.source>21</maven.compiler.source>
|
|
2058
|
+
<maven.compiler.target>21</maven.compiler.target>
|
|
2059
|
+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
2060
|
+
</properties>
|
|
2061
|
+
|
|
2062
|
+
<dependencies>
|
|
2063
|
+
<dependency>
|
|
2064
|
+
<groupId>org.junit.jupiter</groupId>
|
|
2065
|
+
<artifactId>junit-jupiter</artifactId>
|
|
2066
|
+
<version>5.10.0</version>
|
|
2067
|
+
<scope>test</scope>
|
|
2068
|
+
</dependency>
|
|
2069
|
+
</dependencies>
|
|
2070
|
+
</project>
|
|
2071
|
+
`;
|
|
2072
|
+
await fs.writeFile('pom.xml', pomXml);
|
|
2073
|
+
}
|
|
2074
|
+
} else if (primaryLang === 'csharp') {
|
|
2075
|
+
// Generate .csproj file
|
|
2076
|
+
const csprojPath = `${projectName}.csproj`;
|
|
2077
|
+
if (!(await fs.pathExists(csprojPath))) {
|
|
2078
|
+
const csproj = `<Project Sdk="Microsoft.NET.Sdk">
|
|
2079
|
+
|
|
2080
|
+
<PropertyGroup>
|
|
2081
|
+
<OutputType>Exe</OutputType>
|
|
2082
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
2083
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
2084
|
+
<Nullable>enable</Nullable>
|
|
2085
|
+
</PropertyGroup>
|
|
2086
|
+
|
|
2087
|
+
<ItemGroup>
|
|
2088
|
+
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
|
2089
|
+
</ItemGroup>
|
|
2090
|
+
|
|
2091
|
+
</Project>
|
|
2092
|
+
`;
|
|
2093
|
+
await fs.writeFile(csprojPath, csproj);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
/**
|
|
2099
|
+
* Generate language-specific tech.md content
|
|
2100
|
+
*/
|
|
2101
|
+
function generateTechMd(languages, answers, _locale) {
|
|
2102
|
+
const langInfo = {
|
|
2103
|
+
javascript: {
|
|
2104
|
+
name: 'JavaScript/TypeScript',
|
|
2105
|
+
version: 'ES2022+ / TypeScript 5.0+',
|
|
2106
|
+
runtime: 'Node.js 20+ LTS, Bun, Deno',
|
|
2107
|
+
packageManager: 'npm, pnpm, yarn',
|
|
2108
|
+
frameworks: 'React, Vue, Next.js, Express, Fastify',
|
|
2109
|
+
testing: 'Jest, Vitest, Playwright',
|
|
2110
|
+
},
|
|
2111
|
+
python: {
|
|
2112
|
+
name: 'Python',
|
|
2113
|
+
version: '3.11+',
|
|
2114
|
+
runtime: 'CPython, PyPy',
|
|
2115
|
+
packageManager: 'pip, poetry, uv',
|
|
2116
|
+
frameworks: 'FastAPI, Django, Flask',
|
|
2117
|
+
testing: 'pytest, unittest',
|
|
2118
|
+
},
|
|
2119
|
+
rust: {
|
|
2120
|
+
name: 'Rust',
|
|
2121
|
+
version: '1.75+ stable',
|
|
2122
|
+
runtime: 'Native binary',
|
|
2123
|
+
packageManager: 'Cargo',
|
|
2124
|
+
frameworks: 'Axum, Actix-web, Tokio',
|
|
2125
|
+
testing: 'cargo test, criterion',
|
|
2126
|
+
},
|
|
2127
|
+
go: {
|
|
2128
|
+
name: 'Go',
|
|
2129
|
+
version: '1.21+',
|
|
2130
|
+
runtime: 'Native binary',
|
|
2131
|
+
packageManager: 'Go modules',
|
|
2132
|
+
frameworks: 'Gin, Echo, Chi',
|
|
2133
|
+
testing: 'go test, testify',
|
|
2134
|
+
},
|
|
2135
|
+
java: {
|
|
2136
|
+
name: 'Java/Kotlin',
|
|
2137
|
+
version: 'Java 21 LTS / Kotlin 1.9+',
|
|
2138
|
+
runtime: 'JVM, GraalVM',
|
|
2139
|
+
packageManager: 'Maven, Gradle',
|
|
2140
|
+
frameworks: 'Spring Boot, Quarkus, Ktor',
|
|
2141
|
+
testing: 'JUnit 5, Kotest',
|
|
2142
|
+
},
|
|
2143
|
+
csharp: {
|
|
2144
|
+
name: 'C#/.NET',
|
|
2145
|
+
version: '.NET 8+',
|
|
2146
|
+
runtime: '.NET Runtime',
|
|
2147
|
+
packageManager: 'NuGet',
|
|
2148
|
+
frameworks: 'ASP.NET Core, MAUI',
|
|
2149
|
+
testing: 'xUnit, NUnit',
|
|
2150
|
+
},
|
|
2151
|
+
cpp: {
|
|
2152
|
+
name: 'C/C++',
|
|
2153
|
+
version: 'C++20',
|
|
2154
|
+
runtime: 'Native binary',
|
|
2155
|
+
packageManager: 'vcpkg, Conan',
|
|
2156
|
+
frameworks: 'Qt, Boost',
|
|
2157
|
+
testing: 'GoogleTest, Catch2',
|
|
2158
|
+
},
|
|
2159
|
+
swift: {
|
|
2160
|
+
name: 'Swift',
|
|
2161
|
+
version: '5.9+',
|
|
2162
|
+
runtime: 'Native binary',
|
|
2163
|
+
packageManager: 'Swift Package Manager',
|
|
2164
|
+
frameworks: 'SwiftUI, Vapor',
|
|
2165
|
+
testing: 'XCTest',
|
|
2166
|
+
},
|
|
2167
|
+
ruby: {
|
|
2168
|
+
name: 'Ruby',
|
|
2169
|
+
version: '3.2+',
|
|
2170
|
+
runtime: 'CRuby, JRuby',
|
|
2171
|
+
packageManager: 'Bundler, RubyGems',
|
|
2172
|
+
frameworks: 'Rails, Sinatra',
|
|
2173
|
+
testing: 'RSpec, Minitest',
|
|
2174
|
+
},
|
|
2175
|
+
php: {
|
|
2176
|
+
name: 'PHP',
|
|
2177
|
+
version: '8.2+',
|
|
2178
|
+
runtime: 'PHP-FPM, Swoole',
|
|
2179
|
+
packageManager: 'Composer',
|
|
2180
|
+
frameworks: 'Laravel, Symfony',
|
|
2181
|
+
testing: 'PHPUnit, Pest',
|
|
2182
|
+
},
|
|
2183
|
+
};
|
|
2184
|
+
|
|
2185
|
+
const isUndecided = languages[0] === 'undecided';
|
|
2186
|
+
const date = new Date().toISOString().split('T')[0];
|
|
2187
|
+
|
|
2188
|
+
if (isUndecided) {
|
|
2189
|
+
return `# Technology Stack
|
|
2190
|
+
|
|
2191
|
+
**Project**: ${answers.projectName}
|
|
2192
|
+
**Last Updated**: ${date}
|
|
2193
|
+
**Status**: Technology stack to be determined
|
|
2194
|
+
|
|
2195
|
+
---
|
|
2196
|
+
|
|
2197
|
+
## Overview
|
|
2198
|
+
|
|
2199
|
+
The technology stack for this project has not yet been decided. This document will be updated once the technical decisions are made.
|
|
2200
|
+
|
|
2201
|
+
## Decision Criteria
|
|
2202
|
+
|
|
2203
|
+
When selecting technologies, consider:
|
|
2204
|
+
|
|
2205
|
+
1. **Application Type**: What type of application is being built?
|
|
2206
|
+
2. **Performance Requirements**: What are the performance constraints?
|
|
2207
|
+
3. **Team Expertise**: What technologies is the team familiar with?
|
|
2208
|
+
4. **Ecosystem**: What libraries and tools are available?
|
|
2209
|
+
5. **Long-term Maintainability**: How well-supported is the technology?
|
|
2210
|
+
|
|
2211
|
+
## Candidates Under Consideration
|
|
2212
|
+
|
|
2213
|
+
| Aspect | Options | Decision |
|
|
2214
|
+
|--------|---------|----------|
|
|
2215
|
+
| Primary Language | TBD | ⏳ Pending |
|
|
2216
|
+
| Web Framework | TBD | ⏳ Pending |
|
|
2217
|
+
| Database | TBD | ⏳ Pending |
|
|
2218
|
+
| Hosting | TBD | ⏳ Pending |
|
|
2219
|
+
|
|
2220
|
+
## Next Steps
|
|
2221
|
+
|
|
2222
|
+
1. [ ] Define functional requirements
|
|
2223
|
+
2. [ ] Identify performance constraints
|
|
2224
|
+
3. [ ] Evaluate team skills
|
|
2225
|
+
4. [ ] Create proof-of-concept
|
|
2226
|
+
5. [ ] Make final decision and update this document
|
|
2227
|
+
|
|
2228
|
+
---
|
|
2229
|
+
|
|
2230
|
+
*Run \`musubi steering\` to update this document after decisions are made.*
|
|
2231
|
+
`;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// Generate tech.md for selected languages
|
|
2235
|
+
const primaryLang = languages[0];
|
|
2236
|
+
const primary = langInfo[primaryLang] || { name: primaryLang, version: 'Latest' };
|
|
2237
|
+
|
|
2238
|
+
let languageTable = `### Programming Languages
|
|
2239
|
+
|
|
2240
|
+
| Language | Version | Role | Notes |
|
|
2241
|
+
|----------|---------|------|-------|
|
|
2242
|
+
`;
|
|
2243
|
+
|
|
2244
|
+
for (let i = 0; i < languages.length; i++) {
|
|
2245
|
+
const lang = languages[i];
|
|
2246
|
+
const info = langInfo[lang] || { name: lang, version: 'Latest' };
|
|
2247
|
+
const role = i === 0 ? 'Primary' : 'Secondary';
|
|
2248
|
+
languageTable += `| ${info.name} | ${info.version} | ${role} | ${info.runtime || ''} |\n`;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
let frameworksSection = '';
|
|
2252
|
+
for (const lang of languages) {
|
|
2253
|
+
const info = langInfo[lang];
|
|
2254
|
+
if (info && info.frameworks) {
|
|
2255
|
+
frameworksSection += `
|
|
2256
|
+
### ${info.name} Ecosystem
|
|
2257
|
+
|
|
2258
|
+
- **Package Manager**: ${info.packageManager}
|
|
2259
|
+
- **Frameworks**: ${info.frameworks}
|
|
2260
|
+
- **Testing**: ${info.testing}
|
|
2261
|
+
`;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
return `# Technology Stack
|
|
2266
|
+
|
|
2267
|
+
**Project**: ${answers.projectName}
|
|
2268
|
+
**Last Updated**: ${date}
|
|
2269
|
+
**Version**: 0.1.0
|
|
2270
|
+
|
|
2271
|
+
---
|
|
2272
|
+
|
|
2273
|
+
## Overview
|
|
2274
|
+
|
|
2275
|
+
${answers.description}
|
|
2276
|
+
|
|
2277
|
+
---
|
|
2278
|
+
|
|
2279
|
+
## Primary Technologies
|
|
2280
|
+
|
|
2281
|
+
${languageTable}
|
|
2282
|
+
${frameworksSection}
|
|
2283
|
+
|
|
2284
|
+
---
|
|
2285
|
+
|
|
2286
|
+
## Development Environment
|
|
2287
|
+
|
|
2288
|
+
### Required Tools
|
|
2289
|
+
|
|
2290
|
+
- Primary language runtime (see above)
|
|
2291
|
+
- Git 2.40+
|
|
2292
|
+
- IDE: VS Code / JetBrains / Neovim
|
|
2293
|
+
|
|
2294
|
+
### Recommended Extensions
|
|
2295
|
+
|
|
2296
|
+
- Language-specific LSP
|
|
2297
|
+
- Linter/Formatter integration
|
|
2298
|
+
- Test runner integration
|
|
2299
|
+
|
|
2300
|
+
---
|
|
2301
|
+
|
|
2302
|
+
## Architecture Decisions
|
|
2303
|
+
|
|
2304
|
+
| Decision | Choice | Rationale |
|
|
2305
|
+
|----------|--------|-----------|
|
|
2306
|
+
| Primary Language | ${primary.name} | Selected during project initialization |
|
|
2307
|
+
| Package Manager | ${primary.packageManager || 'TBD'} | Standard for ${primary.name} |
|
|
2308
|
+
|
|
2309
|
+
---
|
|
2310
|
+
|
|
2311
|
+
## Dependencies
|
|
2312
|
+
|
|
2313
|
+
### Production Dependencies
|
|
2314
|
+
|
|
2315
|
+
*To be documented as dependencies are added.*
|
|
2316
|
+
|
|
2317
|
+
### Development Dependencies
|
|
2318
|
+
|
|
2319
|
+
*To be documented as dependencies are added.*
|
|
2320
|
+
|
|
2321
|
+
---
|
|
2322
|
+
|
|
2323
|
+
*Generated by MUSUBI SDD - Update with \`musubi steering\`*
|
|
2324
|
+
`;
|
|
383
2325
|
}
|
|
384
2326
|
|
|
385
2327
|
async function createConstitution() {
|