@polymorphism-tech/morph-spec 4.22.0 → 5.0.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 (211) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1198
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
@@ -1,600 +0,0 @@
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, derivePhase } from '../../core/state/state-manager.js';
14
- import { PHASES, validatePhase } from '../../lib/phase-chain/phase-validator.js';
15
- import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
16
- import { getOutputPath, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
17
- import { shouldAutoApprove } from '../../lib/trust/trust-manager.js';
18
- import { validateSpec } from '../../lib/validators/spec-validator.js';
19
- import { validateTransition } from '../../core/state/phase-state-machine.js';
20
- import { validateSpecContent, validateTasksContent } from '../../lib/validators/content/content-validator.js';
21
- import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
22
- import { getStackProfile } from '../../lib/stack/stack-profile.js';
23
- import { readFileSync, existsSync, mkdirSync } from 'fs';
24
- import { join, dirname } from 'path';
25
- import { fileURLToPath } from 'url';
26
- import { emitValidatorDispatch } from '../../lib/validators/shared/emit-validator-dispatch.js';
27
-
28
- const __dirname = dirname(fileURLToPath(import.meta.url));
29
-
30
- // Phase order for advancing (skips optional phases unless active)
31
- const PHASE_ORDER = ['proposal', 'uiux', 'design', 'plan', 'tasks', 'implement', 'sync'];
32
-
33
- /**
34
- * Phases that require an explicit approval gate before advancing.
35
- * When --skip-approval is passed, these gates are bypassed and a warning is logged.
36
- * Exported for use in tests and in phase-runner warnings.
37
- */
38
- export const APPROVAL_GATE_MAP = {
39
- 'design': 'design',
40
- 'plan': 'plan',
41
- 'tasks': 'tasks',
42
- 'uiux': 'uiux'
43
- };
44
-
45
- /**
46
- * Get the next phase after the current one.
47
- * Returns { nextPhase, skippedPhases } so callers can persist skipped phases in state.
48
- */
49
- function getNextPhase(currentPhase, feature, skipOptional, featureName) {
50
- // Load workflow config if workflow is set
51
- let workflowConfig = null;
52
- if (feature.workflow && feature.workflow !== 'auto') {
53
- workflowConfig = getWorkflowConfig(feature.workflow);
54
- }
55
-
56
- const currentIndex = PHASE_ORDER.indexOf(currentPhase);
57
- if (currentIndex === -1 || currentIndex >= PHASE_ORDER.length - 1) {
58
- return { nextPhase: null, skippedPhases: [] };
59
- }
60
-
61
- // Agents that justify the uiux phase (wireframes, design system).
62
- // Implementation-only agents (nextjs-expert) are NOT included —
63
- // they build components from existing designs but don't create the design artifacts.
64
- const uiDesignAgents = ['uiux-designer', 'ui-ux-designer', 'ui-designer'];
65
-
66
- const skippedPhases = [];
67
-
68
- for (let i = currentIndex + 1; i < PHASE_ORDER.length; i++) {
69
- const candidate = PHASE_ORDER[i];
70
- const phaseDef = PHASES[candidate];
71
-
72
- // === Workflow-Based Phase Skipping ===
73
- if (workflowConfig && workflowConfig.phases) {
74
- // Check if workflow explicitly skips this phase
75
- if (workflowConfig.phases.skip && workflowConfig.phases.skip.includes(candidate)) {
76
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: workflow ${feature.workflow} skips this phase`));
77
- skippedPhases.push(candidate);
78
- continue;
79
- }
80
-
81
- // Check if this phase is part of a combined phase
82
- if (workflowConfig.phases.combined) {
83
- let isCombined = false;
84
- for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
85
- if (phases.includes(candidate) && candidate !== combinedName) {
86
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
87
- skippedPhases.push(candidate);
88
- isCombined = true;
89
- break;
90
- }
91
- }
92
- if (isCombined) continue;
93
- }
94
-
95
- // Check if workflow only runs specific phases
96
- if (workflowConfig.phases.run && !workflowConfig.phases.run.includes(candidate)) {
97
- // Also check if it's not a combined phase
98
- const isCombinedPhase = workflowConfig.phases.combined &&
99
- Object.keys(workflowConfig.phases.combined).includes(candidate);
100
- if (!isCombinedPhase) {
101
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: not in workflow run list`));
102
- skippedPhases.push(candidate);
103
- continue;
104
- }
105
- }
106
-
107
- // Check conditional skips
108
- if (workflowConfig.phases.skipIfCondition) {
109
- const condition = workflowConfig.phases.skipIfCondition[candidate];
110
- if (condition) {
111
- // Evaluate condition (simple conditions for now)
112
- if (condition === '!hasUIAgents' && !hasUIAgentsActive(process.cwd(), featureName)) {
113
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
114
- skippedPhases.push(candidate);
115
- continue;
116
- }
117
- }
118
- }
119
- }
120
-
121
- // === Legacy Phase Skipping (fallback when no workflow config) ===
122
- if (!workflowConfig) {
123
- // Skip optional phases if flag set or no relevant agents/outputs
124
- if (phaseDef?.optional && skipOptional) {
125
- skippedPhases.push(candidate);
126
- continue;
127
- }
128
-
129
- // Skip uiux if no UI design agents are active
130
- if (candidate === 'uiux') {
131
- if (feature.workflow === 'migration' || feature.workflow === 'refactor') {
132
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: ${feature.workflow} workflow — no visual changes`));
133
- skippedPhases.push(candidate);
134
- continue;
135
- }
136
- const hasUiDesignAgent = feature.activeAgents?.some(a => uiDesignAgents.includes(a));
137
- if (!hasUiDesignAgent && skipOptional !== false) {
138
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI design agents active`));
139
- skippedPhases.push(candidate);
140
- continue;
141
- }
142
- }
143
-
144
- // sync is optional — only skip if --skip-optional is set
145
- if (candidate === 'sync' && skipOptional) {
146
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: optional phase`));
147
- skippedPhases.push(candidate);
148
- continue;
149
- }
150
- }
151
-
152
- return { nextPhase: candidate, skippedPhases };
153
- }
154
-
155
- return { nextPhase: null, skippedPhases };
156
- }
157
-
158
- /**
159
- * Main command handler
160
- */
161
- export async function advancePhaseCommand(feature, options = {}) {
162
- console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
163
- console.log(chalk.cyan('║ MORPH-SPEC PHASE ADVANCE ║'));
164
- console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
165
-
166
- // Get current feature state
167
- const featureData = getFeature(feature);
168
- if (!featureData) {
169
- console.log(chalk.red(`✗ Feature not found: ${feature}`));
170
- console.log(chalk.yellow(` Run: morph-spec state set ${feature} phase proposal`));
171
- process.exit(1);
172
- }
173
-
174
- const featureFolderPath = join(process.cwd(), '.morph', 'features', feature);
175
- const currentPhase = featureData.phase || derivePhase(featureFolderPath);
176
- const currentPhaseDef = PHASES[currentPhase];
177
-
178
- console.log(chalk.gray('Feature:'), feature);
179
- console.log(chalk.gray('Current Phase:'), currentPhaseDef?.name || currentPhase);
180
- console.log(chalk.gray('Workflow:'), featureData.workflow || 'auto');
181
-
182
- // Determine next phase
183
- const { nextPhase, skippedPhases } = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
184
-
185
- if (!nextPhase) {
186
- console.log(chalk.green('\n✓ Feature is at the final phase!'));
187
- if (currentPhase === 'implement' || currentPhase === 'sync') {
188
- console.log(chalk.green(' Consider running: morph-spec generate recap ' + feature));
189
- }
190
- return;
191
- }
192
-
193
- const nextPhaseDef = PHASES[nextPhase];
194
- console.log(chalk.gray('Next Phase:'), nextPhaseDef.name);
195
-
196
- // === GATE 1: State Machine Validation ===
197
- // When a workflow config defines which phases to run, getNextPhase()
198
- // is authoritative for phase navigation. The state machine's
199
- // adjacent-only transitions would reject legitimate workflow skips
200
- // (e.g., fast-track: proposal → tasks).
201
- const activeWorkflowConfig = featureData.workflow && featureData.workflow !== 'auto'
202
- ? getWorkflowConfig(featureData.workflow)
203
- : null;
204
-
205
- if (!options.force && !activeWorkflowConfig) {
206
- try {
207
- validateTransition(currentPhase, nextPhase);
208
- } catch (error) {
209
- console.log(chalk.red('\n✗ Invalid phase transition'));
210
- console.log(chalk.yellow(error.message));
211
- console.log(chalk.gray('\nUse --force to override (not recommended)\n'));
212
- process.exit(1);
213
- }
214
- }
215
-
216
- // === GATE 2: Approval Gate Check ===
217
- // Check if current phase requires approval before advancing
218
- // Track gates that get approved implicitly so we can persist them after state load.
219
- let gateApprovedImplicitly = null; // { gate, approvedBy, reason }
220
-
221
- const requiredGate = APPROVAL_GATE_MAP[currentPhase];
222
- if (requiredGate && options.skipApproval) {
223
- console.log(chalk.yellow(`\n⚠ --skip-approval: Approval gate "${requiredGate}" bypassed for phase "${currentPhase}"`));
224
- console.log(chalk.gray(' Intended for CI/automation only. Run without --skip-approval for normal workflow.\n'));
225
- gateApprovedImplicitly = { gate: requiredGate, approvedBy: 'skip-approval' };
226
- }
227
- if (requiredGate && !options.skipApproval) {
228
- const gateStatus = getApprovalGate(feature, requiredGate);
229
-
230
- // Check trust-based auto-approval before blocking
231
- if (!gateStatus || !gateStatus.approved) {
232
- try {
233
- const trustResult = shouldAutoApprove(feature, requiredGate);
234
- if (trustResult.autoApprove) {
235
- console.log(chalk.green(`\n✓ Auto-approved gate "${requiredGate}" (${trustResult.reason})`));
236
- // Proceed — trust bypasses manual gate requirement
237
- gateApprovedImplicitly = { gate: requiredGate, approvedBy: `trust:${trustResult.level}` };
238
- } else {
239
- console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
240
- console.log(chalk.gray(` Trust level: ${trustResult.level} (${trustResult.reason})`));
241
- console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
242
- console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
243
- process.exit(1);
244
- }
245
- } catch {
246
- // Trust manager unavailable — fall through to manual gate check
247
- console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
248
- console.log(chalk.yellow(`\nRun: morph-spec approve ${feature} ${requiredGate}`));
249
- console.log(chalk.gray('Or use --skip-approval to bypass (not recommended)\n'));
250
- process.exit(1);
251
- }
252
- } else {
253
- console.log(chalk.green(`✓ Approval gate "${requiredGate}" passed`));
254
- }
255
- }
256
-
257
- // === GATE 3: Output Requirements ===
258
- // Validate that current phase requirements are met before advancing
259
- const validation = validatePhase(feature, nextPhase);
260
-
261
- if (!validation.valid) {
262
- console.log(chalk.red('\n✗ Cannot advance — missing requirements:'));
263
- validation.missingOutputs.forEach(output => {
264
- console.log(chalk.red(` - ${output}`));
265
- });
266
- console.log(chalk.yellow(`\n Complete these before advancing to ${nextPhaseDef.name}`));
267
- process.exit(1);
268
- }
269
-
270
- if (validation.stateWarning) {
271
- console.log(chalk.yellow(`\n⚠️ ${validation.stateWarning}`));
272
- }
273
-
274
- // === GATE 4: Content Validation ===
275
- // Validate spec.md and contracts.cs when advancing from design phase
276
- if (currentPhase === 'design' && nextPhase === 'plan') {
277
- // Check spec.md structure and content (filesystem-based)
278
- const specAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'spec');
279
- if (existsSync(specAbsPath)) {
280
- const specContentValidation = validateSpecContent(specAbsPath);
281
-
282
- if (!specContentValidation.valid) {
283
- console.log(chalk.red('\n✗ Spec content validation failed:'));
284
- specContentValidation.errors.forEach(error => {
285
- console.log(chalk.red(` - ${error}`));
286
- });
287
- console.log('');
288
- process.exit(1);
289
- }
290
-
291
- if (specContentValidation.warnings?.length > 0 && !options.skipWarnings) {
292
- console.log(chalk.yellow('\n⚠️ Spec content warnings:'));
293
- specContentValidation.warnings.forEach(warning => {
294
- console.log(chalk.yellow(` - ${warning}`));
295
- });
296
- }
297
- }
298
-
299
- // Run existing spec validator (anti-patterns, IaC checks)
300
- const specValidation = await validateSpec('.', feature);
301
-
302
- if (specValidation.errors > 0) {
303
- console.log(chalk.red('\n✗ Spec validation failed — fix errors before advancing:'));
304
- specValidation.issues.filter(i => i.level === 'error').forEach(issue => {
305
- console.log(chalk.red(` - ${issue.message}`));
306
- console.log(chalk.yellow(` → ${issue.solution}`));
307
- });
308
- console.log('');
309
- process.exit(1);
310
- }
311
-
312
- if (specValidation.warnings > 0 && !options.skipWarnings) {
313
- console.log(chalk.yellow('\n⚠️ Spec validation warnings:'));
314
- specValidation.issues.filter(i => i.level === 'warning').forEach(issue => {
315
- console.log(chalk.yellow(` - ${issue.message}`));
316
- });
317
- console.log(chalk.gray('\n (Use --skip-warnings to ignore warnings)\n'));
318
- }
319
- }
320
-
321
- // === GATE 5: Tasks Content Validation ===
322
- // Validate tasks.md structure when advancing to implement
323
- if (currentPhase === 'tasks' && nextPhase === 'implement') {
324
- const tasksAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'tasks');
325
- if (existsSync(tasksAbsPath)) {
326
- const tasksContentValidation = validateTasksContent(tasksAbsPath);
327
-
328
- if (!tasksContentValidation.valid) {
329
- console.log(chalk.red('\n✗ Tasks content validation failed:'));
330
- tasksContentValidation.errors.forEach(error => {
331
- console.log(chalk.red(` - ${error}`));
332
- });
333
- console.log('');
334
- process.exit(1);
335
- }
336
-
337
- if (tasksContentValidation.warnings?.length > 0 && !options.skipWarnings) {
338
- console.log(chalk.yellow('\n⚠️ Tasks content warnings:'));
339
- tasksContentValidation.warnings.forEach(warning => {
340
- console.log(chalk.yellow(` - ${warning}`));
341
- });
342
- }
343
-
344
- // Show tasks stats
345
- console.log(chalk.green(`\n✓ Tasks validated: ${tasksContentValidation.stats.totalTasks} total (${tasksContentValidation.stats.regularTasks} tasks + ${tasksContentValidation.stats.checkpoints} checkpoints)`));
346
- }
347
- }
348
-
349
- // Gate: Check design system when advancing to implement with UI agents
350
- if (nextPhase === 'implement') {
351
- const gateResult = designSystemGate(feature, process.cwd());
352
-
353
- if (gateResult.blocked) {
354
- console.log(chalk.red(`\n✗ ${gateResult.message}`));
355
- console.log('');
356
- gateResult.solution.forEach(line => {
357
- if (line === '') {
358
- console.log('');
359
- } else {
360
- console.log(chalk.yellow(` ${line}`));
361
- }
362
- });
363
- console.log('');
364
- process.exit(1);
365
- }
366
- }
367
-
368
- // Advance the phase
369
- const state = loadState();
370
- state.features[feature].phase = nextPhase;
371
- state.features[feature].updatedAt = new Date().toISOString();
372
-
373
- // Create the target phase directory so derivePhase() reflects the new phase
374
- const PHASE_DIR_MAP = {
375
- 'proposal': '0-proposal', 'design': '2-design', 'uiux': '1-ui',
376
- 'plan': '3-plan', 'tasks': '4-tasks', 'implement': '5-implement',
377
- };
378
- const targetDir = PHASE_DIR_MAP[nextPhase];
379
- if (targetDir) {
380
- const dirPath = join(featureFolderPath, targetDir);
381
- if (!existsSync(dirPath)) {
382
- mkdirSync(dirPath, { recursive: true });
383
- }
384
- }
385
- if (skippedPhases.length > 0) {
386
- const existingSkipped = state.features[feature].skippedPhases || [];
387
- state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
388
- }
389
-
390
- // Ensure approvalGates object exists
391
- if (!state.features[feature].approvalGates) {
392
- state.features[feature].approvalGates = {};
393
- }
394
-
395
- const now = new Date().toISOString();
396
- const gates = state.features[feature].approvalGates;
397
-
398
- // Auto-approve proposal gate when advancing past the proposal phase.
399
- // The act of advancing is itself the approval — no explicit gate check needed.
400
- if (currentPhase === 'proposal' && !gates.proposal?.approved) {
401
- gates.proposal = { approved: true, timestamp: now, approvedBy: 'workflow-advance' };
402
- }
403
-
404
- // Persist gates approved implicitly (trust auto-approve or --skip-approval bypass).
405
- if (gateApprovedImplicitly && !gates[gateApprovedImplicitly.gate]?.approved) {
406
- gates[gateApprovedImplicitly.gate] = {
407
- approved: true,
408
- timestamp: now,
409
- approvedBy: gateApprovedImplicitly.approvedBy
410
- };
411
- }
412
-
413
- // Auto-approve gates for phases that were legitimately skipped.
414
- // Skipped phases never run their explicit approve step, so their gate would
415
- // otherwise stay false in state — making approval-status misleading.
416
- for (const skippedPhase of skippedPhases) {
417
- const skippedGate = APPROVAL_GATE_MAP[skippedPhase];
418
- if (skippedGate && !gates[skippedGate]?.approved) {
419
- gates[skippedGate] = { approved: true, timestamp: now, approvedBy: 'phase-skipped' };
420
- }
421
- }
422
-
423
- saveState(state);
424
-
425
- // Run PhaseAdvanced agent-teams hook (non-blocking)
426
- try {
427
- const { executeHook, formatHookResults } = await import('../../lib/hooks/hook-executor.js');
428
- const hookResult = await executeHook(process.cwd(), feature, 'PhaseAdvanced', {
429
- fromPhase: currentPhase,
430
- toPhase: nextPhase
431
- });
432
- if (!hookResult.passed && hookResult.errors.length > 0) {
433
- const output = formatHookResults(hookResult, 'PhaseAdvanced');
434
- console.log(output);
435
- }
436
- } catch {
437
- // Hook executor unavailable — non-blocking
438
- }
439
-
440
- // Agent detection refresh (fail-open)
441
- try {
442
- const { refreshAgents, displayDetectionDiff } = await import('../agents/detect-agents.js');
443
- const refreshResult = await refreshAgents(process.cwd(), feature);
444
- if (refreshResult.added.length > 0) {
445
- displayDetectionDiff(refreshResult, currentPhase, nextPhase);
446
- }
447
- } catch {
448
- // Refresh is diagnostic — fail-open, phase advance already completed
449
- }
450
-
451
- // Emit Tier-4 validator dispatch when advancing into implement or sync phase
452
- const phasesNeedingValidation = ['implement', 'sync'];
453
- if (phasesNeedingValidation.includes(nextPhase) || phasesNeedingValidation.includes(currentPhase)) {
454
- const validationPhase = phasesNeedingValidation.includes(nextPhase) ? nextPhase : currentPhase;
455
- await emitValidatorDispatch(feature, validationPhase, process.cwd());
456
- }
457
-
458
- // Emit SKILL DISPATCH for any phase that has requiredSkills
459
- await emitSkillDispatch(nextPhase, process.cwd());
460
-
461
- console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
462
-
463
- // Show what's needed in the new phase
464
- console.log(chalk.cyan('\n📋 Phase requirements:'));
465
- console.log(chalk.gray(` ${nextPhaseDef.description}`));
466
-
467
- if (nextPhaseDef.requiredOutputs?.length > 0) {
468
- console.log(chalk.cyan('\n Required outputs:'));
469
- nextPhaseDef.requiredOutputs.forEach(output => {
470
- const exists = validation.phase ? true : false; // Already validated
471
- console.log(chalk.gray(` ✓ ${output} (exists)`));
472
- });
473
- }
474
-
475
- // Show phase-specific guidance
476
- const guidance = getPhaseGuidance(nextPhase, feature);
477
- if (guidance) {
478
- console.log(chalk.cyan('\n Next steps:'));
479
- guidance.forEach(step => console.log(chalk.white(` → ${step}`)));
480
- }
481
-
482
- console.log('');
483
- }
484
-
485
- /**
486
- * Get guidance for what to do in a phase
487
- */
488
- function getPhaseGuidance(phase, feature) {
489
- const guides = {
490
- 'uiux': [
491
- `Resume spec pipeline: /morph-proposal ${feature}`,
492
- 'Provide layout references and preferences',
493
- 'Review wireframes before proceeding'
494
- ],
495
- 'design': (() => {
496
- const { isDotnet } = getStackProfile();
497
- return [
498
- `Resume spec pipeline: /morph-proposal ${feature}`,
499
- 'Review DECISION POINTS carefully',
500
- isDotnet ? 'Approve spec.md and contracts.cs' : 'Approve spec.md and contracts.ts (TypeScript + Zod schemas)'
501
- ];
502
- })(),
503
- 'plan': [
504
- `Resume spec pipeline: /morph-proposal ${feature}`,
505
- 'Review implementation plan for completeness',
506
- 'Approve plan before generating task breakdown'
507
- ],
508
- 'tasks': [
509
- `Resume spec pipeline: /morph-proposal ${feature}`,
510
- 'Review task order and dependencies',
511
- 'Approve task list before implementing'
512
- ],
513
- 'implement': [
514
- `Start implementing: /morph-apply ${feature}`,
515
- 'Complete tasks one by one with: morph-spec task done',
516
- 'Validators run automatically on task completion'
517
- ],
518
- 'sync': [
519
- 'Review decisions.md for standards to promote',
520
- `Decisions file: ${getOutputPath(feature, 'decisions')}`
521
- ]
522
- };
523
-
524
- return guides[phase] || null;
525
- }
526
-
527
- /**
528
- * Gate: Check design system exists when advancing to implement with UI agents
529
- * @param {string} feature - Feature name
530
- * @param {string} projectPath - Project root path
531
- * @returns {Object} { blocked: boolean, message?: string, solution?: string[] }
532
- */
533
- function designSystemGate(feature, projectPath = '.') {
534
- // Check if UI agents are active
535
- const hasUIAgents = hasUIAgentsActive(projectPath, feature);
536
-
537
- if (!hasUIAgents) {
538
- return { blocked: false }; // No UI agents, gate doesn't apply
539
- }
540
-
541
- // Check if design system exists
542
- const dsDetection = detectDesignSystem(projectPath, feature);
543
-
544
- if (dsDetection.hasDesignSystem) {
545
- return { blocked: false }; // Design system exists, gate passed
546
- }
547
-
548
- // Block advancement - no design system found with UI agents active
549
- return {
550
- blocked: true,
551
- message: 'Cannot advance to implementation — UI agents are active but no design system found',
552
- solution: [
553
- 'Design system is required when UI agents (ui-designer, css-specialist) are active',
554
- '',
555
- 'Create a design system with one of these options:',
556
- ' 1. Project-level: .morph/context/design-system.md (shared across features)',
557
- ` 2. Feature-level: ${getOutputPath(feature, 'uiDesignSystem')} (feature-specific)`,
558
- ' 3. Run /morph-phase-uiux skill to auto-generate from existing codebase',
559
- '',
560
- 'Or remove UI agents if they are not needed:',
561
- ` morph-spec state remove-agent ${feature} css-specialist`
562
- ]
563
- };
564
- }
565
-
566
- /**
567
- * Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
568
- * Outputs structured JSON for the LLM to read and follow.
569
- * Non-blocking — fails silently.
570
- *
571
- * @param {string} nextPhase - The phase being advanced into
572
- * @param {string} cwd - Project root path (for locating phases.json)
573
- */
574
- async function emitSkillDispatch(nextPhase, cwd) {
575
- try {
576
- // Try project-local phases.json first, fall back to package-level
577
- const localPath = join(cwd, 'framework', 'phases.json');
578
- const packagePath = join(__dirname, '../../../framework/phases.json');
579
- const resolvedPath = existsSync(localPath) ? localPath : packagePath;
580
-
581
- if (!existsSync(resolvedPath)) return;
582
-
583
- const phasesData = JSON.parse(readFileSync(resolvedPath, 'utf8'));
584
- const skills = phasesData?.phases?.[nextPhase]?.requiredSkills;
585
- if (!skills || skills.length === 0) return;
586
-
587
- const dispatch = {
588
- skillDispatch: true,
589
- phase: nextPhase,
590
- skills,
591
- instruction: `These skills are MANDATORY for phase '${nextPhase}'. Invoke them at the specified trigger points using Skill(). Do NOT skip them.`,
592
- };
593
-
594
- console.log(chalk.cyan('\n--- SKILL DISPATCH ---'));
595
- console.log(JSON.stringify(dispatch, null, 2));
596
- console.log(chalk.cyan('--- END SKILL DISPATCH ---\n'));
597
- } catch {
598
- // Non-blocking — fail silently
599
- }
600
- }