@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,416 +1,448 @@
1
- /**
2
- * MORPH-SPEC Phase Advance Command
3
- *
4
- * Validates current phase → advances to next → shows requirements.
5
- * Replaces the two-step `state set` + `validate-phase` dance.
6
- *
7
- * Usage:
8
- * morph-spec phase advance <feature>
9
- * morph-spec phase advance <feature> --skip-optional
10
- */
11
-
12
- import chalk from 'chalk';
13
- import { loadState, saveState, getFeature, getApprovalGate } from '../../core/state/state-manager.js';
14
- import { PHASES, validatePhase } from './validate-phase.js';
15
- import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
16
- import { validateSpec } from '../../lib/validators/spec-validator.js';
17
- import { validateTransition, getPhaseDisplayName } from '../../core/state/phase-state-machine.js';
18
- import { validateSpecContent, validateTasksContent, validateFeatureOutputs } from '../../lib/validators/content-validator.js';
19
- import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
20
- import { readFileSync, existsSync } from 'fs';
21
- import { join, dirname } from 'path';
22
- import { fileURLToPath } from 'url';
23
-
24
- const __dirname = dirname(fileURLToPath(import.meta.url));
25
-
26
- // Phase order for advancing (skips optional phases unless active)
27
- const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
28
-
29
- /**
30
- * Get the next phase after the current one
31
- */
32
- function getNextPhase(currentPhase, feature, skipOptional) {
33
- // Load workflow config if workflow is set
34
- let workflowConfig = null;
35
- if (feature.workflow && feature.workflow !== 'auto') {
36
- workflowConfig = getWorkflowConfig(feature.workflow);
37
- }
38
-
39
- const currentIndex = PHASE_ORDER.indexOf(currentPhase);
40
- if (currentIndex === -1 || currentIndex >= PHASE_ORDER.length - 1) {
41
- return null;
42
- }
43
-
44
- for (let i = currentIndex + 1; i < PHASE_ORDER.length; i++) {
45
- const candidate = PHASE_ORDER[i];
46
- const phaseDef = PHASES[candidate];
47
-
48
- // === Workflow-Based Phase Skipping ===
49
- if (workflowConfig && workflowConfig.phases) {
50
- // Check if workflow explicitly skips this phase
51
- if (workflowConfig.phases.skip && workflowConfig.phases.skip.includes(candidate)) {
52
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: workflow ${feature.workflow} skips this phase`));
53
- continue;
54
- }
55
-
56
- // Check if this phase is part of a combined phase
57
- if (workflowConfig.phases.combined) {
58
- for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
59
- if (phases.includes(candidate) && candidate !== combinedName) {
60
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
61
- continue;
62
- }
63
- }
64
- }
65
-
66
- // Check if workflow only runs specific phases
67
- if (workflowConfig.phases.run && !workflowConfig.phases.run.includes(candidate)) {
68
- // Also check if it's not a combined phase
69
- const isCombinedPhase = workflowConfig.phases.combined &&
70
- Object.keys(workflowConfig.phases.combined).includes(candidate);
71
- if (!isCombinedPhase) {
72
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: not in workflow run list`));
73
- continue;
74
- }
75
- }
76
-
77
- // Check conditional skips
78
- if (workflowConfig.phases.skipIfCondition) {
79
- const condition = workflowConfig.phases.skipIfCondition[candidate];
80
- if (condition) {
81
- // Evaluate condition (simple conditions for now)
82
- if (condition === '!hasUIAgents' && !hasUIAgentsActive(feature)) {
83
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
84
- continue;
85
- }
86
- }
87
- }
88
- }
89
-
90
- // === Legacy Phase Skipping (fallback when no workflow config) ===
91
- if (!workflowConfig) {
92
- // Skip optional phases if flag set or no relevant agents/outputs
93
- if (phaseDef?.optional && skipOptional) {
94
- continue;
95
- }
96
-
97
- // Skip uiux if no UI agents are active
98
- if (candidate === 'uiux') {
99
- const uiAgents = ['blazor-builder', 'uiux-designer', 'nextjs-expert', 'ui-ux-designer'];
100
- const hasUiAgent = feature.activeAgents?.some(a => uiAgents.includes(a));
101
- if (!hasUiAgent && skipOptional !== false) {
102
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
103
- continue;
104
- }
105
- }
106
-
107
- // Skip sync for simple workflows
108
- if (candidate === 'sync') {
109
- const isSimple = feature.workflow === 'fast-track';
110
- if (isSimple && skipOptional !== false) {
111
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: fast-track workflow`));
112
- continue;
113
- }
114
- }
115
- }
116
-
117
- return candidate;
118
- }
119
-
120
- return null;
121
- }
122
-
123
- /**
124
- * Main command handler
125
- */
126
- export async function advancePhaseCommand(feature, options = {}) {
127
- console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
128
- console.log(chalk.cyan('║ MORPH-SPEC PHASE ADVANCE ║'));
129
- console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
130
-
131
- // Get current feature state
132
- const featureData = getFeature(feature);
133
- if (!featureData) {
134
- console.log(chalk.red(`✗ Feature not found: ${feature}`));
135
- console.log(chalk.yellow(` Run: morph-spec state set ${feature} phase proposal`));
136
- process.exit(1);
137
- }
138
-
139
- const currentPhase = featureData.phase;
140
- const currentPhaseDef = PHASES[currentPhase];
141
-
142
- console.log(chalk.gray('Feature:'), feature);
143
- console.log(chalk.gray('Current Phase:'), currentPhaseDef?.name || currentPhase);
144
- console.log(chalk.gray('Workflow:'), featureData.workflow || 'auto');
145
-
146
- // Determine next phase
147
- const nextPhase = getNextPhase(currentPhase, featureData, options.skipOptional);
148
-
149
- if (!nextPhase) {
150
- console.log(chalk.green('\n✓ Feature is at the final phase!'));
151
- if (currentPhase === 'implement' || currentPhase === 'sync') {
152
- console.log(chalk.green(' Consider running: morph-spec generate recap ' + feature));
153
- }
154
- return;
155
- }
156
-
157
- const nextPhaseDef = PHASES[nextPhase];
158
- console.log(chalk.gray('Next Phase:'), nextPhaseDef.name);
159
-
160
- // === GATE 1: State Machine Validation ===
161
- // Ensure phase transition is valid (no skipping required phases)
162
- if (!options.force) {
163
- try {
164
- validateTransition(currentPhase, nextPhase);
165
- } catch (error) {
166
- console.log(chalk.red('\n✗ Invalid phase transition'));
167
- console.log(chalk.yellow(error.message));
168
- console.log(chalk.gray('\nUse --force to override (not recommended)\n'));
169
- process.exit(1);
170
- }
171
- }
172
-
173
- // === GATE 2: Approval Gate Check ===
174
- // Check if current phase requires approval before advancing
175
- const approvalGateMap = {
176
- 'design': 'design',
177
- 'tasks': 'tasks',
178
- 'uiux': 'uiux'
179
- };
180
-
181
- const requiredGate = approvalGateMap[currentPhase];
182
- if (requiredGate && !options.skipApproval) {
183
- const gateStatus = getApprovalGate(feature, requiredGate);
184
-
185
- if (!gateStatus || !gateStatus.approved) {
186
- console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
187
- console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
188
- console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
189
- process.exit(1);
190
- }
191
-
192
- console.log(chalk.green(`✓ Approval gate "${requiredGate}" passed`));
193
- }
194
-
195
- // === GATE 3: Output Requirements ===
196
- // Validate that current phase requirements are met before advancing
197
- const validation = validatePhase(feature, nextPhase);
198
-
199
- if (!validation.valid) {
200
- console.log(chalk.red('\n✗ Cannot advance — missing requirements:'));
201
- validation.missingOutputs.forEach(output => {
202
- console.log(chalk.red(` - ${output}`));
203
- });
204
- console.log(chalk.yellow(`\n Complete these before advancing to ${nextPhaseDef.name}`));
205
- process.exit(1);
206
- }
207
-
208
- if (validation.stateWarning) {
209
- console.log(chalk.yellow(`\n⚠️ ${validation.stateWarning}`));
210
- }
211
-
212
- // === GATE 4: Content Validation ===
213
- // Validate spec.md and contracts.cs when advancing from design phase
214
- if (currentPhase === 'design' && (nextPhase === 'clarify' || nextPhase === 'tasks')) {
215
- // Check spec.md structure and content
216
- if (featureData.outputs?.spec?.created) {
217
- const specContentValidation = validateSpecContent(featureData.outputs.spec.path);
218
-
219
- if (!specContentValidation.valid) {
220
- console.log(chalk.red('\n✗ Spec content validation failed:'));
221
- specContentValidation.missing.forEach(section => {
222
- console.log(chalk.red(` - Missing section: ${section}`));
223
- });
224
- specContentValidation.errors.forEach(error => {
225
- console.log(chalk.red(` - ${error}`));
226
- });
227
- console.log('');
228
- process.exit(1);
229
- }
230
-
231
- if (specContentValidation.warnings?.length > 0 && !options.skipWarnings) {
232
- console.log(chalk.yellow('\n⚠️ Spec content warnings:'));
233
- specContentValidation.warnings.forEach(warning => {
234
- console.log(chalk.yellow(` - ${warning}`));
235
- });
236
- }
237
- }
238
-
239
- // Run existing spec validator (anti-patterns, IaC checks)
240
- const specValidation = await validateSpec('.', feature);
241
-
242
- if (specValidation.errors > 0) {
243
- console.log(chalk.red('\n✗ Spec validation failed — fix errors before advancing:'));
244
- specValidation.issues.filter(i => i.level === 'error').forEach(issue => {
245
- console.log(chalk.red(` - ${issue.message}`));
246
- console.log(chalk.yellow(` → ${issue.solution}`));
247
- });
248
- console.log('');
249
- process.exit(1);
250
- }
251
-
252
- if (specValidation.warnings > 0 && !options.skipWarnings) {
253
- console.log(chalk.yellow('\n⚠️ Spec validation warnings:'));
254
- specValidation.issues.filter(i => i.level === 'warning').forEach(issue => {
255
- console.log(chalk.yellow(` - ${issue.message}`));
256
- });
257
- console.log(chalk.gray('\n (Use --skip-warnings to ignore warnings)\n'));
258
- }
259
- }
260
-
261
- // === GATE 5: Tasks Content Validation ===
262
- // Validate tasks.json structure when advancing to implement
263
- if (currentPhase === 'tasks' && nextPhase === 'implement') {
264
- if (featureData.outputs?.tasks?.created) {
265
- const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
266
-
267
- if (!tasksContentValidation.valid) {
268
- console.log(chalk.red('\n✗ Tasks content validation failed:'));
269
- tasksContentValidation.errors.forEach(error => {
270
- console.log(chalk.red(` - ${error}`));
271
- });
272
- console.log('');
273
- process.exit(1);
274
- }
275
-
276
- if (tasksContentValidation.warnings?.length > 0 && !options.skipWarnings) {
277
- console.log(chalk.yellow('\n⚠️ Tasks content warnings:'));
278
- tasksContentValidation.warnings.forEach(warning => {
279
- console.log(chalk.yellow(` - ${warning}`));
280
- });
281
- }
282
-
283
- // Show tasks stats
284
- console.log(chalk.green(`\n✓ Tasks validated: ${tasksContentValidation.stats.totalTasks} total (${tasksContentValidation.stats.regularTasks} tasks + ${tasksContentValidation.stats.checkpoints} checkpoints)`));
285
- }
286
- }
287
-
288
- // Gate: Check design system when advancing to implement with UI agents
289
- if (nextPhase === 'implement') {
290
- const gateResult = designSystemGate(feature);
291
-
292
- if (gateResult.blocked) {
293
- console.log(chalk.red(`\n✗ ${gateResult.message}`));
294
- console.log('');
295
- gateResult.solution.forEach(line => {
296
- if (line === '') {
297
- console.log('');
298
- } else {
299
- console.log(chalk.yellow(` ${line}`));
300
- }
301
- });
302
- console.log('');
303
- process.exit(1);
304
- }
305
- }
306
-
307
- // Advance the phase
308
- const state = loadState();
309
- state.features[feature].phase = nextPhase;
310
- state.features[feature].updatedAt = new Date().toISOString();
311
- saveState(state);
312
-
313
- console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
314
-
315
- // Show what's needed in the new phase
316
- console.log(chalk.cyan('\n📋 Phase requirements:'));
317
- console.log(chalk.gray(` ${nextPhaseDef.description}`));
318
-
319
- if (nextPhaseDef.requiredOutputs?.length > 0) {
320
- console.log(chalk.cyan('\n Required outputs:'));
321
- nextPhaseDef.requiredOutputs.forEach(output => {
322
- const exists = validation.phase ? true : false; // Already validated
323
- console.log(chalk.gray(` ✓ ${output} (exists)`));
324
- });
325
- }
326
-
327
- // Show phase-specific guidance
328
- const guidance = getPhaseGuidance(nextPhase, feature);
329
- if (guidance) {
330
- console.log(chalk.cyan('\n Next steps:'));
331
- guidance.forEach(step => console.log(chalk.white(` → ${step}`)));
332
- }
333
-
334
- console.log('');
335
- }
336
-
337
- /**
338
- * Get guidance for what to do in a phase
339
- */
340
- function getPhaseGuidance(phase, feature) {
341
- const guides = {
342
- 'setup': [
343
- `Resume spec pipeline: /morph-proposal ${feature}`,
344
- 'Auto-continues from setup phase'
345
- ],
346
- 'uiux': [
347
- `Resume spec pipeline: /morph-proposal ${feature}`,
348
- 'Provide layout references and preferences',
349
- 'Review wireframes before proceeding'
350
- ],
351
- 'design': [
352
- `Resume spec pipeline: /morph-proposal ${feature}`,
353
- 'Review DECISION POINTS carefully',
354
- 'Approve spec.md and contracts.cs'
355
- ],
356
- 'clarify': [
357
- `Resume spec pipeline: /morph-proposal ${feature}`,
358
- 'Resolve edge cases and unknowns'
359
- ],
360
- 'tasks': [
361
- `Resume spec pipeline: /morph-proposal ${feature}`,
362
- 'Review task order and dependencies',
363
- 'Approve task list before implementing'
364
- ],
365
- 'implement': [
366
- `Start implementing: /morph-apply ${feature}`,
367
- 'Complete tasks one by one with: morph-spec task done',
368
- 'Validators run automatically on task completion'
369
- ],
370
- 'sync': [
371
- 'Review decisions.md for standards to promote',
372
- `Sync: morph-spec sync --path .morph/project/outputs/${feature}/decisions.md`
373
- ]
374
- };
375
-
376
- return guides[phase] || null;
377
- }
378
-
379
- /**
380
- * Gate: Check design system exists when advancing to implement with UI agents
381
- * @param {string} feature - Feature name
382
- * @param {string} projectPath - Project root path
383
- * @returns {Object} { blocked: boolean, message?: string, solution?: string[] }
384
- */
385
- function designSystemGate(feature, projectPath = '.') {
386
- // Check if UI agents are active
387
- const hasUIAgents = hasUIAgentsActive(projectPath, feature);
388
-
389
- if (!hasUIAgents) {
390
- return { blocked: false }; // No UI agents, gate doesn't apply
391
- }
392
-
393
- // Check if design system exists
394
- const dsDetection = detectDesignSystem(projectPath, feature);
395
-
396
- if (dsDetection.hasDesignSystem) {
397
- return { blocked: false }; // Design system exists, gate passed
398
- }
399
-
400
- // Block advancement - no design system found with UI agents active
401
- return {
402
- blocked: true,
403
- message: 'Cannot advance to implementation UI agents are active but no design system found',
404
- solution: [
405
- 'Design system is required when UI agents (blazor-builder, ui-designer, css-specialist) are active',
406
- '',
407
- 'Create a design system with one of these options:',
408
- ' 1. Project-level: .morph/project/design-system.md (shared across features)',
409
- ` 2. Feature-level: .morph/project/outputs/${feature}/ui-design-system.md (feature-specific)`,
410
- ' 3. Auto-generate: morph-spec generate design-system (scans existing CSS)',
411
- '',
412
- 'Or remove UI agents if they are not needed:',
413
- ` morph-spec state remove-agent ${feature} blazor-builder`
414
- ]
415
- };
416
- }
1
+ /**
2
+ * MORPH-SPEC Phase Advance Command
3
+ *
4
+ * Validates current phase → advances to next → shows requirements.
5
+ * Replaces the two-step `state set` + `validate-phase` dance.
6
+ *
7
+ * Usage:
8
+ * morph-spec phase advance <feature>
9
+ * morph-spec phase advance <feature> --skip-optional
10
+ */
11
+
12
+ import chalk from 'chalk';
13
+ import { loadState, saveState, getFeature, getApprovalGate } from '../../core/state/state-manager.js';
14
+ import { PHASES, validatePhase } from './validate-phase.js';
15
+ import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
16
+ import { shouldAutoApprove, autoCalculateTrust } from '../../lib/trust/trust-manager.js';
17
+ import { validateSpec } from '../../lib/validators/spec-validator.js';
18
+ import { validateTransition, getPhaseDisplayName } from '../../core/state/phase-state-machine.js';
19
+ import { validateSpecContent, validateTasksContent, validateFeatureOutputs } from '../../lib/validators/content/content-validator.js';
20
+ import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
21
+ import { readFileSync, existsSync } from 'fs';
22
+ import { join, dirname } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+
27
+ // Phase order for advancing (skips optional phases unless active)
28
+ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
29
+
30
+ /**
31
+ * Get the next phase after the current one
32
+ */
33
+ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
34
+ // Load workflow config if workflow is set
35
+ let workflowConfig = null;
36
+ if (feature.workflow && feature.workflow !== 'auto') {
37
+ workflowConfig = getWorkflowConfig(feature.workflow);
38
+ }
39
+
40
+ const currentIndex = PHASE_ORDER.indexOf(currentPhase);
41
+ if (currentIndex === -1 || currentIndex >= PHASE_ORDER.length - 1) {
42
+ return null;
43
+ }
44
+
45
+ for (let i = currentIndex + 1; i < PHASE_ORDER.length; i++) {
46
+ const candidate = PHASE_ORDER[i];
47
+ const phaseDef = PHASES[candidate];
48
+
49
+ // === Workflow-Based Phase Skipping ===
50
+ if (workflowConfig && workflowConfig.phases) {
51
+ // Check if workflow explicitly skips this phase
52
+ if (workflowConfig.phases.skip && workflowConfig.phases.skip.includes(candidate)) {
53
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: workflow ${feature.workflow} skips this phase`));
54
+ continue;
55
+ }
56
+
57
+ // Check if this phase is part of a combined phase
58
+ if (workflowConfig.phases.combined) {
59
+ for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
60
+ if (phases.includes(candidate) && candidate !== combinedName) {
61
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
62
+ continue;
63
+ }
64
+ }
65
+ }
66
+
67
+ // Check if workflow only runs specific phases
68
+ if (workflowConfig.phases.run && !workflowConfig.phases.run.includes(candidate)) {
69
+ // Also check if it's not a combined phase
70
+ const isCombinedPhase = workflowConfig.phases.combined &&
71
+ Object.keys(workflowConfig.phases.combined).includes(candidate);
72
+ if (!isCombinedPhase) {
73
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: not in workflow run list`));
74
+ continue;
75
+ }
76
+ }
77
+
78
+ // Check conditional skips
79
+ if (workflowConfig.phases.skipIfCondition) {
80
+ const condition = workflowConfig.phases.skipIfCondition[candidate];
81
+ if (condition) {
82
+ // Evaluate condition (simple conditions for now)
83
+ if (condition === '!hasUIAgents' && !hasUIAgentsActive(process.cwd(), featureName)) {
84
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
85
+ continue;
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ // === Legacy Phase Skipping (fallback when no workflow config) ===
92
+ if (!workflowConfig) {
93
+ // Skip optional phases if flag set or no relevant agents/outputs
94
+ if (phaseDef?.optional && skipOptional) {
95
+ continue;
96
+ }
97
+
98
+ // Skip uiux if no UI agents are active
99
+ if (candidate === 'uiux') {
100
+ const uiAgents = ['blazor-builder', 'uiux-designer', 'nextjs-expert', 'ui-ux-designer'];
101
+ const hasUiAgent = feature.activeAgents?.some(a => uiAgents.includes(a));
102
+ if (!hasUiAgent && skipOptional !== false) {
103
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
104
+ continue;
105
+ }
106
+ }
107
+
108
+ // Skip sync for simple workflows
109
+ if (candidate === 'sync') {
110
+ const isSimple = feature.workflow === 'fast-track';
111
+ if (isSimple && skipOptional !== false) {
112
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: fast-track workflow`));
113
+ continue;
114
+ }
115
+ }
116
+ }
117
+
118
+ return candidate;
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ /**
125
+ * Main command handler
126
+ */
127
+ export async function advancePhaseCommand(feature, options = {}) {
128
+ console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
129
+ console.log(chalk.cyan('║ MORPH-SPEC PHASE ADVANCE ║'));
130
+ console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
131
+
132
+ // Get current feature state
133
+ const featureData = getFeature(feature);
134
+ if (!featureData) {
135
+ console.log(chalk.red(`✗ Feature not found: ${feature}`));
136
+ console.log(chalk.yellow(` Run: morph-spec state set ${feature} phase proposal`));
137
+ process.exit(1);
138
+ }
139
+
140
+ const currentPhase = featureData.phase;
141
+ const currentPhaseDef = PHASES[currentPhase];
142
+
143
+ console.log(chalk.gray('Feature:'), feature);
144
+ console.log(chalk.gray('Current Phase:'), currentPhaseDef?.name || currentPhase);
145
+ console.log(chalk.gray('Workflow:'), featureData.workflow || 'auto');
146
+
147
+ // Determine next phase
148
+ const nextPhase = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
149
+
150
+ if (!nextPhase) {
151
+ console.log(chalk.green('\n✓ Feature is at the final phase!'));
152
+ if (currentPhase === 'implement' || currentPhase === 'sync') {
153
+ console.log(chalk.green(' Consider running: morph-spec generate recap ' + feature));
154
+ }
155
+ return;
156
+ }
157
+
158
+ const nextPhaseDef = PHASES[nextPhase];
159
+ console.log(chalk.gray('Next Phase:'), nextPhaseDef.name);
160
+
161
+ // === GATE 1: State Machine Validation ===
162
+ // Ensure phase transition is valid (no skipping required phases)
163
+ if (!options.force) {
164
+ try {
165
+ validateTransition(currentPhase, nextPhase);
166
+ } catch (error) {
167
+ console.log(chalk.red('\n✗ Invalid phase transition'));
168
+ console.log(chalk.yellow(error.message));
169
+ console.log(chalk.gray('\nUse --force to override (not recommended)\n'));
170
+ process.exit(1);
171
+ }
172
+ }
173
+
174
+ // === GATE 2: Approval Gate Check ===
175
+ // Check if current phase requires approval before advancing
176
+ const approvalGateMap = {
177
+ 'design': 'design',
178
+ 'tasks': 'tasks',
179
+ 'uiux': 'uiux'
180
+ };
181
+
182
+ const requiredGate = approvalGateMap[currentPhase];
183
+ if (requiredGate && !options.skipApproval) {
184
+ const gateStatus = getApprovalGate(feature, requiredGate);
185
+
186
+ // Check trust-based auto-approval before blocking
187
+ if (!gateStatus || !gateStatus.approved) {
188
+ try {
189
+ const trustResult = shouldAutoApprove(feature, requiredGate);
190
+ if (trustResult.autoApprove) {
191
+ console.log(chalk.green(`\n✓ Auto-approved gate "${requiredGate}" (${trustResult.reason})`));
192
+ // Proceed — trust bypasses manual gate requirement
193
+ } else {
194
+ console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
195
+ console.log(chalk.gray(` Trust level: ${trustResult.level} (${trustResult.reason})`));
196
+ console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
197
+ console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
198
+ process.exit(1);
199
+ }
200
+ } catch {
201
+ // Trust manager unavailable — fall through to manual gate check
202
+ console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
203
+ console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
204
+ console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
205
+ process.exit(1);
206
+ }
207
+ } else {
208
+ console.log(chalk.green(`✓ Approval gate "${requiredGate}" passed`));
209
+ }
210
+ }
211
+
212
+ // === GATE 3: Output Requirements ===
213
+ // Validate that current phase requirements are met before advancing
214
+ const validation = validatePhase(feature, nextPhase);
215
+
216
+ if (!validation.valid) {
217
+ console.log(chalk.red('\n✗ Cannot advance — missing requirements:'));
218
+ validation.missingOutputs.forEach(output => {
219
+ console.log(chalk.red(` - ${output}`));
220
+ });
221
+ console.log(chalk.yellow(`\n Complete these before advancing to ${nextPhaseDef.name}`));
222
+ process.exit(1);
223
+ }
224
+
225
+ if (validation.stateWarning) {
226
+ console.log(chalk.yellow(`\n⚠️ ${validation.stateWarning}`));
227
+ }
228
+
229
+ // === GATE 4: Content Validation ===
230
+ // Validate spec.md and contracts.cs when advancing from design phase
231
+ if (currentPhase === 'design' && (nextPhase === 'clarify' || nextPhase === 'tasks')) {
232
+ // Check spec.md structure and content
233
+ if (featureData.outputs?.spec?.created) {
234
+ const specContentValidation = validateSpecContent(featureData.outputs.spec.path);
235
+
236
+ if (!specContentValidation.valid) {
237
+ console.log(chalk.red('\n✗ Spec content validation failed:'));
238
+ specContentValidation.missing.forEach(section => {
239
+ console.log(chalk.red(` - Missing section: ${section}`));
240
+ });
241
+ specContentValidation.errors.forEach(error => {
242
+ console.log(chalk.red(` - ${error}`));
243
+ });
244
+ console.log('');
245
+ process.exit(1);
246
+ }
247
+
248
+ if (specContentValidation.warnings?.length > 0 && !options.skipWarnings) {
249
+ console.log(chalk.yellow('\n⚠️ Spec content warnings:'));
250
+ specContentValidation.warnings.forEach(warning => {
251
+ console.log(chalk.yellow(` - ${warning}`));
252
+ });
253
+ }
254
+ }
255
+
256
+ // Run existing spec validator (anti-patterns, IaC checks)
257
+ const specValidation = await validateSpec('.', feature);
258
+
259
+ if (specValidation.errors > 0) {
260
+ console.log(chalk.red('\n✗ Spec validation failed — fix errors before advancing:'));
261
+ specValidation.issues.filter(i => i.level === 'error').forEach(issue => {
262
+ console.log(chalk.red(` - ${issue.message}`));
263
+ console.log(chalk.yellow(` → ${issue.solution}`));
264
+ });
265
+ console.log('');
266
+ process.exit(1);
267
+ }
268
+
269
+ if (specValidation.warnings > 0 && !options.skipWarnings) {
270
+ console.log(chalk.yellow('\n⚠️ Spec validation warnings:'));
271
+ specValidation.issues.filter(i => i.level === 'warning').forEach(issue => {
272
+ console.log(chalk.yellow(` - ${issue.message}`));
273
+ });
274
+ console.log(chalk.gray('\n (Use --skip-warnings to ignore warnings)\n'));
275
+ }
276
+ }
277
+
278
+ // === GATE 5: Tasks Content Validation ===
279
+ // Validate tasks.json structure when advancing to implement
280
+ if (currentPhase === 'tasks' && nextPhase === 'implement') {
281
+ if (featureData.outputs?.tasks?.created) {
282
+ const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
283
+
284
+ if (!tasksContentValidation.valid) {
285
+ console.log(chalk.red('\n✗ Tasks content validation failed:'));
286
+ tasksContentValidation.errors.forEach(error => {
287
+ console.log(chalk.red(` - ${error}`));
288
+ });
289
+ console.log('');
290
+ process.exit(1);
291
+ }
292
+
293
+ if (tasksContentValidation.warnings?.length > 0 && !options.skipWarnings) {
294
+ console.log(chalk.yellow('\n⚠️ Tasks content warnings:'));
295
+ tasksContentValidation.warnings.forEach(warning => {
296
+ console.log(chalk.yellow(` - ${warning}`));
297
+ });
298
+ }
299
+
300
+ // Show tasks stats
301
+ console.log(chalk.green(`\n✓ Tasks validated: ${tasksContentValidation.stats.totalTasks} total (${tasksContentValidation.stats.regularTasks} tasks + ${tasksContentValidation.stats.checkpoints} checkpoints)`));
302
+ }
303
+ }
304
+
305
+ // Gate: Check design system when advancing to implement with UI agents
306
+ if (nextPhase === 'implement') {
307
+ const gateResult = designSystemGate(feature, process.cwd());
308
+
309
+ if (gateResult.blocked) {
310
+ console.log(chalk.red(`\n✗ ${gateResult.message}`));
311
+ console.log('');
312
+ gateResult.solution.forEach(line => {
313
+ if (line === '') {
314
+ console.log('');
315
+ } else {
316
+ console.log(chalk.yellow(` ${line}`));
317
+ }
318
+ });
319
+ console.log('');
320
+ process.exit(1);
321
+ }
322
+ }
323
+
324
+ // Advance the phase
325
+ const state = loadState();
326
+ state.features[feature].phase = nextPhase;
327
+ state.features[feature].updatedAt = new Date().toISOString();
328
+ saveState(state);
329
+
330
+ // Run PhaseAdvanced agent-teams hook (non-blocking)
331
+ try {
332
+ const { executeHook, formatHookResults } = await import('../../lib/hooks/hook-executor.js');
333
+ const hookResult = await executeHook(process.cwd(), feature, 'PhaseAdvanced', {
334
+ fromPhase: currentPhase,
335
+ toPhase: nextPhase
336
+ });
337
+ if (!hookResult.passed && hookResult.errors.length > 0) {
338
+ const output = formatHookResults(hookResult, 'PhaseAdvanced');
339
+ console.log(output);
340
+ }
341
+ } catch {
342
+ // Hook executor unavailable — non-blocking
343
+ }
344
+
345
+ console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
346
+
347
+ // Show what's needed in the new phase
348
+ console.log(chalk.cyan('\n📋 Phase requirements:'));
349
+ console.log(chalk.gray(` ${nextPhaseDef.description}`));
350
+
351
+ if (nextPhaseDef.requiredOutputs?.length > 0) {
352
+ console.log(chalk.cyan('\n Required outputs:'));
353
+ nextPhaseDef.requiredOutputs.forEach(output => {
354
+ const exists = validation.phase ? true : false; // Already validated
355
+ console.log(chalk.gray(` ✓ ${output} (exists)`));
356
+ });
357
+ }
358
+
359
+ // Show phase-specific guidance
360
+ const guidance = getPhaseGuidance(nextPhase, feature);
361
+ if (guidance) {
362
+ console.log(chalk.cyan('\n Next steps:'));
363
+ guidance.forEach(step => console.log(chalk.white(` → ${step}`)));
364
+ }
365
+
366
+ console.log('');
367
+ }
368
+
369
+ /**
370
+ * Get guidance for what to do in a phase
371
+ */
372
+ function getPhaseGuidance(phase, feature) {
373
+ const guides = {
374
+ 'setup': [
375
+ `Resume spec pipeline: /morph-proposal ${feature}`,
376
+ 'Auto-continues from setup phase'
377
+ ],
378
+ 'uiux': [
379
+ `Resume spec pipeline: /morph-proposal ${feature}`,
380
+ 'Provide layout references and preferences',
381
+ 'Review wireframes before proceeding'
382
+ ],
383
+ 'design': [
384
+ `Resume spec pipeline: /morph-proposal ${feature}`,
385
+ 'Review DECISION POINTS carefully',
386
+ 'Approve spec.md and contracts.cs'
387
+ ],
388
+ 'clarify': [
389
+ `Resume spec pipeline: /morph-proposal ${feature}`,
390
+ 'Resolve edge cases and unknowns'
391
+ ],
392
+ 'tasks': [
393
+ `Resume spec pipeline: /morph-proposal ${feature}`,
394
+ 'Review task order and dependencies',
395
+ 'Approve task list before implementing'
396
+ ],
397
+ 'implement': [
398
+ `Start implementing: /morph-apply ${feature}`,
399
+ 'Complete tasks one by one with: morph-spec task done',
400
+ 'Validators run automatically on task completion'
401
+ ],
402
+ 'sync': [
403
+ 'Review decisions.md for standards to promote',
404
+ `Sync: morph-spec sync --path .morph/project/outputs/${feature}/decisions.md`
405
+ ]
406
+ };
407
+
408
+ return guides[phase] || null;
409
+ }
410
+
411
+ /**
412
+ * Gate: Check design system exists when advancing to implement with UI agents
413
+ * @param {string} feature - Feature name
414
+ * @param {string} projectPath - Project root path
415
+ * @returns {Object} { blocked: boolean, message?: string, solution?: string[] }
416
+ */
417
+ function designSystemGate(feature, projectPath = '.') {
418
+ // Check if UI agents are active
419
+ const hasUIAgents = hasUIAgentsActive(projectPath, feature);
420
+
421
+ if (!hasUIAgents) {
422
+ return { blocked: false }; // No UI agents, gate doesn't apply
423
+ }
424
+
425
+ // Check if design system exists
426
+ const dsDetection = detectDesignSystem(projectPath, feature);
427
+
428
+ if (dsDetection.hasDesignSystem) {
429
+ return { blocked: false }; // Design system exists, gate passed
430
+ }
431
+
432
+ // Block advancement - no design system found with UI agents active
433
+ return {
434
+ blocked: true,
435
+ message: 'Cannot advance to implementation — UI agents are active but no design system found',
436
+ solution: [
437
+ 'Design system is required when UI agents (blazor-builder, ui-designer, css-specialist) are active',
438
+ '',
439
+ 'Create a design system with one of these options:',
440
+ ' 1. Project-level: .morph/project/design-system.md (shared across features)',
441
+ ` 2. Feature-level: .morph/project/outputs/${feature}/ui-design-system.md (feature-specific)`,
442
+ ' 3. Auto-generate: morph-spec generate design-system (scans existing CSS)',
443
+ '',
444
+ 'Or remove UI agents if they are not needed:',
445
+ ` morph-spec state remove-agent ${feature} blazor-builder`
446
+ ]
447
+ };
448
+ }