@polymorphism-tech/morph-spec 4.7.1 → 4.8.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.
Files changed (138) hide show
  1. package/.morph/analytics/threads-log.jsonl +54 -0
  2. package/.morph/state.json +198 -0
  3. package/LICENSE +1 -2
  4. package/README.md +379 -414
  5. package/bin/morph-spec.js +57 -403
  6. package/bin/validate.js +2 -26
  7. package/claude-plugin.json +2 -2
  8. package/docs/ARCHITECTURE.md +43 -46
  9. package/docs/CHEATSHEET.md +203 -221
  10. package/docs/COMMAND-FLOWS.md +319 -289
  11. package/docs/QUICKSTART.md +2 -8
  12. package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +2 -0
  13. package/docs/plans/2026-02-22-claude-settings.md +2 -0
  14. package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +2 -0
  15. package/docs/plans/2026-02-22-morph-spec-next.md +2 -0
  16. package/docs/plans/2026-02-22-native-alignment-design.md +2 -0
  17. package/docs/plans/2026-02-22-native-alignment-impl.md +2 -0
  18. package/docs/plans/2026-02-22-native-enrichment-design.md +2 -0
  19. package/docs/plans/2026-02-22-native-enrichment.md +2 -0
  20. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +2 -0
  21. package/docs/plans/2026-02-23-ddd-nextsteps.md +2 -0
  22. package/docs/plans/2026-02-23-infra-architect-refactor.md +2 -0
  23. package/docs/plans/2026-02-23-nextjs-code-review-design.md +2 -1
  24. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +2 -0
  25. package/docs/plans/2026-02-23-nextjs-standards-design.md +2 -1
  26. package/docs/plans/2026-02-23-nextjs-standards-impl.md +2 -0
  27. package/docs/plans/2026-02-24-cli-radical-simplification.md +592 -0
  28. package/docs/plans/2026-02-24-framework-failure-points.md +125 -0
  29. package/docs/plans/2026-02-24-morph-init-design.md +337 -0
  30. package/docs/plans/2026-02-24-morph-init-impl.md +1269 -0
  31. package/docs/plans/2026-02-24-tutorial-command-design.md +71 -0
  32. package/docs/plans/2026-02-24-tutorial-command.md +298 -0
  33. package/framework/CLAUDE.md +2 -2
  34. package/framework/commands/morph-proposal.md +3 -3
  35. package/framework/hooks/README.md +11 -10
  36. package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
  37. package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
  38. package/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +4 -55
  39. package/framework/hooks/claude-code/session-start/inject-morph-context.js +20 -5
  40. package/framework/hooks/claude-code/statusline.py +6 -1
  41. package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
  42. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
  43. package/framework/hooks/dev/check-sync-health.js +117 -0
  44. package/framework/hooks/dev/guard-version-numbers.js +57 -0
  45. package/framework/hooks/dev/sync-standards-registry.js +60 -0
  46. package/framework/hooks/dev/sync-template-registry.js +60 -0
  47. package/framework/hooks/dev/validate-skill-format.js +70 -0
  48. package/framework/hooks/dev/validate-standard-format.js +73 -0
  49. package/framework/hooks/shared/payload-utils.js +39 -0
  50. package/framework/hooks/shared/state-reader.js +25 -1
  51. package/framework/rules/morph-workflow.md +1 -1
  52. package/framework/skills/level-0-meta/morph-init/SKILL.md +216 -0
  53. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  54. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +4 -4
  55. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  56. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +192 -191
  57. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +181 -180
  58. package/framework/skills/level-1-workflows/phase-design/SKILL.md +339 -338
  59. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +254 -253
  60. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +168 -170
  61. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +284 -283
  62. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +246 -245
  63. package/framework/templates/examples/design-system-examples.md +1 -1
  64. package/framework/templates/ui/FluentDesignTheme.cs +1 -1
  65. package/framework/templates/ui/MudTheme.cs +1 -1
  66. package/framework/templates/ui/design-system.css +1 -1
  67. package/package.json +4 -2
  68. package/scripts/bump-version.js +248 -0
  69. package/scripts/install-dev-hooks.js +138 -0
  70. package/src/commands/agents/index.js +1 -2
  71. package/src/commands/index.js +13 -16
  72. package/src/commands/project/doctor.js +100 -14
  73. package/src/commands/project/index.js +7 -10
  74. package/src/commands/project/init.js +398 -555
  75. package/src/commands/project/install-plugin-cmd.js +28 -0
  76. package/src/commands/project/setup-infra-cmd.js +12 -0
  77. package/src/commands/project/tutorial.js +115 -0
  78. package/src/commands/project/update.js +22 -37
  79. package/src/commands/state/approve.js +213 -221
  80. package/src/commands/state/index.js +0 -1
  81. package/src/commands/state/state.js +337 -365
  82. package/src/commands/templates/index.js +0 -4
  83. package/src/commands/trust/trust.js +1 -93
  84. package/src/commands/utils/index.js +1 -5
  85. package/src/commands/validation/index.js +1 -5
  86. package/src/core/registry/command-registry.js +11 -285
  87. package/src/core/state/state-manager.js +5 -2
  88. package/src/lib/detectors/index.js +81 -87
  89. package/src/lib/detectors/structure-detector.js +275 -273
  90. package/src/lib/generators/recap-generator.js +232 -225
  91. package/src/lib/installers/mcp-installer.js +18 -3
  92. package/src/scripts/global-install.js +34 -0
  93. package/src/scripts/install-plugin.js +126 -0
  94. package/src/scripts/setup-infra.js +203 -0
  95. package/src/utils/agents-installer.js +10 -1
  96. package/src/utils/hooks-installer.js +70 -17
  97. package/CLAUDE.md +0 -77
  98. package/docs/claude-alignment-report.md +0 -137
  99. package/docs/examples/order-management/contracts.cs +0 -84
  100. package/docs/examples/order-management/proposal.md +0 -24
  101. package/docs/examples/order-management/spec.md +0 -162
  102. package/src/commands/feature/create-story.js +0 -362
  103. package/src/commands/feature/index.js +0 -6
  104. package/src/commands/feature/shard-spec.js +0 -225
  105. package/src/commands/feature/sprint-status.js +0 -250
  106. package/src/commands/generation/generate-onboarding.js +0 -169
  107. package/src/commands/generation/generate.js +0 -276
  108. package/src/commands/generation/index.js +0 -5
  109. package/src/commands/learning/capture-pattern.js +0 -121
  110. package/src/commands/learning/index.js +0 -5
  111. package/src/commands/learning/search-patterns.js +0 -126
  112. package/src/commands/mcp/mcp.js +0 -102
  113. package/src/commands/project/changes.js +0 -66
  114. package/src/commands/project/cost.js +0 -179
  115. package/src/commands/project/detect.js +0 -114
  116. package/src/commands/project/diff.js +0 -278
  117. package/src/commands/project/revert.js +0 -173
  118. package/src/commands/project/standards.js +0 -80
  119. package/src/commands/project/sync.js +0 -167
  120. package/src/commands/project/update-agents.js +0 -23
  121. package/src/commands/state/rollback-phase.js +0 -185
  122. package/src/commands/templates/template-customize.js +0 -87
  123. package/src/commands/templates/template-list.js +0 -114
  124. package/src/commands/templates/template-show.js +0 -129
  125. package/src/commands/templates/template-validate.js +0 -91
  126. package/src/commands/utils/troubleshoot.js +0 -222
  127. package/src/commands/validation/analyze-blazor-concurrency.js +0 -193
  128. package/src/commands/validation/lint-fluent.js +0 -352
  129. package/src/commands/validation/validate-blazor-state.js +0 -210
  130. package/src/commands/validation/validate-blazor.js +0 -156
  131. package/src/commands/validation/validate-css.js +0 -84
  132. package/src/lib/detectors/conversation-analyzer.js +0 -163
  133. package/src/lib/learning/index.js +0 -7
  134. package/src/lib/learning/learning-system.js +0 -520
  135. package/src/lib/troubleshooting/index.js +0 -8
  136. package/src/lib/troubleshooting/troubleshoot-grep.js +0 -198
  137. package/src/lib/troubleshooting/troubleshoot-index.js +0 -144
  138. package/src/llm/environment-detector.js +0 -43
@@ -1,555 +1,398 @@
1
- import { join } from 'path';
2
- import fs from 'fs-extra';
3
- import ora from 'ora';
4
- import chalk from 'chalk';
5
- import { logger } from '../../utils/logger.js';
6
- import {
7
- copyDirectory,
8
- copyFile,
9
- pathExists,
10
- readJson,
11
- writeJson,
12
- ensureDir,
13
- writeFile,
14
- readFile,
15
- updateGitignore
16
- } from '../../utils/file-copier.js';
17
- import { saveProjectMorphVersion, getInstalledCLIVersion } from '../../utils/version-checker.js';
18
- import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
19
- import { installSkills } from '../../utils/skills-installer.js';
20
- import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
21
- import { AutoContextOrchestrator } from '../../core/orchestrator.js';
22
- import { detectClaudeCode } from '../../llm/environment-detector.js';
23
- import inquirer from 'inquirer';
24
- import { detectProject } from '../../lib/detectors/index.js';
25
- import { detectClaudeConfig, mapMcpsToPhases, formatConfigSummary } from '../../lib/detectors/claude-config-detector.js';
26
- import { generateSettings, saveIntegrations } from '../../lib/generators/settings-generator.js';
27
- import {
28
- orchestrateMcpSetup,
29
- installAutoMcps,
30
- installMcpWithCredentials,
31
- generateSetupInstructions,
32
- formatMcpStatusTable
33
- } from '../../lib/installers/mcp-installer.js';
34
-
35
- export async function initCommand(options) {
36
- const targetPath = options.path || process.cwd();
37
- const claudeMdPath = join(import.meta.dirname, '..', '..', '..', 'framework', 'CLAUDE.md');
38
-
39
- logger.header('MORPH-SPEC Framework Installation');
40
- logger.dim(`Target: ${targetPath}`);
41
- logger.blank();
42
-
43
- // Check if already initialized
44
- const morphPath = join(targetPath, '.morph');
45
- if (await pathExists(morphPath)) {
46
- if (!options.force) {
47
- logger.warn('MORPH already initialized in this directory.');
48
- logger.dim('Use --force to overwrite existing installation.');
49
- process.exit(1);
50
- }
51
- logger.warn('Overwriting existing MORPH installation...');
52
- }
53
-
54
- const spinner = ora('Installing MORPH-SPEC...').start();
55
-
56
- try {
57
- // 1. Copy CLAUDE.md
58
- spinner.text = 'Copying CLAUDE.md...';
59
- const claudeMdDest = join(targetPath, 'CLAUDE.md');
60
-
61
- // Backup existing CLAUDE.md if not from MORPH
62
- if (await pathExists(claudeMdDest)) {
63
- const existingContent = await readFile(claudeMdDest);
64
- if (!existingContent.includes('MORPH-SPEC')) {
65
- await copyFile(claudeMdDest, `${claudeMdDest}.backup`);
66
- logger.dim('Backed up existing CLAUDE.md');
67
- }
68
- }
69
- await copyFile(claudeMdPath, claudeMdDest);
70
-
71
- // 2. Create .morph directory structure
72
- spinner.text = 'Creating .morph structure...';
73
- const configDir = join(morphPath, 'config');
74
- const frameworkDestDir = join(morphPath, 'framework');
75
- const contextDir = join(morphPath, 'context');
76
- const featuresDir = join(morphPath, 'features');
77
- await ensureDir(contextDir);
78
- await ensureDir(featuresDir);
79
- await ensureDir(configDir);
80
- await ensureDir(frameworkDestDir);
81
-
82
- // 3. Create config.json
83
- spinner.text = 'Generating config.json...';
84
- const configPath = join(configDir, 'config.json');
85
- const dirName = targetPath.split(/[\\/]/).pop();
86
-
87
- const config = {
88
- framework: 'global',
89
- frameworkVersion: getInstalledCLIVersion(),
90
- project: {
91
- name: dirName,
92
- architecture: 'unknown'
93
- }
94
- };
95
-
96
- await writeJson(configPath, config);
97
-
98
- // 4. Create context/README.md
99
- spinner.text = 'Creating context README...';
100
- const contextReadme = join(contextDir, 'README.md');
101
- const readmeContent = `# ${dirName} - Project Context
102
-
103
- ## Overview
104
-
105
- This file will be auto-updated by MORPH-SPEC detection system.
106
-
107
- ## Stack
108
-
109
- Run \`morph-spec detect\` to analyze your project.
110
-
111
- ## Technologies
112
-
113
- (To be detected)
114
-
115
- ## Architecture
116
-
117
- (To be detected)
118
- `;
119
- await writeFile(contextReadme, readmeContent);
120
-
121
- // 5. Copy framework templates (project-local overrides start from framework defaults)
122
- const frameworkTemplatesSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
123
- const templatesDest = join(frameworkDestDir, 'templates');
124
- let templatesCopied = false;
125
- if (await pathExists(frameworkTemplatesSrc)) {
126
- await copyDirectory(frameworkTemplatesSrc, templatesDest);
127
- templatesCopied = true;
128
- } else {
129
- await ensureDir(templatesDest);
130
- }
131
-
132
- // 6b. Copy framework standards hierarchy (universal standards)
133
- spinner.text = 'Copying framework standards hierarchy...';
134
- const frameworkStandardsSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'standards');
135
- const frameworkStandardsDest = join(frameworkDestDir, 'standards');
136
- if (await pathExists(frameworkStandardsSrc)) {
137
- // Copy entire hierarchy (core/, backend/, frontend/, infrastructure/)
138
- await copyDirectory(frameworkStandardsSrc, frameworkStandardsDest);
139
- logger.dim(' ✓ Copied framework standards: core/, backend/, frontend/, infrastructure/');
140
- }
141
-
142
- // 7. Copy agents.json (sourced from framework/ — canonical single source of truth)
143
- spinner.text = 'Copying agents configuration...';
144
- const agentsSrc = join(import.meta.dirname, '..', '..', '..', 'framework', 'agents.json');
145
- const agentsDest = join(frameworkDestDir, 'agents.json');
146
- if (await pathExists(agentsSrc)) {
147
- await copyFile(agentsSrc, agentsDest);
148
- }
149
-
150
- // 8. Copy .claude commands and install skills
151
- // Source: framework/ (canonical for all stacks)
152
- spinner.text = 'Setting up Claude Code integration...';
153
- const frameworkDir = join(import.meta.dirname, '..', '..', '..', 'framework');
154
- const claudeDest = join(targetPath, '.claude');
155
-
156
- let commandsCopied = false;
157
-
158
- // Copy commands directory (slash commands): framework/commands/ → .claude/commands/
159
- const commandsSrc = join(frameworkDir, 'commands');
160
- const commandsDest = join(claudeDest, 'commands');
161
- if (await pathExists(commandsSrc)) {
162
- await copyDirectory(commandsSrc, commandsDest);
163
- commandsCopied = true;
164
- } else {
165
- logger.warn(' ⚠ framework/commands/ source missing commands not installed');
166
- }
167
-
168
- // 9a. Install path-scoped rules to .claude/rules/ (native Claude Code pattern)
169
- spinner.text = 'Installing path-scoped rules to .claude/rules/...';
170
- const rulesSrc = join(frameworkDir, 'rules');
171
- const rulesDest = join(claudeDest, 'rules');
172
- let rulesCopied = false;
173
- if (await pathExists(rulesSrc)) {
174
- await copyDirectory(rulesSrc, rulesDest);
175
- rulesCopied = true;
176
- }
177
-
178
- // 9b. Install morph skills to .claude/skills/ for native Claude Code discovery
179
- spinner.text = 'Installing morph skills to .claude/skills/...';
180
- await installSkills(targetPath);
181
-
182
- // 9b2. Install tier-1/2 agents as native Claude Code subagents in .claude/agents/
183
- spinner.text = 'Installing native subagents to .claude/agents/...';
184
- const detectedStackForAgents = config.project.stack ?? null;
185
- await installAgents(targetPath, frameworkDir, { projectStack: detectedStackForAgents });
186
-
187
- // 9b2b. Install level-2 domain agents as native Claude Code subagents in .claude/agents/
188
- await installDomainAgents(targetPath, frameworkDir);
189
-
190
- // 9b3. Install runtime CLAUDE.md to .claude/CLAUDE.md (Claude Code runtime instructions)
191
- spinner.text = 'Installing .claude/CLAUDE.md...';
192
- const runtimeClaudeMdSrc = join(frameworkDir, 'CLAUDE.md');
193
- const runtimeClaudeMdDest = join(claudeDest, 'CLAUDE.md');
194
- if (await pathExists(runtimeClaudeMdSrc)) {
195
- await copyFile(runtimeClaudeMdSrc, runtimeClaudeMdDest);
196
- }
197
-
198
- // 9c. Install statusline globally to ~/.claude/ (applies to all Claude Code sessions)
199
- spinner.text = 'Installing statusline globally to ~/.claude/...';
200
- const HOOKS_SRC = join(import.meta.dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
201
- try {
202
- await installGlobalStatusline(HOOKS_SRC);
203
- } catch {
204
- // Non-critical: global dir may not be writable in all environments
205
- logger.dim(' ⚠ Could not install statusline globally (non-critical)');
206
- }
207
-
208
- // 9d. Install/update Claude Code hooks in .claude/settings.local.json
209
- spinner.text = 'Installing Claude Code hooks...';
210
- const hooksResult = await installClaudeHooks(targetPath);
211
-
212
- // 10. Save version info
213
- spinner.text = 'Saving version info...';
214
- const cliVersion = getInstalledCLIVersion();
215
- await saveProjectMorphVersion(targetPath, cliVersion);
216
-
217
- // 11. Update .gitignore
218
- spinner.text = 'Updating .gitignore...';
219
- await updateGitignore(targetPath);
220
-
221
- // 11b. Detect Claude Code environment (plugins, MCPs, skills)
222
- spinner.text = 'Detecting Claude Code environment...';
223
- let claudeConfigDetected = null;
224
- let integrationsCreated = false;
225
- try {
226
- claudeConfigDetected = await detectClaudeConfig(targetPath);
227
- const hasIntegrations = claudeConfigDetected.plugins.length > 0
228
- || claudeConfigDetected.mcpServers.length > 0
229
- || claudeConfigDetected.superpowers.installed;
230
-
231
- if (hasIntegrations) {
232
- spinner.stop();
233
- logger.blank();
234
- logger.header('Claude Code Environment Detected');
235
-
236
- const summary = formatConfigSummary(claudeConfigDetected);
237
- if (summary) {
238
- logger.info(summary);
239
- }
240
-
241
- // Show MCP-to-phase mapping
242
- if (claudeConfigDetected.mcpServers.length > 0) {
243
- const phaseMap = mapMcpsToPhases(claudeConfigDetected.mcpServers);
244
- logger.blank();
245
- logger.dim('MCP usage by phase:');
246
- for (const [phase, mcps] of Object.entries(phaseMap)) {
247
- if (mcps.length > 0) {
248
- logger.dim(` ${phase}: ${mcps.map(m => m.name).join(', ')}`);
249
- }
250
- }
251
- }
252
-
253
- logger.blank();
254
- const { saveIntegrationsChoice } = await inquirer.prompt([{
255
- type: 'confirm',
256
- name: 'saveIntegrationsChoice',
257
- message: 'Save detected integrations and generate optimized settings?',
258
- default: true
259
- }]);
260
-
261
- if (saveIntegrationsChoice) {
262
- spinner.start('Saving integrations...');
263
- await saveIntegrations(targetPath, claudeConfigDetected);
264
- await generateSettings(targetPath, claudeConfigDetected, config);
265
- integrationsCreated = true;
266
- spinner.succeed('Integrations saved to .morph/config/integrations.json');
267
- } else {
268
- spinner.start('Continuing...');
269
- }
270
- }
271
- } catch (error) {
272
- // Non-fatal: continue without integrations
273
- logger.dim(` Claude config detection skipped: ${error.message}`);
274
- }
275
-
276
- // 11c. Stack detection (moved before MCP setup so we know the stack)
277
- spinner.text = 'Detecting project stack...';
278
- let detectedStack = 'unknown';
279
- const hasProjectFiles = await pathExists(join(targetPath, 'package.json'))
280
- || await pathExists(join(targetPath, 'src'))
281
- || (await fs.readdir(targetPath)).some(f => f.endsWith('.csproj'));
282
-
283
- if (hasProjectFiles) {
284
- try {
285
- const quickResults = await detectProject(targetPath, { conversation: false, generateStandards: false });
286
- const structure = quickResults.structure;
287
-
288
- if (structure?.stack && structure.stack !== 'unknown') {
289
- detectedStack = structure.stack;
290
-
291
- // Persist detected stack to config.json
292
- if (structure?.stack && structure.stack !== 'unknown') {
293
- config.project.stack = structure.stack;
294
- }
295
- if (structure?.architecture && structure.architecture !== 'unknown') {
296
- config.project.architecture = structure.architecture;
297
- }
298
- if (structure?.uiLibrary) {
299
- config.project.uiLibrary = structure.uiLibrary;
300
- }
301
- await writeJson(configPath, config);
302
-
303
- spinner.stop();
304
- logger.blank();
305
- logger.header('Existing Project Detected');
306
- logger.info(`Stack detected: ${structure.stack}`);
307
- if (structure?.architecture) logger.dim(`Architecture: ${structure.architecture}`);
308
- if (structure?.uiLibrary) logger.dim(`UI Library: ${structure.uiLibrary}`);
309
-
310
- const { standardsChoice } = await inquirer.prompt([{
311
- type: 'list',
312
- name: 'standardsChoice',
313
- message: 'How should morph-spec handle your project standards?',
314
- choices: [
315
- { name: 'Keep morph-spec defaults (recommended)', value: 'morph-spec' },
316
- { name: 'Detect project-specific standards', value: 'detect' }
317
- ]
318
- }]);
319
-
320
- if (standardsChoice === 'detect') {
321
- spinner.start('Generating project-specific standards...');
322
- const fullResults = await detectProject(targetPath, { conversation: false });
323
- const inferredPath = join(contextDir, 'standards.md');
324
- await writeFile(inferredPath, fullResults.inferred.markdown);
325
- spinner.succeed('Generated .morph/context/standards.md');
326
- logger.dim('Review and edit .morph/context/standards.md as needed.');
327
- }
328
-
329
- spinner.start('Continuing...');
330
- }
331
- } catch (error) {
332
- logger.dim(` Stack detection skipped: ${error.message}`);
333
- }
334
- }
335
-
336
- // 11d. MCP Auto-Setup
337
- let mcpSetupResult = null;
338
- if (!options.skipMcp) {
339
- try {
340
- spinner.stop();
341
- const existingMcps = claudeConfigDetected?.mcpServers || [];
342
- const orchestration = orchestrateMcpSetup(targetPath, detectedStack, existingMcps);
343
-
344
- const autoNames = Object.keys(orchestration.autoInstallable);
345
- const manualNames = Object.keys(orchestration.needsManualSetup);
346
- const hasAutoMcps = autoNames.length > 0;
347
- const hasManualMcps = manualNames.length > 0;
348
-
349
- if (hasAutoMcps || hasManualMcps) {
350
- logger.blank();
351
- logger.header('MCP Server Setup');
352
-
353
- // Auto-install prompt
354
- let autoInstalled = {};
355
- if (hasAutoMcps) {
356
- const { installAuto } = await inquirer.prompt([{
357
- type: 'confirm',
358
- name: 'installAuto',
359
- message: `Install recommended MCP servers? (${autoNames.join(', ')})`,
360
- default: true
361
- }]);
362
-
363
- if (installAuto) {
364
- spinner.start('Installing MCP servers...');
365
- const result = await installAutoMcps(targetPath, orchestration.autoInstallable);
366
- spinner.succeed(`Installed ${result.added.length} MCP server(s)`);
367
- for (const name of result.added) {
368
- autoInstalled[name] = orchestration.autoInstallable[name];
369
- }
370
- }
371
- }
372
-
373
- // Credential MCPsinline setup or instructions
374
- const manualInstalled = {};
375
- if (hasManualMcps) {
376
- for (const [name, entry] of Object.entries(orchestration.needsManualSetup)) {
377
- logger.blank();
378
-
379
- // Show warnings
380
- for (const warning of entry.install.warnings || []) {
381
- logger.warn(` ${warning}`);
382
- }
383
-
384
- const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
385
- const { setupNow } = await inquirer.prompt([{
386
- type: 'confirm',
387
- name: 'setupNow',
388
- message: `Set up ${capitalizedName} MCP now? (${entry.usage})`,
389
- default: false
390
- }]);
391
-
392
- if (setupNow) {
393
- // Collect credentials
394
- const credentialValues = {};
395
- for (const cred of entry.install.credentials) {
396
- const { value } = await inquirer.prompt([{
397
- type: cred.secret ? 'password' : 'input',
398
- name: 'value',
399
- message: `${cred.name}:`,
400
- mask: cred.secret ? '*' : undefined
401
- }]);
402
- credentialValues[cred.envVar] = value;
403
- }
404
-
405
- spinner.start(`Configuring ${name}...`);
406
- await installMcpWithCredentials(targetPath, name, entry, credentialValues);
407
- spinner.succeed(`${capitalizedName} MCP configured`);
408
- manualInstalled[name] = entry;
409
- } else {
410
- // Show setup instructions
411
- const instructions = generateSetupInstructions(name, entry);
412
- logger.blank();
413
- logger.dim(' Add to .claude/settings.local.json → mcpServers:');
414
- logger.dim(' ' + '─'.repeat(55));
415
- for (const line of instructions.configSnippet.split('\n')) {
416
- logger.dim(` ${line}`);
417
- }
418
- logger.dim(' ' + '─'.repeat(55));
419
-
420
- for (const cred of instructions.credentialUrls) {
421
- logger.dim(` Get ${cred.name}: ${cred.helpUrl}`);
422
- }
423
- logger.dim(` Or run later: ${instructions.cliCommand}`);
424
- }
425
- }
426
- }
427
-
428
- // Summary table
429
- const justInstalled = { ...autoInstalled, ...manualInstalled };
430
- const statusRows = formatMcpStatusTable(orchestration, justInstalled);
431
-
432
- if (statusRows.length > 0) {
433
- logger.blank();
434
- logger.info('MCP Servers');
435
- logger.dim('─'.repeat(60));
436
-
437
- const STATUS_ICONS = {
438
- installed: '✓',
439
- available: '○',
440
- already_configured: '✓',
441
- needs_setup: '⚠',
442
- prereq_missing: '✗',
443
- not_relevant: '─'
444
- };
445
-
446
- for (const row of statusRows) {
447
- const icon = STATUS_ICONS[row.status] || '?';
448
- const statusLabel = row.status.replace(/_/g, ' ');
449
- logger.dim(` ${icon} ${row.name.padEnd(15)} ${statusLabel.padEnd(20)} ${row.detail}`);
450
- }
451
- }
452
-
453
- mcpSetupResult = { autoInstalled, manualInstalled, orchestration };
454
- }
455
- } catch (error) {
456
- logger.dim(` MCP setup skipped: ${error.message}`);
457
- }
458
- } else {
459
- logger.dim('\nSkipped MCP setup (--skip-mcp flag)');
460
- }
461
-
462
- spinner.succeed('MORPH-SPEC installed successfully!');
463
-
464
- // Show next steps
465
- logger.blank();
466
- logger.success(`MORPH-SPEC v${cliVersion} installed successfully!`);
467
- logger.blank();
468
- logger.header('Next Steps');
469
-
470
- logger.step(1, 'Run detection: morph-spec detect');
471
- logger.step(2, 'Review .morph/config/config.json and .morph/context/standards.md');
472
- logger.step(3, 'Open project in VS Code with Claude Code');
473
- logger.step(4, 'Start your first feature:');
474
- logger.blank();
475
- logger.box([
476
- 'Ask Claude Code to implement a feature'
477
- ]);
478
- logger.blank();
479
-
480
- logger.info('Files installed:');
481
- logger.dim(` ✓ CLAUDE.md`);
482
- logger.dim(` ✓ .morph/config/ (config.json)`);
483
- logger.dim(` ✓ .morph/framework/ (agents.json, standards/, templates/)`);
484
- logger.dim(` ✓ .morph/context/ (project context)`);
485
- logger.dim(` ✓ .morph/features/ (feature outputs)`);
486
- if (commandsCopied) {
487
- logger.dim(` ✓ .claude/commands/ (slash commands)`);
488
- }
489
- if (rulesCopied) {
490
- logger.dim(` ✓ .claude/rules/ (5 path-scoped rules)`);
491
- }
492
- logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
493
- if (integrationsCreated) {
494
- logger.dim(` ✓ .morph/config/integrations.json (detected plugins & MCPs)`);
495
- }
496
- if (mcpSetupResult) {
497
- const autoCount = Object.keys(mcpSetupResult.autoInstalled).length;
498
- const manualCount = Object.keys(mcpSetupResult.manualInstalled).length;
499
- if (autoCount + manualCount > 0) {
500
- logger.dim(` ✓ MCP servers (${autoCount + manualCount} configured)`);
501
- }
502
- }
503
-
504
- logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
505
- logger.dim(' ✓ .claude/agents/ (native subagents installed)');
506
-
507
- logger.blank();
508
-
509
- // 13. LLM-based auto-detect project context (if Claude Code is available and not --skip-detection)
510
- if (!options.skipDetection && detectClaudeCode()) {
511
- logger.blank();
512
- logger.header('Auto-Detecting Project Context');
513
- logger.dim('Using Claude Code LLM to analyze your project...');
514
- logger.blank();
515
-
516
- try {
517
- const orchestrator = new AutoContextOrchestrator();
518
- const result = await orchestrator.execute(targetPath, {
519
- skipReview: false,
520
- fallbackOnError: true,
521
- wizardMode: options.wizard || false
522
- });
523
-
524
- if (result.success) {
525
- logger.success('Project context detected and saved to .morph/project.md');
526
- } else {
527
- logger.warn('Auto-detection incomplete. Run "morph-spec update" to retry.');
528
- }
529
- } catch (error) {
530
- logger.warn(`Auto-detection failed: ${error.message}`);
531
- logger.dim('You can run "morph-spec update" later to re-analyze your project.');
532
- }
533
- } else if (options.skipDetection) {
534
- logger.dim('\nSkipped auto-detection (--skip-detection flag)');
535
- logger.dim('Run "morph-spec update" later to analyze your project.');
536
- } else {
537
- logger.warn('\n⚠️ Claude Code not detected');
538
- logger.dim('Auto-detection requires Claude Code CLI.');
539
- logger.dim('Run "morph-spec update --wizard" to configure manually.');
540
- }
541
-
542
- // Suggest tutorial for first-time users
543
- if (integrationsCreated) {
544
- logger.blank();
545
- logger.info('First time using morph-spec? Run `morph-spec tutorial` to get started.');
546
- }
547
-
548
- logger.blank();
549
-
550
- } catch (error) {
551
- spinner.fail('Installation failed');
552
- logger.error(error.message);
553
- process.exit(1);
554
- }
555
- }
1
+ import { join } from 'path';
2
+ import { homedir } from 'os';
3
+ import fs from 'fs-extra';
4
+ import ora from 'ora';
5
+ import { logger } from '../../utils/logger.js';
6
+ import {
7
+ pathExists,
8
+ readJson,
9
+ writeJson,
10
+ writeFile
11
+ } from '../../utils/file-copier.js';
12
+ import { getInstalledCLIVersion } from '../../utils/version-checker.js';
13
+ import { installClaudeHooks } from '../../utils/claude-settings-manager.js';
14
+ import inquirer from 'inquirer';
15
+ import { detectProject } from '../../lib/detectors/index.js';
16
+ import { detectClaudeConfig, mapMcpsToPhases, formatConfigSummary } from '../../lib/detectors/claude-config-detector.js';
17
+ import { generateSettings, saveIntegrations } from '../../lib/generators/settings-generator.js';
18
+ import {
19
+ orchestrateMcpSetup,
20
+ installAutoMcps,
21
+ installMcpWithCredentials,
22
+ generateSetupInstructions,
23
+ formatMcpStatusTable
24
+ } from '../../lib/installers/mcp-installer.js';
25
+ import { setupInfra } from '../../scripts/setup-infra.js';
26
+
27
+ export async function initCommand(options) {
28
+ const targetPath = options.path || process.cwd();
29
+
30
+ logger.header('MORPH-SPEC Framework Installation');
31
+ logger.dim(`Target: ${targetPath}`);
32
+ logger.blank();
33
+
34
+ // Check if already initialized
35
+ const morphPath = join(targetPath, '.morph');
36
+ if (await pathExists(morphPath)) {
37
+ if (!options.force) {
38
+ logger.warn('MORPH already initialized in this directory.');
39
+ logger.dim('Use --force to overwrite existing installation.');
40
+ process.exit(1);
41
+ }
42
+ logger.warn('Overwriting existing MORPH installation...');
43
+ }
44
+
45
+ const spinner = ora('Installing MORPH-SPEC...').start();
46
+
47
+ try {
48
+
49
+ // Steps 1-10: Infrastructure (headless, no prompts)
50
+ spinner.stop();
51
+ const infraResult = await setupInfra(targetPath);
52
+ spinner.start('Continuing...');
53
+
54
+ // Derive display variables from what setupInfra installed
55
+ const cliVersion = getInstalledCLIVersion();
56
+ const commandsCopied = await pathExists(join(targetPath, '.claude', 'commands'));
57
+ const rulesCopied = await pathExists(join(targetPath, '.claude', 'rules'));
58
+ // Re-run installClaudeHooks to get the result object (idempotent — hooks already installed)
59
+ const hooksResult = await installClaudeHooks(targetPath);
60
+
61
+ // Re-derive config path for stack detection updates
62
+ const configDir = join(morphPath, 'config');
63
+ const configPath = join(configDir, 'config.json');
64
+ const contextDir = join(morphPath, 'context');
65
+
66
+ // Read config written by setupInfra so stack detection can update it
67
+ let config = (await pathExists(configPath)) ? await readJson(configPath) : { project: {} };
68
+ if (!config.project) config.project = {};
69
+
70
+ // 11b. Detect Claude Code environment (plugins, MCPs, skills)
71
+ spinner.text = 'Detecting Claude Code environment...';
72
+ let claudeConfigDetected = null;
73
+ let integrationsCreated = false;
74
+ try {
75
+ claudeConfigDetected = await detectClaudeConfig(targetPath);
76
+ const hasIntegrations = claudeConfigDetected.plugins.length > 0
77
+ || claudeConfigDetected.mcpServers.length > 0
78
+ || claudeConfigDetected.superpowers.installed;
79
+
80
+ if (hasIntegrations) {
81
+ spinner.stop();
82
+ logger.blank();
83
+ logger.header('Claude Code Environment Detected');
84
+
85
+ const summary = formatConfigSummary(claudeConfigDetected);
86
+ if (summary) {
87
+ logger.info(summary);
88
+ }
89
+
90
+ // Show MCP-to-phase mapping
91
+ if (claudeConfigDetected.mcpServers.length > 0) {
92
+ const phaseMap = mapMcpsToPhases(claudeConfigDetected.mcpServers);
93
+ logger.blank();
94
+ logger.dim('MCP usage by phase:');
95
+ for (const [phase, mcps] of Object.entries(phaseMap)) {
96
+ if (mcps.length > 0) {
97
+ logger.dim(` ${phase}: ${mcps.map(m => m.name).join(', ')}`);
98
+ }
99
+ }
100
+ }
101
+
102
+ logger.blank();
103
+ const { saveIntegrationsChoice } = await inquirer.prompt([{
104
+ type: 'confirm',
105
+ name: 'saveIntegrationsChoice',
106
+ message: 'Save detected integrations and generate optimized settings?',
107
+ default: true
108
+ }]);
109
+
110
+ if (saveIntegrationsChoice) {
111
+ spinner.start('Saving integrations...');
112
+ await saveIntegrations(targetPath, claudeConfigDetected);
113
+ await generateSettings(targetPath, claudeConfigDetected, config);
114
+ integrationsCreated = true;
115
+ spinner.succeed('Integrations saved to .morph/config/integrations.json');
116
+ } else {
117
+ spinner.start('Continuing...');
118
+ }
119
+ }
120
+ } catch (error) {
121
+ // Non-fatal: continue without integrations
122
+ logger.dim(` Claude config detection skipped: ${error.message}`);
123
+ }
124
+
125
+ // 11c. Stack detection (moved before MCP setup so we know the stack)
126
+ spinner.text = 'Detecting project stack...';
127
+ let detectedStack = 'unknown';
128
+ const hasProjectFiles = await pathExists(join(targetPath, 'package.json'))
129
+ || await pathExists(join(targetPath, 'src'))
130
+ || (await fs.readdir(targetPath)).some(f => f.endsWith('.csproj'));
131
+
132
+ if (hasProjectFiles) {
133
+ try {
134
+ const quickResults = await detectProject(targetPath, { conversation: false, generateStandards: false });
135
+ const structure = quickResults.structure;
136
+
137
+ if (structure?.stack && structure.stack !== 'unknown') {
138
+ detectedStack = structure.stack;
139
+
140
+ // Persist detected stack to config.json
141
+ if (structure?.stack && structure.stack !== 'unknown') {
142
+ config.project.stack = structure.stack;
143
+ }
144
+ if (structure?.architecture && structure.architecture !== 'unknown') {
145
+ config.project.architecture = structure.architecture;
146
+ }
147
+ if (structure?.uiLibrary) {
148
+ config.project.uiLibrary = structure.uiLibrary;
149
+ }
150
+ await writeJson(configPath, config);
151
+
152
+ spinner.stop();
153
+ logger.blank();
154
+ logger.header('Existing Project Detected');
155
+ logger.info(`Stack detected: ${structure.stack}`);
156
+ if (structure?.architecture) logger.dim(`Architecture: ${structure.architecture}`);
157
+ if (structure?.uiLibrary) logger.dim(`UI Library: ${structure.uiLibrary}`);
158
+
159
+ const { standardsChoice } = await inquirer.prompt([{
160
+ type: 'list',
161
+ name: 'standardsChoice',
162
+ message: 'How should morph-spec handle your project standards?',
163
+ choices: [
164
+ { name: 'Keep morph-spec defaults (recommended)', value: 'morph-spec' },
165
+ { name: 'Detect project-specific standards', value: 'detect' }
166
+ ]
167
+ }]);
168
+
169
+ if (standardsChoice === 'detect') {
170
+ spinner.start('Generating project-specific standards...');
171
+ const fullResults = await detectProject(targetPath, { conversation: false });
172
+ const inferredPath = join(contextDir, 'standards.md');
173
+ await writeFile(inferredPath, fullResults.inferred.markdown);
174
+ spinner.succeed('Generated .morph/context/standards.md');
175
+ logger.dim('Review and edit .morph/context/standards.md as needed.');
176
+ }
177
+
178
+ spinner.start('Continuing...');
179
+ }
180
+ } catch (error) {
181
+ logger.dim(` Stack detection skipped: ${error.message}`);
182
+ }
183
+ }
184
+
185
+ // 11d. MCP Auto-Setup
186
+ let mcpSetupResult = null;
187
+ if (!options.skipMcp) {
188
+ try {
189
+ spinner.stop();
190
+ const existingMcps = claudeConfigDetected?.mcpServers || [];
191
+ const orchestration = orchestrateMcpSetup(targetPath, detectedStack, existingMcps);
192
+
193
+ const autoNames = Object.keys(orchestration.autoInstallable);
194
+ const manualNames = Object.keys(orchestration.needsManualSetup);
195
+ const hasAutoMcps = autoNames.length > 0;
196
+ const hasManualMcps = manualNames.length > 0;
197
+
198
+ if (hasAutoMcps || hasManualMcps) {
199
+ logger.blank();
200
+ logger.header('MCP Server Setup');
201
+
202
+ // Auto-install prompt
203
+ let autoInstalled = {};
204
+ if (hasAutoMcps) {
205
+ const { installAuto } = await inquirer.prompt([{
206
+ type: 'confirm',
207
+ name: 'installAuto',
208
+ message: `Install recommended MCP servers? (${autoNames.join(', ')})`,
209
+ default: true
210
+ }]);
211
+
212
+ if (installAuto) {
213
+ spinner.start('Installing MCP servers...');
214
+ const result = await installAutoMcps(targetPath, orchestration.autoInstallable);
215
+ spinner.succeed(`Installed ${result.added.length} MCP server(s)`);
216
+ for (const name of result.added) {
217
+ autoInstalled[name] = orchestration.autoInstallable[name];
218
+ }
219
+ }
220
+ }
221
+
222
+ // Credential MCPs inline setup or instructions
223
+ const manualInstalled = {};
224
+ if (hasManualMcps) {
225
+ for (const [name, entry] of Object.entries(orchestration.needsManualSetup)) {
226
+ logger.blank();
227
+
228
+ // Show warnings
229
+ for (const warning of entry.install.warnings || []) {
230
+ logger.warn(` ${warning}`);
231
+ }
232
+
233
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
234
+ const { setupNow } = await inquirer.prompt([{
235
+ type: 'confirm',
236
+ name: 'setupNow',
237
+ message: `Set up ${capitalizedName} MCP now? (${entry.usage})`,
238
+ default: false
239
+ }]);
240
+
241
+ if (setupNow) {
242
+ // Collect credentials
243
+ const credentialValues = {};
244
+ for (const cred of entry.install.credentials) {
245
+ const { value } = await inquirer.prompt([{
246
+ type: cred.secret ? 'password' : 'input',
247
+ name: 'value',
248
+ message: `${cred.name}:`,
249
+ mask: cred.secret ? '*' : undefined
250
+ }]);
251
+ credentialValues[cred.envVar] = value;
252
+ }
253
+
254
+ spinner.start(`Configuring ${name}...`);
255
+ await installMcpWithCredentials(targetPath, name, entry, credentialValues);
256
+ spinner.succeed(`${capitalizedName} MCP configured`);
257
+ manualInstalled[name] = entry;
258
+ } else {
259
+ // Show setup instructions
260
+ const instructions = generateSetupInstructions(name, entry);
261
+ logger.blank();
262
+ logger.dim(' Add to .claude/settings.local.json → mcpServers:');
263
+ logger.dim(' ' + '─'.repeat(55));
264
+ for (const line of instructions.configSnippet.split('\n')) {
265
+ logger.dim(` ${line}`);
266
+ }
267
+ logger.dim(' ' + '─'.repeat(55));
268
+
269
+ for (const cred of instructions.credentialUrls) {
270
+ logger.dim(` Get ${cred.name}: ${cred.helpUrl}`);
271
+ }
272
+ logger.dim(` Or run later: ${instructions.cliCommand}`);
273
+ }
274
+ }
275
+ }
276
+
277
+ // Summary table
278
+ const justInstalled = { ...autoInstalled, ...manualInstalled };
279
+ const statusRows = formatMcpStatusTable(orchestration, justInstalled);
280
+
281
+ if (statusRows.length > 0) {
282
+ logger.blank();
283
+ logger.info('MCP Servers');
284
+ logger.dim('─'.repeat(60));
285
+
286
+ const STATUS_ICONS = {
287
+ installed: '✓',
288
+ available: '',
289
+ already_configured: '✓',
290
+ needs_setup: '⚠',
291
+ prereq_missing: '✗',
292
+ not_relevant: ''
293
+ };
294
+
295
+ for (const row of statusRows) {
296
+ const icon = STATUS_ICONS[row.status] || '?';
297
+ const statusLabel = row.status.replace(/_/g, ' ');
298
+ logger.dim(` ${icon} ${row.name.padEnd(15)} ${statusLabel.padEnd(20)} ${row.detail}`);
299
+ }
300
+ }
301
+
302
+ mcpSetupResult = { autoInstalled, manualInstalled, orchestration };
303
+ }
304
+ } catch (error) {
305
+ logger.dim(` MCP setup skipped: ${error.message}`);
306
+ }
307
+ } else {
308
+ logger.dim('\nSkipped MCP setup (--skip-mcp flag)');
309
+ }
310
+
311
+ spinner.succeed('MORPH-SPEC installed successfully!');
312
+
313
+ // Show next steps
314
+ logger.blank();
315
+ logger.success(`MORPH-SPEC v${cliVersion} installed successfully!`);
316
+ logger.blank();
317
+ logger.header('Next Steps');
318
+
319
+ logger.step(1, 'Review .morph/config/config.json');
320
+ logger.step(2, 'Open project in VS Code with Claude Code');
321
+ logger.step(3, 'Start your first feature:');
322
+ logger.blank();
323
+ logger.box([
324
+ 'Ask Claude Code to implement a feature'
325
+ ]);
326
+ logger.blank();
327
+
328
+ logger.info('Files installed:');
329
+ logger.dim(` ✓ CLAUDE.md`);
330
+ logger.dim(` ✓ .morph/config/ (config.json)`);
331
+ logger.dim(` ✓ .morph/framework/ (agents.json, standards/, templates/, hooks/)`);
332
+ logger.dim(` .morph/context/ (project context)`);
333
+ logger.dim(` ✓ .morph/features/ (feature outputs)`);
334
+ if (commandsCopied) {
335
+ logger.dim(` ✓ .claude/commands/ (slash commands)`);
336
+ }
337
+ if (rulesCopied) {
338
+ logger.dim(` ✓ .claude/rules/ (5 path-scoped rules)`);
339
+ }
340
+ logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
341
+ if (integrationsCreated) {
342
+ logger.dim(` ✓ .morph/config/integrations.json (detected plugins & MCPs)`);
343
+ }
344
+ if (mcpSetupResult) {
345
+ const autoCount = Object.keys(mcpSetupResult.autoInstalled).length;
346
+ const manualCount = Object.keys(mcpSetupResult.manualInstalled).length;
347
+ if (autoCount + manualCount > 0) {
348
+ logger.dim(` ✓ MCP servers (${autoCount + manualCount} configured)`);
349
+ }
350
+ }
351
+
352
+ logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
353
+
354
+ // Show agent team breakdown from setup-infra result
355
+ const agents = infraResult?.agents;
356
+ if (agents) {
357
+ const total = agents.tier1 + agents.tier2 + agents.specialists;
358
+ logger.dim(` ✓ .claude/agents/ (${total} agents: ${agents.tier1} orchestrators, ${agents.tier2} domain leaders, ${agents.specialists} specialists)`);
359
+ } else {
360
+ logger.dim(' ✓ .claude/agents/ (native subagents installed)');
361
+ }
362
+
363
+ logger.dim(' ✓ ~/.claude/statusline.sh (global statusline installed)');
364
+
365
+ // Check CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS in ~/.claude/settings.local.json
366
+ logger.blank();
367
+ const globalSettingsPath = join(homedir(), '.claude', 'settings.local.json');
368
+ let agentTeamsEnabled = false;
369
+ try {
370
+ const globalSettings = await readJson(globalSettingsPath);
371
+ agentTeamsEnabled = globalSettings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === '1';
372
+ } catch {
373
+ // File missing or unreadable treat as not enabled
374
+ }
375
+
376
+ if (agentTeamsEnabled) {
377
+ logger.success('Agent Teams: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS enabled ✓');
378
+ } else {
379
+ logger.warn('Agent Teams not enabled. To activate multi-agent workflows, add to ~/.claude/settings.local.json:');
380
+ logger.dim(' {"env": {"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"}}');
381
+ }
382
+
383
+ logger.blank();
384
+
385
+ // Suggest tutorial for first-time users
386
+ if (integrationsCreated) {
387
+ logger.blank();
388
+ logger.info('First time using morph-spec? Run `morph-spec tutorial` to get started.');
389
+ }
390
+
391
+ logger.blank();
392
+
393
+ } catch (error) {
394
+ spinner.fail('Installation failed');
395
+ logger.error(error.message);
396
+ process.exit(1);
397
+ }
398
+ }