@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.
- package/CLAUDE.md +108 -946
- package/bin/morph-spec.js +284 -9
- package/bin/task-manager.cjs +102 -14
- package/bin/validate.js +4 -4
- package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
- package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
- package/docs/next-generation/EXECUTION-FLOW.md +274 -0
- package/docs/next-generation/META-PROMPTS.md +235 -0
- package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
- package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
- package/package.json +5 -5
- package/src/commands/agents/agents-fuse.js +97 -0
- package/src/commands/agents/micro-agent.js +112 -0
- package/src/commands/agents/spawn-team.js +69 -4
- package/src/commands/agents/squad-template.js +146 -0
- package/src/commands/analytics/analytics.js +176 -0
- package/src/commands/context/context-prime.js +63 -0
- package/src/commands/context/core-four.js +54 -0
- package/src/commands/mcp/mcp.js +102 -0
- package/src/commands/project/detect-agents.js +32 -2
- package/src/commands/project/detect.js +11 -1
- package/src/commands/project/doctor.js +573 -356
- package/src/commands/project/init.js +9 -2
- package/src/commands/project/update.js +13 -3
- package/src/commands/state/advance-phase.js +448 -416
- package/src/commands/state/state.js +14 -12
- package/src/commands/tasks/task.js +1 -1
- package/src/commands/templates/template-render.js +80 -1
- package/src/commands/threads/thread-template.js +103 -0
- package/src/commands/threads/threads.js +261 -0
- package/src/commands/trust/trust.js +205 -0
- package/src/{orchestrator.js → core/orchestrator.js} +8 -8
- package/src/core/state/state-manager.js +37 -17
- package/src/core/workflows/workflow-detector.js +114 -3
- package/src/lib/agents/micro-agent-factory.js +161 -0
- package/src/lib/analytics/analytics-engine.js +345 -0
- package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
- package/src/lib/context/context-bundler.js +240 -0
- package/src/lib/context/context-optimizer.js +212 -0
- package/src/lib/context/context-tracker.js +273 -0
- package/src/lib/context/core-four-tracker.js +201 -0
- package/src/lib/context/mcp-optimizer.js +200 -0
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/detectors/standards-generator.js +77 -17
- package/src/lib/detectors/structure-detector.js +67 -39
- package/src/lib/execution/fusion-executor.js +304 -0
- package/src/lib/execution/parallel-executor.js +270 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/recap-generator.js +32 -12
- package/src/lib/hooks/hook-executor.js +169 -0
- package/src/lib/hooks/stop-hook-executor.js +286 -0
- package/src/lib/hops/hop-composer.js +221 -0
- package/src/lib/threads/thread-coordinator.js +238 -0
- package/src/lib/threads/thread-manager.js +317 -0
- package/src/lib/tracking/artifact-trail.js +202 -0
- package/src/lib/trust/trust-manager.js +269 -0
- package/src/lib/validators/design-system/design-system-validator.js +2 -2
- package/src/lib/validators/validation-runner.js +14 -30
- package/src/utils/hooks-installer.js +69 -0
- package/stacks/blazor-azure/.morph/config/agents.json +72 -3
- package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
- package/docs/llm-interaction-config.md +0 -735
- package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
- package/src/commands/utils/migrate-state.js +0 -158
- package/src/commands/utils/upgrade.js +0 -346
- package/src/lib/validators/architecture-validator.js +0 -60
- package/src/lib/validators/content-validator.js +0 -164
- package/src/lib/validators/package-validator.js +0 -61
- package/src/lib/validators/ui-contrast-validator.js +0 -44
- package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
- package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
- package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
- package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
- package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
- package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
- package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
- package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
- package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
- package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
- package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
- package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
- package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
- package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
- package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
- package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
- /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
- /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
- /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
- /package/docs/{v3.0 → next-generation}/README.md +0 -0
- /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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
+
}
|