@polymorphism-tech/morph-spec 4.2.0 → 4.3.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 (140) hide show
  1. package/CLAUDE.md +108 -946
  2. package/bin/morph-spec.js +284 -9
  3. package/bin/task-manager.cjs +102 -14
  4. package/bin/validate.js +4 -4
  5. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  6. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  7. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  8. package/docs/next-generation/META-PROMPTS.md +235 -0
  9. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  10. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  11. package/package.json +5 -5
  12. package/src/commands/agents/agents-fuse.js +97 -0
  13. package/src/commands/agents/micro-agent.js +112 -0
  14. package/src/commands/agents/spawn-team.js +69 -4
  15. package/src/commands/agents/squad-template.js +146 -0
  16. package/src/commands/analytics/analytics.js +176 -0
  17. package/src/commands/context/context-prime.js +63 -0
  18. package/src/commands/context/core-four.js +54 -0
  19. package/src/commands/mcp/mcp.js +102 -0
  20. package/src/commands/project/detect-agents.js +32 -2
  21. package/src/commands/project/detect.js +11 -1
  22. package/src/commands/project/doctor.js +573 -356
  23. package/src/commands/project/init.js +9 -2
  24. package/src/commands/project/update.js +13 -3
  25. package/src/commands/state/advance-phase.js +448 -416
  26. package/src/commands/state/state.js +14 -12
  27. package/src/commands/tasks/task.js +1 -1
  28. package/src/commands/templates/template-render.js +80 -1
  29. package/src/commands/threads/thread-template.js +103 -0
  30. package/src/commands/threads/threads.js +261 -0
  31. package/src/commands/trust/trust.js +205 -0
  32. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  33. package/src/core/state/state-manager.js +37 -17
  34. package/src/core/workflows/workflow-detector.js +114 -3
  35. package/src/lib/agents/micro-agent-factory.js +161 -0
  36. package/src/lib/analytics/analytics-engine.js +345 -0
  37. package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
  38. package/src/lib/context/context-bundler.js +240 -0
  39. package/src/lib/context/context-optimizer.js +212 -0
  40. package/src/lib/context/context-tracker.js +273 -0
  41. package/src/lib/context/core-four-tracker.js +201 -0
  42. package/src/lib/context/mcp-optimizer.js +200 -0
  43. package/src/lib/detectors/index.js +1 -1
  44. package/src/lib/detectors/standards-generator.js +77 -17
  45. package/src/lib/detectors/structure-detector.js +67 -39
  46. package/src/lib/execution/fusion-executor.js +304 -0
  47. package/src/lib/execution/parallel-executor.js +270 -0
  48. package/src/lib/generators/context-generator.js +3 -3
  49. package/src/lib/generators/recap-generator.js +32 -12
  50. package/src/lib/hooks/hook-executor.js +169 -0
  51. package/src/lib/hooks/stop-hook-executor.js +286 -0
  52. package/src/lib/hops/hop-composer.js +221 -0
  53. package/src/lib/threads/thread-coordinator.js +238 -0
  54. package/src/lib/threads/thread-manager.js +317 -0
  55. package/src/lib/tracking/artifact-trail.js +202 -0
  56. package/src/lib/trust/trust-manager.js +269 -0
  57. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  58. package/src/lib/validators/validation-runner.js +14 -30
  59. package/src/utils/hooks-installer.js +69 -0
  60. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  61. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  62. package/docs/llm-interaction-config.md +0 -735
  63. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  64. package/src/commands/utils/migrate-state.js +0 -158
  65. package/src/commands/utils/upgrade.js +0 -346
  66. package/src/lib/validators/architecture-validator.js +0 -60
  67. package/src/lib/validators/content-validator.js +0 -164
  68. package/src/lib/validators/package-validator.js +0 -61
  69. package/src/lib/validators/ui-contrast-validator.js +0 -44
  70. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  71. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  72. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  73. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  74. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  75. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  76. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  77. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  78. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  79. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  80. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  81. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  82. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  83. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  84. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  85. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  86. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  87. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  88. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  107. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  108. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  109. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  110. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  111. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  112. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  113. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  114. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  115. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  116. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  117. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  118. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  119. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  120. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  121. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  122. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  123. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  124. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  125. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  126. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  127. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  128. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  129. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  130. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  131. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  132. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  133. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  134. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  135. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  136. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  137. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  138. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  139. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  140. /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
@@ -1,356 +1,573 @@
1
- import { join } from 'path';
2
- import { execSync } from 'child_process';
3
- import { platform } from 'os';
4
- import fs from 'fs-extra';
5
- import chalk from 'chalk';
6
- import { logger } from '../../utils/logger.js';
7
- import { pathExists, readJson } from '../../utils/file-copier.js';
8
- import {
9
- checkCLIOutdated,
10
- checkProjectOutdated,
11
- getInstalledCLIVersion
12
- } from '../../utils/version-checker.js';
13
-
14
- const isWindows = platform() === 'win32';
15
-
16
- /**
17
- * Check if morph-spec command is in PATH
18
- */
19
- function isMorphSpecInPath() {
20
- try {
21
- const command = isWindows ? 'where morph-spec' : 'which morph-spec';
22
- execSync(command, { stdio: 'ignore' });
23
- return true;
24
- } catch {
25
- return false;
26
- }
27
- }
28
-
29
- /**
30
- * Detect if nvm-windows is installed
31
- */
32
- function isUsingNvmWindows() {
33
- if (!isWindows) return false;
34
-
35
- try {
36
- const path = process.env.PATH || '';
37
- return path.includes('nvm4w') || path.includes('\\nvm\\');
38
- } catch {
39
- return false;
40
- }
41
- }
42
-
43
- /**
44
- * Get npm global prefix
45
- */
46
- function getNpmGlobalPrefix() {
47
- try {
48
- const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
49
- return prefix;
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- export async function doctorCommand() {
56
- const targetPath = process.cwd();
57
-
58
- logger.header('MORPH-SPEC Health Check');
59
-
60
- const checks = [];
61
- let hasErrors = false;
62
- let hasWarnings = false;
63
-
64
- // Check versions first
65
- const cliCheck = await checkCLIOutdated();
66
- const projectCheck = await checkProjectOutdated(targetPath);
67
-
68
- // CLI Version
69
- if (cliCheck.latest) {
70
- if (cliCheck.isOutdated) {
71
- checks.push({
72
- name: `CLI version (${cliCheck.current})`,
73
- status: 'warn',
74
- msg: `outdated, ${cliCheck.latest} available`
75
- });
76
- hasWarnings = true;
77
- } else {
78
- checks.push({
79
- name: `CLI version (${cliCheck.current})`,
80
- status: 'ok',
81
- msg: 'latest'
82
- });
83
- }
84
- } else {
85
- checks.push({
86
- name: `CLI version (${cliCheck.current})`,
87
- status: 'ok'
88
- });
89
- }
90
-
91
- // Project MORPH version
92
- if (projectCheck.current) {
93
- if (projectCheck.isOutdated) {
94
- checks.push({
95
- name: `Project MORPH version (${projectCheck.current})`,
96
- status: 'warn',
97
- msg: `outdated, ${projectCheck.cliVersion} available`
98
- });
99
- hasWarnings = true;
100
- } else {
101
- checks.push({
102
- name: `Project MORPH version (${projectCheck.current})`,
103
- status: 'ok'
104
- });
105
- }
106
- } else {
107
- checks.push({
108
- name: 'Project MORPH version',
109
- status: 'warn',
110
- msg: 'not found (legacy installation)'
111
- });
112
- hasWarnings = true;
113
- }
114
-
115
- // Check if morph-spec is in PATH
116
- const inPath = isMorphSpecInPath();
117
- if (inPath) {
118
- checks.push({
119
- name: 'morph-spec in PATH',
120
- status: 'ok'
121
- });
122
- } else {
123
- checks.push({
124
- name: 'morph-spec in PATH',
125
- status: 'warn',
126
- msg: 'command not found'
127
- });
128
- hasWarnings = true;
129
- }
130
-
131
- // Check CLAUDE.md
132
- const claudeMd = join(targetPath, 'CLAUDE.md');
133
- if (await pathExists(claudeMd)) {
134
- checks.push({ name: 'CLAUDE.md', status: 'ok' });
135
- } else {
136
- checks.push({ name: 'CLAUDE.md', status: 'missing' });
137
- hasErrors = true;
138
- }
139
-
140
- // Check .morph folder
141
- const morphPath = join(targetPath, '.morph');
142
- if (await pathExists(morphPath)) {
143
- checks.push({ name: '.morph/', status: 'ok' });
144
- } else {
145
- checks.push({ name: '.morph/', status: 'missing' });
146
- hasErrors = true;
147
- }
148
-
149
- // Check config.json
150
- const configPath = join(morphPath, 'config', 'config.json');
151
- if (await pathExists(configPath)) {
152
- try {
153
- const config = await readJson(configPath);
154
- if (config.project?.name) {
155
- checks.push({ name: 'config.json', status: 'ok' });
156
- } else {
157
- checks.push({ name: 'config.json', status: 'warn', msg: 'project.name not set' });
158
- }
159
- } catch {
160
- checks.push({ name: 'config.json', status: 'error', msg: 'invalid JSON' });
161
- hasErrors = true;
162
- }
163
- } else {
164
- checks.push({ name: 'config.json', status: 'missing' });
165
- hasErrors = true;
166
- }
167
-
168
- // Check agents.json
169
- const agentsPath = join(morphPath, 'config', 'agents.json');
170
- if (await pathExists(agentsPath)) {
171
- checks.push({ name: 'agents.json', status: 'ok' });
172
- } else {
173
- checks.push({ name: 'agents.json', status: 'missing' });
174
- hasErrors = true;
175
- }
176
-
177
- // Check standards
178
- const standardsPath = join(morphPath, 'standards');
179
- if (await pathExists(standardsPath)) {
180
- const codingStd = join(standardsPath, 'coding.md');
181
- const archStd = join(standardsPath, 'architecture.md');
182
- const azureStd = join(standardsPath, 'azure.md');
183
-
184
- const hasAll = await Promise.all([
185
- pathExists(codingStd),
186
- pathExists(archStd),
187
- pathExists(azureStd)
188
- ]).then(results => results.every(Boolean));
189
-
190
- if (hasAll) {
191
- checks.push({ name: 'standards/', status: 'ok' });
192
- } else {
193
- checks.push({ name: 'standards/', status: 'warn', msg: 'some files missing' });
194
- }
195
- } else {
196
- checks.push({ name: 'standards/', status: 'missing' });
197
- hasErrors = true;
198
- }
199
-
200
- // Check templates
201
- const templatesPath = join(morphPath, 'templates');
202
- if (await pathExists(templatesPath)) {
203
- checks.push({ name: 'templates/', status: 'ok' });
204
- } else {
205
- checks.push({ name: 'templates/', status: 'missing' });
206
- hasErrors = true;
207
- }
208
-
209
- // Check .claude folder
210
- const claudePath = join(targetPath, '.claude');
211
- if (await pathExists(claudePath)) {
212
- const commandsPath = join(claudePath, 'commands');
213
- const skillsPath = join(claudePath, 'skills');
214
-
215
- const hasCommands = await pathExists(commandsPath);
216
- const hasSkills = await pathExists(skillsPath);
217
-
218
- if (hasCommands && hasSkills) {
219
- checks.push({ name: '.claude/', status: 'ok' });
220
-
221
- // Check skills link status
222
- const skillEntries = await fs.readdir(skillsPath, { withFileTypes: true });
223
- const skillDirs = skillEntries.filter(e => e.isDirectory() || e.isSymbolicLink());
224
- let linkedCount = 0;
225
-
226
- for (const dir of skillDirs) {
227
- try {
228
- const stat = await fs.lstat(join(skillsPath, dir.name));
229
- if (stat.isSymbolicLink()) linkedCount++;
230
- } catch { /* ignore */ }
231
- }
232
-
233
- if (skillDirs.length > 0) {
234
- if (linkedCount === skillDirs.length) {
235
- checks.push({ name: '.claude/skills/ links', status: 'ok', msg: `${linkedCount} categories linked` });
236
- } else if (linkedCount > 0) {
237
- checks.push({ name: '.claude/skills/ links', status: 'warn', msg: `${linkedCount}/${skillDirs.length} linked, rest copied` });
238
- hasWarnings = true;
239
- } else {
240
- checks.push({ name: '.claude/skills/ links', status: 'warn', msg: 'all categories copied (no auto-update)' });
241
- hasWarnings = true;
242
- }
243
- }
244
- } else {
245
- checks.push({ name: '.claude/', status: 'warn', msg: 'incomplete structure' });
246
- }
247
- } else {
248
- checks.push({ name: '.claude/', status: 'missing' });
249
- hasErrors = true;
250
- }
251
-
252
- // Check state.json
253
- const statePath = join(targetPath, '.morph', 'state.json');
254
- if (await pathExists(statePath)) {
255
- try {
256
- const stateContent = await readJson(statePath);
257
- if (stateContent.version && stateContent.features) {
258
- const featureCount = Object.keys(stateContent.features).length;
259
- checks.push({ name: 'state.json', status: 'ok', msg: `${featureCount} feature(s)` });
260
- } else {
261
- checks.push({ name: 'state.json', status: 'warn', msg: 'invalid schema' });
262
- hasWarnings = true;
263
- }
264
- } catch {
265
- checks.push({ name: 'state.json', status: 'error', msg: 'invalid JSON' });
266
- hasErrors = true;
267
- }
268
- } else {
269
- checks.push({ name: 'state.json', status: 'warn', msg: 'not initialized (run: morph-spec state init)' });
270
- hasWarnings = true;
271
- }
272
-
273
- // Check key templates exist
274
- const keyTemplates = ['proposal.md', 'spec.md', 'tasks.md', 'decisions.md'];
275
- const missingTemplates = [];
276
- for (const template of keyTemplates) {
277
- const templatePath = join(templatesPath, template);
278
- if (!(await pathExists(templatePath))) {
279
- missingTemplates.push(template);
280
- }
281
- }
282
-
283
- if (missingTemplates.length === 0) {
284
- checks.push({ name: 'key templates', status: 'ok' });
285
- } else if (missingTemplates.length < keyTemplates.length) {
286
- checks.push({ name: 'key templates', status: 'warn', msg: `missing: ${missingTemplates.join(', ')}` });
287
- hasWarnings = true;
288
- } else {
289
- checks.push({ name: 'key templates', status: 'missing' });
290
- hasErrors = true;
291
- }
292
-
293
- // Check Node.js version
294
- const nodeVersion = process.version;
295
- const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
296
- if (majorVersion >= 18) {
297
- checks.push({ name: `Node.js ${nodeVersion}`, status: 'ok' });
298
- } else {
299
- checks.push({ name: `Node.js ${nodeVersion}`, status: 'warn', msg: 'v18+ recommended' });
300
- hasWarnings = true;
301
- }
302
-
303
- // Display results
304
- for (const check of checks) {
305
- if (check.status === 'ok') {
306
- console.log(chalk.green(` ✓ ${check.name}`));
307
- } else if (check.status === 'warn') {
308
- console.log(chalk.yellow(` ${check.name}`) + chalk.gray(` (${check.msg})`));
309
- } else if (check.status === 'missing') {
310
- console.log(chalk.red(` ✗ ${check.name}`) + chalk.gray(' (missing)'));
311
- } else if (check.status === 'error') {
312
- console.log(chalk.red(` ✗ ${check.name}`) + chalk.gray(` (${check.msg})`));
313
- }
314
- }
315
-
316
- logger.blank();
317
-
318
- if (hasErrors) {
319
- logger.error('Some checks failed. Run "morph-spec init --force" to fix.');
320
- process.exit(1);
321
- } else if (hasWarnings) {
322
- logger.warn('Some checks need attention.');
323
- logger.blank();
324
-
325
- if (!inPath) {
326
- const usingNvm = isUsingNvmWindows();
327
- const prefix = getNpmGlobalPrefix();
328
-
329
- if (usingNvm && prefix) {
330
- logger.info('PATH Issue Detected (nvm-windows):');
331
- logger.dim(` Add to PATH: ${prefix}`);
332
- logger.blank();
333
- logger.dim(' PowerShell (as Administrator):');
334
- console.log(chalk.gray(` [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";${prefix}", "User")`));
335
- logger.blank();
336
- logger.dim(' Or use npx instead:');
337
- logger.dim(' npx @polymorphism-tech/morph-spec init');
338
- logger.blank();
339
- } else if (!inPath) {
340
- logger.info('morph-spec not in PATH:');
341
- logger.dim(' Use npx instead:');
342
- logger.dim(' npx @polymorphism-tech/morph-spec init');
343
- logger.blank();
344
- }
345
- }
346
-
347
- if (cliCheck.isOutdated || projectCheck.isOutdated) {
348
- logger.info('To update:');
349
- logger.dim(' 1. Update the CLI: npm install -g @polymorphism-tech/morph-spec@latest');
350
- logger.dim(' 2. Update the project: morph-spec update');
351
- logger.blank();
352
- }
353
- } else {
354
- logger.success('All checks passed!');
355
- }
356
- }
1
+ import { join } from 'path';
2
+ import { execSync } from 'child_process';
3
+ import { platform } from 'os';
4
+ import fs from 'fs-extra';
5
+ import chalk from 'chalk';
6
+ import { logger } from '../../utils/logger.js';
7
+ import { pathExists, readJson } from '../../utils/file-copier.js';
8
+ import {
9
+ checkCLIOutdated,
10
+ checkProjectOutdated,
11
+ getInstalledCLIVersion
12
+ } from '../../utils/version-checker.js';
13
+
14
+ const isWindows = platform() === 'win32';
15
+
16
+ /**
17
+ * Check if morph-spec command is in PATH
18
+ */
19
+ function isMorphSpecInPath() {
20
+ try {
21
+ const command = isWindows ? 'where morph-spec' : 'which morph-spec';
22
+ execSync(command, { stdio: 'ignore' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Detect if nvm-windows is installed
31
+ */
32
+ function isUsingNvmWindows() {
33
+ if (!isWindows) return false;
34
+
35
+ try {
36
+ const path = process.env.PATH || '';
37
+ return path.includes('nvm4w') || path.includes('\\nvm\\');
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get npm global prefix
45
+ */
46
+ function getNpmGlobalPrefix() {
47
+ try {
48
+ const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
49
+ return prefix;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ // lib files
56
+ const REQUIRED_LIB_FILES = [
57
+ 'src/lib/analytics/analytics-engine.js',
58
+ 'src/lib/tracking/artifact-trail.js',
59
+ 'src/lib/context/context-bundler.js',
60
+ 'src/lib/context/context-optimizer.js',
61
+ 'src/lib/context/context-tracker.js',
62
+ 'src/lib/context/core-four-tracker.js',
63
+ 'src/lib/execution/fusion-executor.js',
64
+ 'src/lib/hops/hop-composer.js',
65
+ 'src/lib/context/mcp-optimizer.js',
66
+ 'src/lib/agents/micro-agent-factory.js',
67
+ 'src/lib/execution/parallel-executor.js',
68
+ 'src/lib/hooks/stop-hook-executor.js',
69
+ 'src/lib/threads/thread-coordinator.js',
70
+ 'src/lib/threads/thread-manager.js',
71
+ 'src/lib/trust/trust-manager.js'
72
+ ];
73
+
74
+ // command files
75
+ const REQUIRED_COMMAND_FILES = [
76
+ 'src/commands/agents/agents-fuse.js',
77
+ 'src/commands/analytics/analytics.js',
78
+ 'src/commands/context/context-prime.js',
79
+ 'src/commands/context/core-four.js',
80
+ 'src/commands/mcp/mcp.js',
81
+ 'src/commands/agents/micro-agent.js',
82
+ 'src/commands/agents/squad-template.js',
83
+ 'src/commands/threads/thread-template.js',
84
+ 'src/commands/threads/threads.js',
85
+ 'src/commands/trust/trust.js'
86
+ ];
87
+
88
+ // HOP templates (meta-prompts)
89
+ const HOP_TEMPLATES = [
90
+ 'framework/templates/meta-prompts/squad-leaders/backend-squad.md',
91
+ 'framework/templates/meta-prompts/squad-leaders/frontend-squad.md',
92
+ 'framework/templates/meta-prompts/parallel-workers/parallel-worker.md',
93
+ 'framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md',
94
+ 'framework/templates/meta-prompts/hops/hop-wrapper.md',
95
+ 'framework/templates/meta-prompts/hops/hop-retry.md',
96
+ 'framework/templates/meta-prompts/hops/hop-validation.md',
97
+ 'framework/templates/meta-prompts/validators/checkpoint-validator.md',
98
+ 'framework/templates/meta-prompts/validators/pre-commit-validator.md',
99
+ 'framework/templates/meta-prompts/fusion/fusion-agent.md',
100
+ 'framework/templates/meta-prompts/fusion/fusion-aggregator.md',
101
+ 'framework/templates/REGISTRY.json'
102
+ ];
103
+
104
+ // framework standards
105
+ const FRAMEWORK_STANDARDS = [
106
+ 'framework/standards/observability/monitoring.md',
107
+ 'framework/standards/observability/logging.md',
108
+ 'framework/standards/observability/tracing.md',
109
+ 'framework/standards/observability/metrics.md',
110
+ 'framework/standards/integration/event-driven/service-bus.md',
111
+ 'framework/standards/integration/event-driven/cqrs.md',
112
+ 'framework/standards/integration/event-driven/event-sourcing.md',
113
+ 'framework/standards/data/nosql/cosmos-db.md',
114
+ 'framework/standards/data/nosql/cache/redis.md',
115
+ 'framework/standards/data/nosql/blob-storage.md',
116
+ 'framework/standards/integration/api/rest-design.md',
117
+ 'framework/standards/integration/api/graphql.md',
118
+ 'framework/standards/integration/api/grpc.md',
119
+ 'framework/standards/architecture/ddd/aggregates.md',
120
+ 'framework/standards/architecture/ddd/entities.md',
121
+ 'framework/standards/architecture/ddd/value-objects.md',
122
+ 'framework/standards/data/vector-search/azure-ai-search.md',
123
+ 'framework/standards/data/vector-search/rag-chunking.md',
124
+ 'framework/standards/context/priming.md',
125
+ 'framework/standards/context/bundles.md',
126
+ 'framework/standards/context/analytics.md',
127
+ 'framework/standards/workflows/thread-management.md',
128
+ 'framework/standards/workflows/parallel-execution.md'
129
+ ];
130
+
131
+ // required agents expected in agents.json
132
+ const REQUIRED_AGENTS = [
133
+ 'vector-search-expert',
134
+ 'thread-orchestrator',
135
+ 'context-optimizer',
136
+ 'observability-expert'
137
+ ];
138
+
139
+ /**
140
+ * Run full health checks
141
+ */
142
+ async function doctorFullCommand(frameworkRoot) {
143
+ console.log(chalk.bold('\n🔬 MORPH-SPEC Full Health Check\n'));
144
+ console.log('─'.repeat(60));
145
+
146
+ const checks = [];
147
+ let hasErrors = false;
148
+ let hasWarnings = false;
149
+
150
+ const check = (name, passed, warnOnly = false, msg = '') => {
151
+ if (passed) {
152
+ checks.push({ name, status: 'ok' });
153
+ } else {
154
+ checks.push({ name, status: warnOnly ? 'warn' : 'missing', msg });
155
+ if (warnOnly) hasWarnings = true;
156
+ else hasErrors = true;
157
+ }
158
+ };
159
+
160
+ // ── 1. Core Libraries ───────────────────────────────────────────────────
161
+ console.log(chalk.cyan(`\n src/lib/ Core Libraries (${REQUIRED_LIB_FILES.length} files)`));
162
+ const missingLibs = [];
163
+ for (const f of REQUIRED_LIB_FILES) {
164
+ if (!(await pathExists(join(frameworkRoot, f)))) missingLibs.push(f.split('/').pop());
165
+ }
166
+ check(`Core Libraries (${REQUIRED_LIB_FILES.length - missingLibs.length}/${REQUIRED_LIB_FILES.length})`,
167
+ missingLibs.length === 0, false,
168
+ missingLibs.length > 0 ? `missing: ${missingLibs.join(', ')}` : '');
169
+
170
+ // ── 2. Command Files ─────────────────────────────────────────────────────
171
+ console.log(chalk.cyan(`\n src/commands/ Command Files (${REQUIRED_COMMAND_FILES.length} files)`));
172
+ const missingCmds = [];
173
+ for (const f of REQUIRED_COMMAND_FILES) {
174
+ if (!(await pathExists(join(frameworkRoot, f)))) missingCmds.push(f.split('/').pop());
175
+ }
176
+ check(`Command Files (${REQUIRED_COMMAND_FILES.length - missingCmds.length}/${REQUIRED_COMMAND_FILES.length})`,
177
+ missingCmds.length === 0, false,
178
+ missingCmds.length > 0 ? `missing: ${missingCmds.join(', ')}` : '');
179
+
180
+ // ── 3. HOP Templates ────────────────────────────────────────────────────
181
+ console.log(chalk.cyan(`\n framework/templates/meta-prompts/ HOP Templates (${HOP_TEMPLATES.length})`));
182
+ const missingHOPs = [];
183
+ for (const f of HOP_TEMPLATES) {
184
+ if (!(await pathExists(join(frameworkRoot, f)))) missingHOPs.push(f.split('/').pop());
185
+ }
186
+ check(`HOP Templates (${HOP_TEMPLATES.length - missingHOPs.length}/${HOP_TEMPLATES.length})`,
187
+ missingHOPs.length === 0, false,
188
+ missingHOPs.length > 0 ? `missing: ${missingHOPs.join(', ')}` : '');
189
+
190
+ // ── 4. Framework Standards ───────────────────────────────────────────────
191
+ console.log(chalk.cyan(`\n framework/standards/ Framework Standards (${FRAMEWORK_STANDARDS.length} files)`));
192
+ const missingStds = [];
193
+ for (const f of FRAMEWORK_STANDARDS) {
194
+ if (!(await pathExists(join(frameworkRoot, f)))) missingStds.push(f.replace('framework/standards/', ''));
195
+ }
196
+ check(`Framework Standards (${FRAMEWORK_STANDARDS.length - missingStds.length}/${FRAMEWORK_STANDARDS.length})`,
197
+ missingStds.length === 0, false,
198
+ missingStds.length > 0 ? `missing: ${missingStds.join(', ')}` : '');
199
+
200
+ // ── 5. Required Agents in agents.json ───────────────────────────────────
201
+ console.log(chalk.cyan(`\n .morph/config/agents.json Required Agents (${REQUIRED_AGENTS.length})`));
202
+ const agentsPath = join(frameworkRoot, '.morph/config/agents.json');
203
+ if (await pathExists(agentsPath)) {
204
+ try {
205
+ const agentsConfig = JSON.parse(await fs.readFile(agentsPath, 'utf8'));
206
+ const agents = agentsConfig.agents || {};
207
+ const missingAgents = REQUIRED_AGENTS.filter(id => !agents[id]);
208
+ check(`Required Agents (${REQUIRED_AGENTS.length - missingAgents.length}/${REQUIRED_AGENTS.length})`,
209
+ missingAgents.length === 0, false,
210
+ missingAgents.length > 0 ? `missing: ${missingAgents.join(', ')}` : '');
211
+ } catch {
212
+ check('agents.json parse', false, false, 'invalid JSON');
213
+ }
214
+ } else {
215
+ check('agents.json', false, false, 'file not found');
216
+ }
217
+
218
+ // ── 7. Zero-Touch Workflow Config ───────────────────────────────────────
219
+ console.log(chalk.cyan('\n framework/workflows/ Zero-Touch Config'));
220
+ const ztPath = join(frameworkRoot, 'framework/workflows/configs/zero-touch.json');
221
+ check('zero-touch.json workflow config', await pathExists(ztPath));
222
+
223
+ // ── 8. state.json schema version ────────────────────────────────────────
224
+ console.log(chalk.cyan('\n .morph/state.json Schema Version'));
225
+ const statePath = join(frameworkRoot, '.morph/state.json');
226
+ if (await pathExists(statePath)) {
227
+ try {
228
+ const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
229
+ const isCurrent = state.version === '3.0.0';
230
+ check(`state.json schema (${state.version})`, isCurrent, true,
231
+ isCurrent ? '' : 'run: morph-spec state init --force');
232
+ } catch {
233
+ check('state.json parse', false, false, 'invalid JSON');
234
+ }
235
+ } else {
236
+ check('state.json', false, true, 'not found (run: morph-spec state init)');
237
+ }
238
+
239
+ // ── Display ──────────────────────────────────────────────────────────────
240
+ console.log('\n' + ''.repeat(60));
241
+ console.log(chalk.bold('\nResults:\n'));
242
+ for (const c of checks) {
243
+ if (c.status === 'ok') {
244
+ console.log(chalk.green(` ✓ ${c.name}`));
245
+ } else if (c.status === 'warn') {
246
+ console.log(chalk.yellow(` ⚠ ${c.name}`) + chalk.gray(c.msg ? ` (${c.msg})` : ''));
247
+ } else {
248
+ console.log(chalk.red(` ✗ ${c.name}`) + chalk.gray(c.msg ? ` — ${c.msg}` : ''));
249
+ }
250
+ }
251
+
252
+ console.log('');
253
+ const passed = checks.filter(c => c.status === 'ok').length;
254
+ const total = checks.length;
255
+
256
+ if (hasErrors) {
257
+ console.log(chalk.red(`\n❌ ${passed}/${total} checks passed — see errors above\n`));
258
+ process.exit(1);
259
+ } else if (hasWarnings) {
260
+ console.log(chalk.yellow(`\n⚠️ ${passed}/${total} checks passed (warnings only)\n`));
261
+ } else {
262
+ console.log(chalk.green(`\n✅ ${passed}/${total} — All checks passed!\n`));
263
+ }
264
+ }
265
+
266
+ export async function doctorCommand(options = {}) {
267
+ // full health check mode
268
+ if (options.full) {
269
+ const frameworkRoot = process.cwd();
270
+ return doctorFullCommand(frameworkRoot);
271
+ }
272
+
273
+ const targetPath = process.cwd();
274
+
275
+ logger.header('MORPH-SPEC Health Check');
276
+
277
+ const checks = [];
278
+ let hasErrors = false;
279
+ let hasWarnings = false;
280
+
281
+ // Check versions first
282
+ const cliCheck = await checkCLIOutdated();
283
+ const projectCheck = await checkProjectOutdated(targetPath);
284
+
285
+ // CLI Version
286
+ if (cliCheck.latest) {
287
+ if (cliCheck.isOutdated) {
288
+ checks.push({
289
+ name: `CLI version (${cliCheck.current})`,
290
+ status: 'warn',
291
+ msg: `outdated, ${cliCheck.latest} available`
292
+ });
293
+ hasWarnings = true;
294
+ } else {
295
+ checks.push({
296
+ name: `CLI version (${cliCheck.current})`,
297
+ status: 'ok',
298
+ msg: 'latest'
299
+ });
300
+ }
301
+ } else {
302
+ checks.push({
303
+ name: `CLI version (${cliCheck.current})`,
304
+ status: 'ok'
305
+ });
306
+ }
307
+
308
+ // Project MORPH version
309
+ if (projectCheck.current) {
310
+ if (projectCheck.isOutdated) {
311
+ checks.push({
312
+ name: `Project MORPH version (${projectCheck.current})`,
313
+ status: 'warn',
314
+ msg: `outdated, ${projectCheck.cliVersion} available`
315
+ });
316
+ hasWarnings = true;
317
+ } else {
318
+ checks.push({
319
+ name: `Project MORPH version (${projectCheck.current})`,
320
+ status: 'ok'
321
+ });
322
+ }
323
+ } else {
324
+ checks.push({
325
+ name: 'Project MORPH version',
326
+ status: 'warn',
327
+ msg: 'not found (legacy installation)'
328
+ });
329
+ hasWarnings = true;
330
+ }
331
+
332
+ // Check if morph-spec is in PATH
333
+ const inPath = isMorphSpecInPath();
334
+ if (inPath) {
335
+ checks.push({
336
+ name: 'morph-spec in PATH',
337
+ status: 'ok'
338
+ });
339
+ } else {
340
+ checks.push({
341
+ name: 'morph-spec in PATH',
342
+ status: 'warn',
343
+ msg: 'command not found'
344
+ });
345
+ hasWarnings = true;
346
+ }
347
+
348
+ // Check CLAUDE.md
349
+ const claudeMd = join(targetPath, 'CLAUDE.md');
350
+ if (await pathExists(claudeMd)) {
351
+ checks.push({ name: 'CLAUDE.md', status: 'ok' });
352
+ } else {
353
+ checks.push({ name: 'CLAUDE.md', status: 'missing' });
354
+ hasErrors = true;
355
+ }
356
+
357
+ // Check .morph folder
358
+ const morphPath = join(targetPath, '.morph');
359
+ if (await pathExists(morphPath)) {
360
+ checks.push({ name: '.morph/', status: 'ok' });
361
+ } else {
362
+ checks.push({ name: '.morph/', status: 'missing' });
363
+ hasErrors = true;
364
+ }
365
+
366
+ // Check config.json
367
+ const configPath = join(morphPath, 'config', 'config.json');
368
+ if (await pathExists(configPath)) {
369
+ try {
370
+ const config = await readJson(configPath);
371
+ if (config.project?.name) {
372
+ checks.push({ name: 'config.json', status: 'ok' });
373
+ } else {
374
+ checks.push({ name: 'config.json', status: 'warn', msg: 'project.name not set' });
375
+ }
376
+ } catch {
377
+ checks.push({ name: 'config.json', status: 'error', msg: 'invalid JSON' });
378
+ hasErrors = true;
379
+ }
380
+ } else {
381
+ checks.push({ name: 'config.json', status: 'missing' });
382
+ hasErrors = true;
383
+ }
384
+
385
+ // Check agents.json
386
+ const agentsPath = join(morphPath, 'config', 'agents.json');
387
+ if (await pathExists(agentsPath)) {
388
+ checks.push({ name: 'agents.json', status: 'ok' });
389
+ } else {
390
+ checks.push({ name: 'agents.json', status: 'missing' });
391
+ hasErrors = true;
392
+ }
393
+
394
+ // Check standards
395
+ const standardsPath = join(morphPath, 'standards');
396
+ if (await pathExists(standardsPath)) {
397
+ const codingStd = join(standardsPath, 'coding.md');
398
+ const archStd = join(standardsPath, 'architecture.md');
399
+ const azureStd = join(standardsPath, 'azure.md');
400
+
401
+ const hasAll = await Promise.all([
402
+ pathExists(codingStd),
403
+ pathExists(archStd),
404
+ pathExists(azureStd)
405
+ ]).then(results => results.every(Boolean));
406
+
407
+ if (hasAll) {
408
+ checks.push({ name: 'standards/', status: 'ok' });
409
+ } else {
410
+ checks.push({ name: 'standards/', status: 'warn', msg: 'some files missing' });
411
+ }
412
+ } else {
413
+ checks.push({ name: 'standards/', status: 'missing' });
414
+ hasErrors = true;
415
+ }
416
+
417
+ // Check templates
418
+ const templatesPath = join(morphPath, 'templates');
419
+ if (await pathExists(templatesPath)) {
420
+ checks.push({ name: 'templates/', status: 'ok' });
421
+ } else {
422
+ checks.push({ name: 'templates/', status: 'missing' });
423
+ hasErrors = true;
424
+ }
425
+
426
+ // Check .claude folder
427
+ const claudePath = join(targetPath, '.claude');
428
+ if (await pathExists(claudePath)) {
429
+ const commandsPath = join(claudePath, 'commands');
430
+ const skillsPath = join(claudePath, 'skills');
431
+
432
+ const hasCommands = await pathExists(commandsPath);
433
+ const hasSkills = await pathExists(skillsPath);
434
+
435
+ if (hasCommands && hasSkills) {
436
+ checks.push({ name: '.claude/', status: 'ok' });
437
+
438
+ // Check skills link status
439
+ const skillEntries = await fs.readdir(skillsPath, { withFileTypes: true });
440
+ const skillDirs = skillEntries.filter(e => e.isDirectory() || e.isSymbolicLink());
441
+ let linkedCount = 0;
442
+
443
+ for (const dir of skillDirs) {
444
+ try {
445
+ const stat = await fs.lstat(join(skillsPath, dir.name));
446
+ if (stat.isSymbolicLink()) linkedCount++;
447
+ } catch { /* ignore */ }
448
+ }
449
+
450
+ if (skillDirs.length > 0) {
451
+ if (linkedCount === skillDirs.length) {
452
+ checks.push({ name: '.claude/skills/ links', status: 'ok', msg: `${linkedCount} categories linked` });
453
+ } else if (linkedCount > 0) {
454
+ checks.push({ name: '.claude/skills/ links', status: 'warn', msg: `${linkedCount}/${skillDirs.length} linked, rest copied` });
455
+ hasWarnings = true;
456
+ } else {
457
+ checks.push({ name: '.claude/skills/ links', status: 'warn', msg: 'all categories copied (no auto-update)' });
458
+ hasWarnings = true;
459
+ }
460
+ }
461
+ } else {
462
+ checks.push({ name: '.claude/', status: 'warn', msg: 'incomplete structure' });
463
+ }
464
+ } else {
465
+ checks.push({ name: '.claude/', status: 'missing' });
466
+ hasErrors = true;
467
+ }
468
+
469
+ // Check state.json
470
+ const statePath = join(targetPath, '.morph', 'state.json');
471
+ if (await pathExists(statePath)) {
472
+ try {
473
+ const stateContent = await readJson(statePath);
474
+ if (stateContent.version && stateContent.features) {
475
+ const featureCount = Object.keys(stateContent.features).length;
476
+ checks.push({ name: 'state.json', status: 'ok', msg: `${featureCount} feature(s)` });
477
+ } else {
478
+ checks.push({ name: 'state.json', status: 'warn', msg: 'invalid schema' });
479
+ hasWarnings = true;
480
+ }
481
+ } catch {
482
+ checks.push({ name: 'state.json', status: 'error', msg: 'invalid JSON' });
483
+ hasErrors = true;
484
+ }
485
+ } else {
486
+ checks.push({ name: 'state.json', status: 'warn', msg: 'not initialized (run: morph-spec state init)' });
487
+ hasWarnings = true;
488
+ }
489
+
490
+ // Check key templates exist
491
+ const keyTemplates = ['proposal.md', 'spec.md', 'tasks.md', 'decisions.md'];
492
+ const missingTemplates = [];
493
+ for (const template of keyTemplates) {
494
+ const templatePath = join(templatesPath, template);
495
+ if (!(await pathExists(templatePath))) {
496
+ missingTemplates.push(template);
497
+ }
498
+ }
499
+
500
+ if (missingTemplates.length === 0) {
501
+ checks.push({ name: 'key templates', status: 'ok' });
502
+ } else if (missingTemplates.length < keyTemplates.length) {
503
+ checks.push({ name: 'key templates', status: 'warn', msg: `missing: ${missingTemplates.join(', ')}` });
504
+ hasWarnings = true;
505
+ } else {
506
+ checks.push({ name: 'key templates', status: 'missing' });
507
+ hasErrors = true;
508
+ }
509
+
510
+ // Check Node.js version
511
+ const nodeVersion = process.version;
512
+ const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
513
+ if (majorVersion >= 18) {
514
+ checks.push({ name: `Node.js ${nodeVersion}`, status: 'ok' });
515
+ } else {
516
+ checks.push({ name: `Node.js ${nodeVersion}`, status: 'warn', msg: 'v18+ recommended' });
517
+ hasWarnings = true;
518
+ }
519
+
520
+ // Display results
521
+ for (const check of checks) {
522
+ if (check.status === 'ok') {
523
+ console.log(chalk.green(` ✓ ${check.name}`));
524
+ } else if (check.status === 'warn') {
525
+ console.log(chalk.yellow(` ⚠ ${check.name}`) + chalk.gray(` (${check.msg})`));
526
+ } else if (check.status === 'missing') {
527
+ console.log(chalk.red(` ✗ ${check.name}`) + chalk.gray(' (missing)'));
528
+ } else if (check.status === 'error') {
529
+ console.log(chalk.red(` ✗ ${check.name}`) + chalk.gray(` (${check.msg})`));
530
+ }
531
+ }
532
+
533
+ logger.blank();
534
+
535
+ if (hasErrors) {
536
+ logger.error('Some checks failed. Run "morph-spec init --force" to fix.');
537
+ process.exit(1);
538
+ } else if (hasWarnings) {
539
+ logger.warn('Some checks need attention.');
540
+ logger.blank();
541
+
542
+ if (!inPath) {
543
+ const usingNvm = isUsingNvmWindows();
544
+ const prefix = getNpmGlobalPrefix();
545
+
546
+ if (usingNvm && prefix) {
547
+ logger.info('PATH Issue Detected (nvm-windows):');
548
+ logger.dim(` Add to PATH: ${prefix}`);
549
+ logger.blank();
550
+ logger.dim(' PowerShell (as Administrator):');
551
+ console.log(chalk.gray(` [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";${prefix}", "User")`));
552
+ logger.blank();
553
+ logger.dim(' Or use npx instead:');
554
+ logger.dim(' npx @polymorphism-tech/morph-spec init');
555
+ logger.blank();
556
+ } else if (!inPath) {
557
+ logger.info('morph-spec not in PATH:');
558
+ logger.dim(' Use npx instead:');
559
+ logger.dim(' npx @polymorphism-tech/morph-spec init');
560
+ logger.blank();
561
+ }
562
+ }
563
+
564
+ if (cliCheck.isOutdated || projectCheck.isOutdated) {
565
+ logger.info('To update:');
566
+ logger.dim(' 1. Update the CLI: npm install -g @polymorphism-tech/morph-spec@latest');
567
+ logger.dim(' 2. Update the project: morph-spec update');
568
+ logger.blank();
569
+ }
570
+ } else {
571
+ logger.success('All checks passed!');
572
+ }
573
+ }