@polymorphism-tech/morph-spec 7.2.0 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/bin/morph-spec.js +4 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +29 -12
- package/framework/agents.json +132 -132
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-proposal.md +1 -1
- package/framework/evals/rubrics/README.md +9 -8
- package/framework/hooks/README.md +3 -1
- package/framework/hooks/claude-code/core/gate-guard.js +160 -0
- package/framework/hooks/claude-code/core/state-sync.js +15 -3
- package/framework/hooks/claude-code/core/validate-completion.js +241 -136
- package/framework/hooks/shared/state-reader.js +236 -221
- package/framework/phases.json +1 -1
- package/framework/skills/README.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +63 -21
- package/framework/skills/core/morph-brainstorming/SKILL.md +5 -1
- package/framework/skills/core/morph-eval/SKILL.md +4 -5
- package/framework/skills/core/morph-implement/SKILL.md +47 -19
- package/framework/skills/core/morph-plan/SKILL.md +8 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +13 -6
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/setup.md +258 -244
- package/framework/templates/README.md +708 -709
- package/framework/templates/REGISTRY.json +1 -20
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/proposal.md +171 -171
- package/framework/templates/feature/recap.md +145 -145
- package/package.json +1 -1
- package/src/commands/project/doctor.js +941 -946
- package/src/commands/project/init.js +517 -515
- package/src/commands/project/status.js +373 -376
- package/src/commands/state/approve.js +251 -213
- package/src/commands/validation/validate-feature.js +356 -287
- package/src/core/state/state-manager.js +910 -934
- package/src/dashboard/app/App.jsx +29 -31
- package/src/dashboard/app/components/Sidebar.jsx +44 -45
- package/src/dashboard/scanner/index.js +20 -21
- package/src/lib/context/context-builder.js +3 -3
- package/src/lib/generators/context-generator.js +2 -3
- package/src/lib/generators/mandate-generator.js +48 -92
- package/src/lib/generators/metadata-extractor.js +385 -386
- package/src/lib/generators/recap-generator.js +285 -285
- package/src/lib/risk-detector.js +2 -2
- package/src/lib/validators/content/content-validator.js +89 -286
- package/src/lib/validators/content/index.js +0 -1
- package/src/utils/hooks-installer.js +527 -516
- package/framework/templates/feature/tasks.md +0 -199
- package/framework/workflows/README.md +0 -1041
- package/framework/workflows/configs/fast-track.json +0 -46
- package/framework/workflows/configs/full.json +0 -64
- package/framework/workflows/configs/quick.json +0 -32
- package/framework/workflows/configs/standard.json +0 -57
- package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -392
- package/framework/workflows/docs/design-impl.md +0 -37
- package/framework/workflows/docs/enforcement-pipeline.md +0 -668
- package/framework/workflows/docs/fast-track.md +0 -29
- package/framework/workflows/docs/full-morph.md +0 -69
- package/framework/workflows/docs/standard.md +0 -49
- package/framework/workflows/docs/ui-refresh.md +0 -39
- package/src/commands/state/index.js +0 -7
- package/src/core/workflows/workflow-detector.js +0 -586
- package/src/core/workflows/workflow-router.js +0 -204
- package/src/dashboard/app/pages/Workflows.jsx +0 -346
- package/src/dashboard/scanner/workflows-scanner.js +0 -18
- package/src/lib/phase-chain/phase-validator.js +0 -359
- package/src/lib/validators/content/cross-phase-validator.js +0 -281
- package/src/lib/validators/spec-validator.js +0 -312
|
@@ -1,515 +1,517 @@
|
|
|
1
|
-
import { join, dirname } from 'path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
import { logger } from '../../utils/logger.js';
|
|
8
|
-
import {
|
|
9
|
-
pathExists,
|
|
10
|
-
readJson,
|
|
11
|
-
writeJson,
|
|
12
|
-
writeFile
|
|
13
|
-
} from '../../utils/file-copier.js';
|
|
14
|
-
import { getInstalledCLIVersion, checkProjectOutdated } from '../../utils/version-checker.js';
|
|
15
|
-
import { installClaudeHooks } from '../../utils/claude-settings-manager.js';
|
|
16
|
-
import inquirer from 'inquirer';
|
|
17
|
-
import { detectProject } from '../../lib/detectors/index.js';
|
|
18
|
-
import { detectClaudeConfig, mapMcpsToPhases, formatConfigSummary } from '../../lib/detectors/claude-config-detector.js';
|
|
19
|
-
import { generateSettings, saveIntegrations } from '../../lib/generators/settings-generator.js';
|
|
20
|
-
import {
|
|
21
|
-
orchestrateMcpSetup,
|
|
22
|
-
installAutoMcps,
|
|
23
|
-
installCompanionSkills,
|
|
24
|
-
installMcpWithCredentials,
|
|
25
|
-
generateSetupInstructions,
|
|
26
|
-
formatMcpStatusTable,
|
|
27
|
-
isRemoteMcp,
|
|
28
|
-
setupStitch
|
|
29
|
-
} from '../../lib/installers/mcp-installer.js';
|
|
30
|
-
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
31
|
-
import { installSkills } from '../../utils/skills-installer.js';
|
|
32
|
-
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
33
|
-
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
34
|
-
import { updateCommand } from './update.js';
|
|
35
|
-
|
|
36
|
-
export async function initCommand(options) {
|
|
37
|
-
const targetPath = options.path || process.cwd();
|
|
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
|
-
// Check if project version is outdated — auto-update instead of blocking
|
|
48
|
-
const projectCheck = await checkProjectOutdated(targetPath);
|
|
49
|
-
if (projectCheck.isOutdated) {
|
|
50
|
-
logger.info(`MORPH already initialized (v${projectCheck.current}). Framework update available (v${projectCheck.cliVersion}).`);
|
|
51
|
-
logger.blank();
|
|
52
|
-
return updateCommand(options);
|
|
53
|
-
}
|
|
54
|
-
logger.warn('MORPH already initialized in this directory (up to date).');
|
|
55
|
-
logger.dim('Use --force to overwrite existing installation.');
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
logger.warn('Overwriting existing MORPH installation...');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const spinner = ora('Installing MORPH-SPEC...').start();
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
|
|
65
|
-
// Steps 1-10: Infrastructure (headless, no prompts)
|
|
66
|
-
spinner.stop();
|
|
67
|
-
const infraResult = await setupInfra(targetPath);
|
|
68
|
-
spinner.start('Continuing...');
|
|
69
|
-
|
|
70
|
-
// Derive display variables from what setupInfra installed
|
|
71
|
-
const cliVersion = getInstalledCLIVersion();
|
|
72
|
-
const commandsCopied = await pathExists(join(targetPath, '.claude', 'commands'));
|
|
73
|
-
const rulesCopied = await pathExists(join(targetPath, '.claude', 'rules'));
|
|
74
|
-
// Re-run installClaudeHooks to get the result object (idempotent — hooks already installed)
|
|
75
|
-
const hooksResult = await installClaudeHooks(targetPath);
|
|
76
|
-
|
|
77
|
-
// Re-derive config path for stack detection updates
|
|
78
|
-
const configDir = join(morphPath, 'config');
|
|
79
|
-
const configPath = join(configDir, 'config.json');
|
|
80
|
-
const contextDir = join(morphPath, 'context');
|
|
81
|
-
|
|
82
|
-
// Read config written by setupInfra so stack detection can update it
|
|
83
|
-
let config = (await pathExists(configPath)) ? await readJson(configPath) : { project: {} };
|
|
84
|
-
if (!config.project) config.project = {};
|
|
85
|
-
|
|
86
|
-
// 11b. Detect Claude Code environment (plugins, MCPs, skills)
|
|
87
|
-
spinner.text = 'Detecting Claude Code environment...';
|
|
88
|
-
let claudeConfigDetected = null;
|
|
89
|
-
let integrationsCreated = false;
|
|
90
|
-
try {
|
|
91
|
-
claudeConfigDetected = await detectClaudeConfig(targetPath);
|
|
92
|
-
const hasIntegrations = claudeConfigDetected.plugins.length > 0
|
|
93
|
-
|| claudeConfigDetected.mcpServers.length > 0
|
|
94
|
-
|| claudeConfigDetected.superpowers.installed;
|
|
95
|
-
|
|
96
|
-
if (hasIntegrations) {
|
|
97
|
-
spinner.stop();
|
|
98
|
-
logger.blank();
|
|
99
|
-
logger.header('Claude Code Environment Detected');
|
|
100
|
-
|
|
101
|
-
const summary = formatConfigSummary(claudeConfigDetected);
|
|
102
|
-
if (summary) {
|
|
103
|
-
logger.info(summary);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Show MCP-to-phase mapping
|
|
107
|
-
if (claudeConfigDetected.mcpServers.length > 0) {
|
|
108
|
-
const phaseMap = mapMcpsToPhases(claudeConfigDetected.mcpServers);
|
|
109
|
-
logger.blank();
|
|
110
|
-
logger.dim('MCP usage by phase:');
|
|
111
|
-
for (const [phase, mcps] of Object.entries(phaseMap)) {
|
|
112
|
-
if (mcps.length > 0) {
|
|
113
|
-
logger.dim(` ${phase}: ${mcps.map(m => m.name).join(', ')}`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
logger.blank();
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
spinner.
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logger.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (structure?.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
spinner.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
for (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
logger.
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
spinner.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
logger.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
logger.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
await
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
logger.blank();
|
|
418
|
-
logger.
|
|
419
|
-
|
|
420
|
-
logger.
|
|
421
|
-
|
|
422
|
-
logger.step(
|
|
423
|
-
logger.
|
|
424
|
-
logger.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
logger.
|
|
430
|
-
|
|
431
|
-
logger.
|
|
432
|
-
logger.dim(` ✓ .
|
|
433
|
-
logger.dim(` ✓ .morph/
|
|
434
|
-
logger.dim(` ✓ .morph/
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
logger.dim(
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
logger.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
logger.dim(`
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
logger.
|
|
508
|
-
logger.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1
|
+
import { join, dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { logger } from '../../utils/logger.js';
|
|
8
|
+
import {
|
|
9
|
+
pathExists,
|
|
10
|
+
readJson,
|
|
11
|
+
writeJson,
|
|
12
|
+
writeFile
|
|
13
|
+
} from '../../utils/file-copier.js';
|
|
14
|
+
import { getInstalledCLIVersion, checkProjectOutdated } from '../../utils/version-checker.js';
|
|
15
|
+
import { installClaudeHooks } from '../../utils/claude-settings-manager.js';
|
|
16
|
+
import inquirer from 'inquirer';
|
|
17
|
+
import { detectProject } from '../../lib/detectors/index.js';
|
|
18
|
+
import { detectClaudeConfig, mapMcpsToPhases, formatConfigSummary } from '../../lib/detectors/claude-config-detector.js';
|
|
19
|
+
import { generateSettings, saveIntegrations } from '../../lib/generators/settings-generator.js';
|
|
20
|
+
import {
|
|
21
|
+
orchestrateMcpSetup,
|
|
22
|
+
installAutoMcps,
|
|
23
|
+
installCompanionSkills,
|
|
24
|
+
installMcpWithCredentials,
|
|
25
|
+
generateSetupInstructions,
|
|
26
|
+
formatMcpStatusTable,
|
|
27
|
+
isRemoteMcp,
|
|
28
|
+
setupStitch
|
|
29
|
+
} from '../../lib/installers/mcp-installer.js';
|
|
30
|
+
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
31
|
+
import { installSkills } from '../../utils/skills-installer.js';
|
|
32
|
+
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
33
|
+
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
34
|
+
import { updateCommand } from './update.js';
|
|
35
|
+
|
|
36
|
+
export async function initCommand(options) {
|
|
37
|
+
const targetPath = options.path || process.cwd();
|
|
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
|
+
// Check if project version is outdated — auto-update instead of blocking
|
|
48
|
+
const projectCheck = await checkProjectOutdated(targetPath);
|
|
49
|
+
if (projectCheck.isOutdated) {
|
|
50
|
+
logger.info(`MORPH already initialized (v${projectCheck.current}). Framework update available (v${projectCheck.cliVersion}).`);
|
|
51
|
+
logger.blank();
|
|
52
|
+
return updateCommand(options);
|
|
53
|
+
}
|
|
54
|
+
logger.warn('MORPH already initialized in this directory (up to date).');
|
|
55
|
+
logger.dim('Use --force to overwrite existing installation.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
logger.warn('Overwriting existing MORPH installation...');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const spinner = ora('Installing MORPH-SPEC...').start();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
|
|
65
|
+
// Steps 1-10: Infrastructure (headless, no prompts)
|
|
66
|
+
spinner.stop();
|
|
67
|
+
const infraResult = await setupInfra(targetPath);
|
|
68
|
+
spinner.start('Continuing...');
|
|
69
|
+
|
|
70
|
+
// Derive display variables from what setupInfra installed
|
|
71
|
+
const cliVersion = getInstalledCLIVersion();
|
|
72
|
+
const commandsCopied = await pathExists(join(targetPath, '.claude', 'commands'));
|
|
73
|
+
const rulesCopied = await pathExists(join(targetPath, '.claude', 'rules'));
|
|
74
|
+
// Re-run installClaudeHooks to get the result object (idempotent — hooks already installed)
|
|
75
|
+
const hooksResult = await installClaudeHooks(targetPath);
|
|
76
|
+
|
|
77
|
+
// Re-derive config path for stack detection updates
|
|
78
|
+
const configDir = join(morphPath, 'config');
|
|
79
|
+
const configPath = join(configDir, 'config.json');
|
|
80
|
+
const contextDir = join(morphPath, 'context');
|
|
81
|
+
|
|
82
|
+
// Read config written by setupInfra so stack detection can update it
|
|
83
|
+
let config = (await pathExists(configPath)) ? await readJson(configPath) : { project: {} };
|
|
84
|
+
if (!config.project) config.project = {};
|
|
85
|
+
|
|
86
|
+
// 11b. Detect Claude Code environment (plugins, MCPs, skills)
|
|
87
|
+
spinner.text = 'Detecting Claude Code environment...';
|
|
88
|
+
let claudeConfigDetected = null;
|
|
89
|
+
let integrationsCreated = false;
|
|
90
|
+
try {
|
|
91
|
+
claudeConfigDetected = await detectClaudeConfig(targetPath);
|
|
92
|
+
const hasIntegrations = claudeConfigDetected.plugins.length > 0
|
|
93
|
+
|| claudeConfigDetected.mcpServers.length > 0
|
|
94
|
+
|| claudeConfigDetected.superpowers.installed;
|
|
95
|
+
|
|
96
|
+
if (hasIntegrations) {
|
|
97
|
+
spinner.stop();
|
|
98
|
+
logger.blank();
|
|
99
|
+
logger.header('Claude Code Environment Detected');
|
|
100
|
+
|
|
101
|
+
const summary = formatConfigSummary(claudeConfigDetected);
|
|
102
|
+
if (summary) {
|
|
103
|
+
logger.info(summary);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Show MCP-to-phase mapping
|
|
107
|
+
if (claudeConfigDetected.mcpServers.length > 0) {
|
|
108
|
+
const phaseMap = mapMcpsToPhases(claudeConfigDetected.mcpServers);
|
|
109
|
+
logger.blank();
|
|
110
|
+
logger.dim('MCP usage by phase:');
|
|
111
|
+
for (const [phase, mcps] of Object.entries(phaseMap)) {
|
|
112
|
+
if (mcps.length > 0) {
|
|
113
|
+
logger.dim(` ${phase}: ${mcps.map(m => m.name).join(', ')}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.blank();
|
|
119
|
+
const saveIntegrationsChoice = process.stdin.isTTY
|
|
120
|
+
? (await inquirer.prompt([{
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
name: 'saveIntegrationsChoice',
|
|
123
|
+
message: 'Save detected integrations and generate optimized settings?',
|
|
124
|
+
default: true
|
|
125
|
+
}])).saveIntegrationsChoice
|
|
126
|
+
: true;
|
|
127
|
+
|
|
128
|
+
if (saveIntegrationsChoice) {
|
|
129
|
+
spinner.start('Saving integrations...');
|
|
130
|
+
await saveIntegrations(targetPath, claudeConfigDetected);
|
|
131
|
+
await generateSettings(targetPath, claudeConfigDetected, config);
|
|
132
|
+
integrationsCreated = true;
|
|
133
|
+
spinner.succeed('Integrations saved to .morph/config/integrations.json');
|
|
134
|
+
} else {
|
|
135
|
+
spinner.start('Continuing...');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Non-fatal: continue without integrations
|
|
140
|
+
logger.dim(` Claude config detection skipped: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 11c. Stack detection (moved before MCP setup so we know the stack)
|
|
144
|
+
spinner.text = 'Detecting project stack...';
|
|
145
|
+
let detectedStack = 'unknown';
|
|
146
|
+
const hasProjectFiles = await pathExists(join(targetPath, 'package.json'))
|
|
147
|
+
|| await pathExists(join(targetPath, 'src'))
|
|
148
|
+
|| (await fs.readdir(targetPath)).some(f => f.endsWith('.csproj'));
|
|
149
|
+
|
|
150
|
+
if (hasProjectFiles) {
|
|
151
|
+
try {
|
|
152
|
+
const quickResults = await detectProject(targetPath, { conversation: false, generateStandards: false });
|
|
153
|
+
const structure = quickResults.structure;
|
|
154
|
+
|
|
155
|
+
if (structure?.stack && structure.stack !== 'unknown') {
|
|
156
|
+
detectedStack = structure.stack;
|
|
157
|
+
|
|
158
|
+
// Persist detected stack to config.json
|
|
159
|
+
if (structure?.stack && structure.stack !== 'unknown') {
|
|
160
|
+
config.project.stack = structure.stack;
|
|
161
|
+
}
|
|
162
|
+
if (structure?.architecture && structure.architecture !== 'unknown') {
|
|
163
|
+
config.project.architecture = structure.architecture;
|
|
164
|
+
}
|
|
165
|
+
if (structure?.uiLibrary) {
|
|
166
|
+
config.project.uiLibrary = structure.uiLibrary;
|
|
167
|
+
}
|
|
168
|
+
if (structure?.integrations?.length > 0) {
|
|
169
|
+
config.project.integrations = structure.integrations;
|
|
170
|
+
}
|
|
171
|
+
await writeJson(configPath, config);
|
|
172
|
+
|
|
173
|
+
spinner.stop();
|
|
174
|
+
logger.blank();
|
|
175
|
+
logger.header('Existing Project Detected');
|
|
176
|
+
logger.info(`Stack detected: ${structure.stack}`);
|
|
177
|
+
if (structure?.architecture) logger.dim(`Architecture: ${structure.architecture}`);
|
|
178
|
+
if (structure?.uiLibrary) logger.dim(`UI Library: ${structure.uiLibrary}`);
|
|
179
|
+
if (structure?.integrations?.length > 0) logger.dim(`Integrations: ${structure.integrations.join(', ')}`);
|
|
180
|
+
|
|
181
|
+
const { standardsChoice } = await inquirer.prompt([{
|
|
182
|
+
type: 'list',
|
|
183
|
+
name: 'standardsChoice',
|
|
184
|
+
message: 'How should morph-spec handle your project standards?',
|
|
185
|
+
choices: [
|
|
186
|
+
{ name: 'Keep morph-spec defaults (recommended)', value: 'morph-spec' },
|
|
187
|
+
{ name: 'Detect project-specific standards', value: 'detect' }
|
|
188
|
+
]
|
|
189
|
+
}]);
|
|
190
|
+
|
|
191
|
+
if (standardsChoice === 'detect') {
|
|
192
|
+
spinner.start('Generating project-specific standards...');
|
|
193
|
+
const fullResults = await detectProject(targetPath, { conversation: false });
|
|
194
|
+
const inferredPath = join(contextDir, 'standards.md');
|
|
195
|
+
await writeFile(inferredPath, fullResults.inferred.markdown);
|
|
196
|
+
spinner.succeed('Generated .morph/context/standards.md');
|
|
197
|
+
logger.dim('Review and edit .morph/context/standards.md as needed.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Re-install stack-filtered assets now that we know the stack
|
|
201
|
+
const stackTags = getProjectTags(config);
|
|
202
|
+
if (stackTags.length > 0) {
|
|
203
|
+
spinner.start('Re-installing stack-filtered assets...');
|
|
204
|
+
const frameworkDir = join(targetPath, '.morph', 'framework');
|
|
205
|
+
const fwSrcDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'framework');
|
|
206
|
+
|
|
207
|
+
// Rules
|
|
208
|
+
const rulesSrc = join(fwSrcDir, 'rules');
|
|
209
|
+
const rulesDest = join(targetPath, '.claude', 'rules');
|
|
210
|
+
installRulesForStack(rulesSrc, rulesDest, stackTags);
|
|
211
|
+
|
|
212
|
+
// Skills
|
|
213
|
+
await installSkills(targetPath, { projectTags: stackTags });
|
|
214
|
+
|
|
215
|
+
// Agents (domain personas from agents.json)
|
|
216
|
+
await installAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
217
|
+
await installDomainAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
218
|
+
|
|
219
|
+
spinner.succeed(`Stack-filtered assets reinstalled for [${stackTags.join(', ')}]`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
spinner.start('Continuing...');
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
logger.dim(` Stack detection skipped: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 11d. MCP Auto-Setup
|
|
230
|
+
let mcpSetupResult = null;
|
|
231
|
+
if (!options.skipMcp) {
|
|
232
|
+
try {
|
|
233
|
+
spinner.stop();
|
|
234
|
+
const existingMcps = claudeConfigDetected?.mcpServers || [];
|
|
235
|
+
const orchestration = orchestrateMcpSetup(targetPath, detectedStack, existingMcps);
|
|
236
|
+
|
|
237
|
+
const autoNames = Object.keys(orchestration.autoInstallable);
|
|
238
|
+
const manualNames = Object.keys(orchestration.needsManualSetup);
|
|
239
|
+
const hasAutoMcps = autoNames.length > 0;
|
|
240
|
+
const hasManualMcps = manualNames.length > 0;
|
|
241
|
+
|
|
242
|
+
if (hasAutoMcps || hasManualMcps) {
|
|
243
|
+
logger.blank();
|
|
244
|
+
logger.header('MCP Server Setup');
|
|
245
|
+
|
|
246
|
+
// Auto-install prompt
|
|
247
|
+
let autoInstalled = {};
|
|
248
|
+
if (hasAutoMcps) {
|
|
249
|
+
const { installAuto } = await inquirer.prompt([{
|
|
250
|
+
type: 'confirm',
|
|
251
|
+
name: 'installAuto',
|
|
252
|
+
message: `Install recommended MCP servers? (${autoNames.join(', ')})`,
|
|
253
|
+
default: true
|
|
254
|
+
}]);
|
|
255
|
+
|
|
256
|
+
if (installAuto) {
|
|
257
|
+
spinner.start('Installing MCP servers...');
|
|
258
|
+
const result = await installAutoMcps(targetPath, orchestration.autoInstallable);
|
|
259
|
+
spinner.succeed(`Installed ${result.added.length} MCP server(s)`);
|
|
260
|
+
for (const name of result.added) {
|
|
261
|
+
autoInstalled[name] = orchestration.autoInstallable[name];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Install companion skills for MCPs that define them (e.g., Stitch agent skills)
|
|
265
|
+
const skillResults = installCompanionSkills(orchestration.autoInstallable);
|
|
266
|
+
for (const r of skillResults) {
|
|
267
|
+
if (r.success) {
|
|
268
|
+
logger.info(`✓ Companion skills installed: ${r.repo}`);
|
|
269
|
+
} else {
|
|
270
|
+
logger.warn(`Failed to install companion skills: ${r.repo}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Credential MCPs — inline setup or instructions
|
|
277
|
+
const manualInstalled = {};
|
|
278
|
+
if (hasManualMcps) {
|
|
279
|
+
for (const [name, entry] of Object.entries(orchestration.needsManualSetup)) {
|
|
280
|
+
logger.blank();
|
|
281
|
+
|
|
282
|
+
// Show warnings
|
|
283
|
+
for (const warning of entry.install.warnings || []) {
|
|
284
|
+
logger.warn(` ${warning}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
288
|
+
const { setupNow } = await inquirer.prompt([{
|
|
289
|
+
type: 'confirm',
|
|
290
|
+
name: 'setupNow',
|
|
291
|
+
message: `Set up ${capitalizedName} MCP now? (${entry.usage})`,
|
|
292
|
+
default: false
|
|
293
|
+
}]);
|
|
294
|
+
|
|
295
|
+
if (setupNow) {
|
|
296
|
+
// Stitch — automated setup via setupStitch()
|
|
297
|
+
if (name === 'stitch' && !entry.install.autoInstall) {
|
|
298
|
+
spinner.start('Setting up Stitch MCP...');
|
|
299
|
+
const stitchResult = await setupStitch(targetPath);
|
|
300
|
+
if (stitchResult.success) {
|
|
301
|
+
spinner.succeed('Stitch MCP setup complete');
|
|
302
|
+
manualInstalled[name] = entry;
|
|
303
|
+
} else {
|
|
304
|
+
const failedStep = stitchResult.steps.find(s => !s.success && s.step !== 'enable-api' && s.step !== 'health-check');
|
|
305
|
+
spinner.fail(`Stitch setup failed: ${failedStep?.error || 'unknown'}`);
|
|
306
|
+
}
|
|
307
|
+
if (entry.companionSkills?.installCommand) {
|
|
308
|
+
const skillResults = installCompanionSkills({ stitch: entry });
|
|
309
|
+
for (const r of skillResults) {
|
|
310
|
+
if (r.success) logger.success(` Companion skills installed: ${r.repo}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Remote MCPs — launch OAuth via claude mcp add
|
|
317
|
+
if (isRemoteMcp(entry.install.config)) {
|
|
318
|
+
const url = entry.install.config.url;
|
|
319
|
+
logger.info(` Launching ${capitalizedName} OAuth authentication...`);
|
|
320
|
+
try {
|
|
321
|
+
execSync(`claude mcp add --transport http ${name} ${url}`, {
|
|
322
|
+
stdio: 'inherit',
|
|
323
|
+
timeout: 120000
|
|
324
|
+
});
|
|
325
|
+
logger.success(` ${capitalizedName} MCP configured`);
|
|
326
|
+
manualInstalled[name] = entry;
|
|
327
|
+
} catch {
|
|
328
|
+
logger.warn(` ${capitalizedName} setup cancelled. Run later: claude mcp add --transport http ${name} ${url}`);
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Collect credentials
|
|
334
|
+
const credentialValues = {};
|
|
335
|
+
for (const cred of entry.install.credentials) {
|
|
336
|
+
const { value } = await inquirer.prompt([{
|
|
337
|
+
type: cred.secret ? 'password' : 'input',
|
|
338
|
+
name: 'value',
|
|
339
|
+
message: `${cred.name}:`,
|
|
340
|
+
mask: cred.secret ? '*' : undefined
|
|
341
|
+
}]);
|
|
342
|
+
credentialValues[cred.envVar] = value;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
spinner.start(`Configuring ${name}...`);
|
|
346
|
+
await installMcpWithCredentials(targetPath, name, entry, credentialValues);
|
|
347
|
+
spinner.succeed(`${capitalizedName} MCP configured`);
|
|
348
|
+
manualInstalled[name] = entry;
|
|
349
|
+
} else {
|
|
350
|
+
// Show setup instructions
|
|
351
|
+
const instructions = generateSetupInstructions(name, entry);
|
|
352
|
+
logger.blank();
|
|
353
|
+
logger.dim(' Add to .mcp.json → mcpServers:');
|
|
354
|
+
logger.dim(' ' + '─'.repeat(55));
|
|
355
|
+
for (const line of instructions.configSnippet.split('\n')) {
|
|
356
|
+
logger.dim(` ${line}`);
|
|
357
|
+
}
|
|
358
|
+
logger.dim(' ' + '─'.repeat(55));
|
|
359
|
+
|
|
360
|
+
for (const cred of instructions.credentialUrls) {
|
|
361
|
+
logger.dim(` Get ${cred.name}: ${cred.helpUrl}`);
|
|
362
|
+
}
|
|
363
|
+
logger.dim(` Or run later: ${instructions.cliCommand}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Summary table
|
|
369
|
+
const justInstalled = { ...autoInstalled, ...manualInstalled };
|
|
370
|
+
const statusRows = formatMcpStatusTable(orchestration, justInstalled);
|
|
371
|
+
|
|
372
|
+
if (statusRows.length > 0) {
|
|
373
|
+
logger.blank();
|
|
374
|
+
logger.info('MCP Servers');
|
|
375
|
+
logger.dim('─'.repeat(60));
|
|
376
|
+
|
|
377
|
+
const STATUS_ICONS = {
|
|
378
|
+
installed: '✓',
|
|
379
|
+
available: '○',
|
|
380
|
+
already_configured: '✓',
|
|
381
|
+
needs_setup: '⚠',
|
|
382
|
+
prereq_missing: '✗',
|
|
383
|
+
not_relevant: '─'
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
for (const row of statusRows) {
|
|
387
|
+
const icon = STATUS_ICONS[row.status] || '?';
|
|
388
|
+
const statusLabel = row.status.replace(/_/g, ' ');
|
|
389
|
+
logger.dim(` ${icon} ${row.name.padEnd(15)} ${statusLabel.padEnd(20)} ${row.detail}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
mcpSetupResult = { autoInstalled, manualInstalled, orchestration };
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
logger.dim(` MCP setup skipped: ${error.message}`);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
logger.dim('\nSkipped MCP setup (--skip-mcp flag)');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Auto-generate architecture context (non-blocking on failure)
|
|
403
|
+
spinner.text = 'Generating architecture context...';
|
|
404
|
+
try {
|
|
405
|
+
const { scanArchitecture, renderArchitectureContext } = await import('../../lib/generators/architecture-scanner.js');
|
|
406
|
+
const scanResult = await scanArchitecture(targetPath);
|
|
407
|
+
await renderArchitectureContext(targetPath, scanResult);
|
|
408
|
+
spinner.succeed('Architecture context generated');
|
|
409
|
+
} catch {
|
|
410
|
+
// Non-fatal: placeholder README.md remains
|
|
411
|
+
spinner.warn('Architecture context generation skipped');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
spinner.succeed('MORPH-SPEC installed successfully!');
|
|
415
|
+
|
|
416
|
+
// Show next steps
|
|
417
|
+
logger.blank();
|
|
418
|
+
logger.success(`MORPH-SPEC v${cliVersion} installed successfully!`);
|
|
419
|
+
logger.blank();
|
|
420
|
+
logger.header('Next Steps');
|
|
421
|
+
|
|
422
|
+
logger.step(1, 'Review .morph/config/config.json');
|
|
423
|
+
logger.step(2, 'Open project in VS Code with Claude Code');
|
|
424
|
+
logger.step(3, 'Start your first feature:');
|
|
425
|
+
logger.blank();
|
|
426
|
+
logger.box([
|
|
427
|
+
'Ask Claude Code to implement a feature'
|
|
428
|
+
]);
|
|
429
|
+
logger.blank();
|
|
430
|
+
|
|
431
|
+
logger.info('Files installed:');
|
|
432
|
+
logger.dim(` ✓ CLAUDE.md`);
|
|
433
|
+
logger.dim(` ✓ .morph/config/ (config.json)`);
|
|
434
|
+
logger.dim(` ✓ .morph/framework/ (agents.json, standards/, templates/, hooks/)`);
|
|
435
|
+
logger.dim(` ✓ .morph/context/ (project context)`);
|
|
436
|
+
logger.dim(` ✓ .morph/features/ (feature outputs)`);
|
|
437
|
+
if (commandsCopied) {
|
|
438
|
+
logger.dim(` ✓ .claude/commands/ (slash commands)`);
|
|
439
|
+
}
|
|
440
|
+
if (rulesCopied) {
|
|
441
|
+
logger.dim(` ✓ .claude/rules/ (5 path-scoped rules)`);
|
|
442
|
+
}
|
|
443
|
+
logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
|
|
444
|
+
if (integrationsCreated) {
|
|
445
|
+
logger.dim(` ✓ .morph/config/integrations.json (detected plugins & MCPs)`);
|
|
446
|
+
}
|
|
447
|
+
if (mcpSetupResult) {
|
|
448
|
+
const autoCount = Object.keys(mcpSetupResult.autoInstalled).length;
|
|
449
|
+
const manualCount = Object.keys(mcpSetupResult.manualInstalled).length;
|
|
450
|
+
if (autoCount + manualCount > 0) {
|
|
451
|
+
logger.dim(` ✓ MCP servers (${autoCount + manualCount} configured)`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
logger.dim(' ✓ .claude/skills/ (installed as skill directories)');
|
|
456
|
+
|
|
457
|
+
// Show agent team breakdown from setup-infra result
|
|
458
|
+
const agents = infraResult?.agents;
|
|
459
|
+
if (agents) {
|
|
460
|
+
const total = (agents.tier1 || 0) + (agents.tier2 || 0) + (agents.specialists || 0) + (agents.personas || 0);
|
|
461
|
+
logger.dim(` ✓ .claude/agents/ (${total} domain personas installed)`);
|
|
462
|
+
} else {
|
|
463
|
+
logger.dim(' ✓ .claude/agents/ (native subagents installed)');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
logger.dim(' ✓ ~/.claude/statusline.sh (global statusline installed)');
|
|
467
|
+
|
|
468
|
+
// Check CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS in ~/.claude/settings.local.json
|
|
469
|
+
logger.blank();
|
|
470
|
+
const globalSettingsPath = join(homedir(), '.claude', 'settings.local.json');
|
|
471
|
+
let agentTeamsEnabled = false;
|
|
472
|
+
try {
|
|
473
|
+
const globalSettings = await readJson(globalSettingsPath);
|
|
474
|
+
agentTeamsEnabled = globalSettings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === '1';
|
|
475
|
+
} catch {
|
|
476
|
+
// File missing or unreadable — treat as not enabled
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (agentTeamsEnabled) {
|
|
480
|
+
logger.success('Agent Teams: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS enabled ✓');
|
|
481
|
+
} else {
|
|
482
|
+
logger.warn('Agent Teams not enabled. To activate multi-agent workflows, add to ~/.claude/settings.local.json:');
|
|
483
|
+
logger.dim(' {"env": {"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"}}');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
logger.blank();
|
|
487
|
+
|
|
488
|
+
// Populate Claude Code persistent memory so future sessions enter contextualized
|
|
489
|
+
try {
|
|
490
|
+
const { installProjectMemory } = await import('../../lib/installers/memory-installer.js');
|
|
491
|
+
const memResult = await installProjectMemory(targetPath, {
|
|
492
|
+
projectName: config.project?.name ?? targetPath.split(/[\\/]/).pop(),
|
|
493
|
+
frameworkVersion: config.frameworkVersion ?? cliVersion,
|
|
494
|
+
stack: config.project?.stack,
|
|
495
|
+
});
|
|
496
|
+
logger.dim(` ✓ Claude Code memory populated (${memResult.projectMemoryPath})`);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
logger.dim(` ⚠ Memory install skipped: ${err.message}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Suggest tutorial for first-time users
|
|
502
|
+
if (integrationsCreated) {
|
|
503
|
+
logger.blank();
|
|
504
|
+
logger.info('First time using morph-spec? Run `/morph-proposal <feature>` to start a new feature.');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
logger.blank();
|
|
508
|
+
logger.warn('IMPORTANT: Restart your Claude Code session for morph-* skills to be available.');
|
|
509
|
+
logger.dim('Skills installed in .claude/skills/ are only registered at session start.');
|
|
510
|
+
logger.blank();
|
|
511
|
+
|
|
512
|
+
} catch (error) {
|
|
513
|
+
spinner.fail('Installation failed');
|
|
514
|
+
logger.error(error.message);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
}
|