@polymorphism-tech/morph-spec 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/bin/morph-spec.js +283 -8
  2. package/bin/validate.js +4 -4
  3. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  4. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  5. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  6. package/docs/next-generation/META-PROMPTS.md +235 -0
  7. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  8. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  9. package/package.json +5 -5
  10. package/src/commands/agents/agents-fuse.js +96 -0
  11. package/src/commands/agents/micro-agent.js +112 -0
  12. package/src/commands/agents/spawn-team.js +69 -4
  13. package/src/commands/agents/squad-template.js +146 -0
  14. package/src/commands/analytics/analytics.js +176 -0
  15. package/src/commands/context/context-prime.js +63 -0
  16. package/src/commands/context/core-four.js +54 -0
  17. package/src/commands/mcp/mcp.js +102 -0
  18. package/src/commands/project/detect-agents.js +1 -1
  19. package/src/commands/project/doctor.js +573 -356
  20. package/src/commands/project/init.js +1 -1
  21. package/src/commands/project/update.js +1 -1
  22. package/src/commands/state/advance-phase.js +433 -416
  23. package/src/commands/templates/template-render.js +80 -1
  24. package/src/commands/threads/thread-template.js +103 -0
  25. package/src/commands/threads/threads.js +261 -0
  26. package/src/commands/trust/trust.js +205 -0
  27. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  28. package/src/core/state/state-manager.js +18 -2
  29. package/src/core/workflows/workflow-detector.js +100 -2
  30. package/src/lib/agents/micro-agent-factory.js +161 -0
  31. package/src/lib/analytics/analytics-engine.js +345 -0
  32. package/src/lib/checkpoints/checkpoint-hooks.js +293 -258
  33. package/src/lib/context/context-bundler.js +240 -0
  34. package/src/lib/context/context-optimizer.js +212 -0
  35. package/src/lib/context/context-tracker.js +273 -0
  36. package/src/lib/context/core-four-tracker.js +201 -0
  37. package/src/lib/context/mcp-optimizer.js +200 -0
  38. package/src/lib/execution/fusion-executor.js +304 -0
  39. package/src/lib/execution/parallel-executor.js +270 -0
  40. package/src/lib/generators/context-generator.js +3 -3
  41. package/src/lib/generators/recap-generator.js +2 -2
  42. package/src/lib/hooks/hook-executor.js +169 -0
  43. package/src/lib/hooks/stop-hook-executor.js +286 -0
  44. package/src/lib/hops/hop-composer.js +221 -0
  45. package/src/lib/threads/thread-coordinator.js +238 -0
  46. package/src/lib/threads/thread-manager.js +317 -0
  47. package/src/lib/tracking/artifact-trail.js +202 -0
  48. package/src/lib/trust/trust-manager.js +269 -0
  49. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  50. package/src/lib/validators/validation-runner.js +6 -6
  51. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  52. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  53. package/CLAUDE.md +0 -993
  54. package/docs/llm-interaction-config.md +0 -735
  55. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  56. package/src/commands/utils/migrate-state.js +0 -158
  57. package/src/commands/utils/upgrade.js +0 -346
  58. package/src/lib/validators/architecture-validator.js +0 -60
  59. package/src/lib/validators/content-validator.js +0 -164
  60. package/src/lib/validators/package-validator.js +0 -61
  61. package/src/lib/validators/ui-contrast-validator.js +0 -44
  62. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  63. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  64. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  65. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  66. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  67. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  68. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  69. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  70. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  71. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  72. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  73. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  74. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  75. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  76. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  77. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  78. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  79. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  80. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  81. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  82. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  83. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  84. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  85. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  86. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  87. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  88. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  107. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  108. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  109. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  110. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  111. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  112. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  113. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  114. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  115. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  116. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  117. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  118. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  119. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  120. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  121. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  122. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  123. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  124. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  125. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  126. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  127. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  128. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  129. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  130. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  131. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  132. /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 V3_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
+ // v3.0 command files (top-level new commands)
75
+ const V3_COMMAND_FILES = [
76
+ 'src/commands/agents-fuse.js',
77
+ 'src/commands/analytics.js',
78
+ 'src/commands/context-prime.js',
79
+ 'src/commands/core-four.js',
80
+ 'src/commands/mcp.js',
81
+ 'src/commands/micro-agent.js',
82
+ 'src/commands/squad-template.js',
83
+ 'src/commands/thread-template.js',
84
+ 'src/commands/threads.js',
85
+ 'src/commands/trust.js'
86
+ ];
87
+
88
+ // v3.0 HOP templates (meta-prompts)
89
+ const V3_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/meta-prompts/REGISTRY.json'
102
+ ];
103
+
104
+ // v3.0 new standards (framework-level)
105
+ const V3_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
+ // v3.0 new agents expected in agents.json
132
+ const V3_NEW_AGENTS = [
133
+ 'vector-search-expert',
134
+ 'thread-orchestrator',
135
+ 'context-optimizer',
136
+ 'observability-expert'
137
+ ];
138
+
139
+ /**
140
+ * Run v3.0-specific health checks
141
+ */
142
+ async function doctorV3Command(frameworkRoot) {
143
+ console.log(chalk.bold('\n🔬 MORPH-SPEC v3.0 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. v3.0 Lib Files (15) ──────────────────────────────────────────────
161
+ console.log(chalk.cyan('\n src/lib/ v3.0 Core Libraries (15 files)'));
162
+ const missingLibs = [];
163
+ for (const f of V3_LIB_FILES) {
164
+ if (!(await pathExists(join(frameworkRoot, f)))) missingLibs.push(f.split('/').pop());
165
+ }
166
+ check(`v3.0 lib files (${V3_LIB_FILES.length - missingLibs.length}/${V3_LIB_FILES.length})`,
167
+ missingLibs.length === 0, false,
168
+ missingLibs.length > 0 ? `missing: ${missingLibs.join(', ')}` : '');
169
+
170
+ // ── 2. v3.0 Command Files (11) ──────────────────────────────────────────
171
+ console.log(chalk.cyan('\n src/commands/ v3.0 Command Files (11 files)'));
172
+ const missingCmds = [];
173
+ for (const f of V3_COMMAND_FILES) {
174
+ if (!(await pathExists(join(frameworkRoot, f)))) missingCmds.push(f.split('/').pop());
175
+ }
176
+ check(`v3.0 command files (${V3_COMMAND_FILES.length - missingCmds.length}/${V3_COMMAND_FILES.length})`,
177
+ missingCmds.length === 0, false,
178
+ missingCmds.length > 0 ? `missing: ${missingCmds.join(', ')}` : '');
179
+
180
+ // ── 3. HOP Templates (12) ───────────────────────────────────────────────
181
+ console.log(chalk.cyan('\n framework/templates/meta-prompts/ HOP Templates (12)'));
182
+ const missingHOPs = [];
183
+ for (const f of V3_HOP_TEMPLATES) {
184
+ if (!(await pathExists(join(frameworkRoot, f)))) missingHOPs.push(f.split('/').pop());
185
+ }
186
+ check(`HOP templates (${V3_HOP_TEMPLATES.length - missingHOPs.length}/${V3_HOP_TEMPLATES.length})`,
187
+ missingHOPs.length === 0, false,
188
+ missingHOPs.length > 0 ? `missing: ${missingHOPs.join(', ')}` : '');
189
+
190
+ // ── 4. New Standards (23) ───────────────────────────────────────────────
191
+ console.log(chalk.cyan('\n framework/standards/ v3.0 Standards (23 files)'));
192
+ const missingStds = [];
193
+ for (const f of V3_STANDARDS) {
194
+ if (!(await pathExists(join(frameworkRoot, f)))) missingStds.push(f.replace('framework/standards/', ''));
195
+ }
196
+ check(`v3.0 standards (${V3_STANDARDS.length - missingStds.length}/${V3_STANDARDS.length})`,
197
+ missingStds.length === 0, false,
198
+ missingStds.length > 0 ? `missing: ${missingStds.join(', ')}` : '');
199
+
200
+ // ── 5. New Agents in agents.json ────────────────────────────────────────
201
+ console.log(chalk.cyan('\n .morph/config/agents.json v3.0 Agents (4 new)'));
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 = V3_NEW_AGENTS.filter(id => !agents[id]);
208
+ check(`v3.0 agents (${V3_NEW_AGENTS.length - missingAgents.length}/${V3_NEW_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 v3.0 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 isV3 = state.version === '3.0.0';
230
+ check(`state.json schema (v${state.version})`, isV3, true,
231
+ isV3 ? '' : 'run: morph-spec migrate v2-to-v3');
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 — run "morph-spec migrate v2-to-v3" to fix\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 v3.0 checks passed!\n`));
263
+ }
264
+ }
265
+
266
+ export async function doctorCommand(options = {}) {
267
+ // v3-specific check mode
268
+ if (options.v3) {
269
+ const frameworkRoot = process.cwd();
270
+ return doctorV3Command(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
+ }