@polymorphism-tech/morph-spec 4.8.19 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +44 -55
  4. package/bin/task-manager.js +133 -20
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +99 -77
  10. package/framework/agents.json +734 -182
  11. package/framework/commands/commit.md +166 -0
  12. package/framework/commands/morph-apply.md +13 -2
  13. package/framework/commands/morph-archive.md +8 -2
  14. package/framework/commands/morph-infra.md +6 -0
  15. package/framework/commands/morph-preflight.md +6 -0
  16. package/framework/commands/morph-proposal.md +56 -7
  17. package/framework/commands/morph-status.md +6 -0
  18. package/framework/commands/morph-troubleshoot.md +6 -0
  19. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  20. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  21. package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
  22. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
  23. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  24. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  25. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  26. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
  27. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  28. package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
  29. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  30. package/framework/hooks/claude-code/statusline.py +76 -30
  31. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  32. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  33. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  34. package/framework/hooks/shared/activity-logger.js +0 -24
  35. package/framework/hooks/shared/compact-restore.js +100 -0
  36. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  37. package/framework/hooks/shared/phase-utils.js +12 -5
  38. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  39. package/framework/hooks/shared/stale-task-reset.js +57 -0
  40. package/framework/hooks/shared/state-reader.js +29 -5
  41. package/framework/hooks/shared/worktree-helpers.js +53 -0
  42. package/framework/phases.json +69 -14
  43. package/framework/rules/morph-workflow.md +88 -86
  44. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  45. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
  46. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  47. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
  48. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
  49. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
  50. package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
  51. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
  52. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
  53. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  54. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
  55. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
  56. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
  57. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
  58. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
  59. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  60. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  61. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
  62. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  63. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
  64. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  65. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  66. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  67. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  68. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
  69. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
  70. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  71. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
  72. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  73. package/framework/standards/STANDARDS.json +640 -88
  74. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  76. package/framework/templates/REGISTRY.json +1825 -1909
  77. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  78. package/framework/templates/docs/onboarding.md +3 -7
  79. package/package.json +2 -7
  80. package/src/commands/agents/dispatch-agents.js +104 -6
  81. package/src/commands/mcp/mcp-setup.js +39 -2
  82. package/src/commands/phase/phase-reset.js +74 -0
  83. package/src/commands/project/doctor.js +34 -51
  84. package/src/commands/project/init.js +1 -1
  85. package/src/commands/project/status.js +2 -2
  86. package/src/commands/project/update.js +381 -365
  87. package/src/commands/project/worktree.js +154 -0
  88. package/src/commands/scope/escalate.js +215 -0
  89. package/src/commands/state/advance-phase.js +132 -68
  90. package/src/commands/state/approve.js +2 -2
  91. package/src/commands/state/index.js +7 -8
  92. package/src/commands/state/phase-runner.js +1 -1
  93. package/src/commands/state/state.js +61 -6
  94. package/src/commands/task/expand.js +100 -0
  95. package/src/commands/tasks/task.js +78 -99
  96. package/src/commands/templates/template-render.js +93 -173
  97. package/src/commands/trust/trust.js +26 -21
  98. package/src/core/paths/output-schema.js +19 -3
  99. package/src/core/state/phase-state-machine.js +7 -4
  100. package/src/core/state/state-manager.js +32 -57
  101. package/src/core/workflows/workflow-detector.js +9 -87
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +336 -0
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack/stack-profile.js +88 -0
  110. package/src/lib/tasks/task-classifier.js +16 -0
  111. package/src/lib/tasks/task-parser.js +1 -1
  112. package/src/lib/tasks/test-runner.js +77 -0
  113. package/src/lib/trust/trust-manager.js +32 -144
  114. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  115. package/src/lib/validators/spec-validator.js +58 -4
  116. package/src/lib/validators/validation-runner.js +23 -11
  117. package/src/scripts/setup-infra.js +255 -224
  118. package/src/utils/agents-installer.js +34 -14
  119. package/src/utils/banner.js +1 -1
  120. package/src/utils/claude-settings-manager.js +1 -1
  121. package/src/utils/file-copier.js +1 -1
  122. package/src/utils/hooks-installer.js +272 -8
  123. package/framework/hooks/dev/check-sync-health.js +0 -117
  124. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  125. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  126. package/framework/hooks/dev/sync-template-registry.js +0 -60
  127. package/framework/hooks/dev/validate-skill-format.js +0 -70
  128. package/framework/hooks/dev/validate-standard-format.js +0 -73
  129. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
  130. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
  131. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  132. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  133. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  134. package/framework/workflows/configs/design-impl.json +0 -49
  135. package/framework/workflows/configs/express.json +0 -45
  136. package/framework/workflows/configs/fast-track.json +0 -42
  137. package/framework/workflows/configs/full-morph.json +0 -79
  138. package/framework/workflows/configs/fusion.json +0 -39
  139. package/framework/workflows/configs/long-running.json +0 -33
  140. package/framework/workflows/configs/spec-only.json +0 -43
  141. package/framework/workflows/configs/ui-refresh.json +0 -49
  142. package/framework/workflows/configs/zero-touch.json +0 -82
  143. package/src/commands/project/index.js +0 -8
  144. package/src/commands/project/monitor.js +0 -295
  145. package/src/commands/project/tutorial.js +0 -115
  146. package/src/commands/state/validate-phase.js +0 -238
  147. package/src/commands/templates/generate-contracts.js +0 -445
  148. package/src/core/index.js +0 -10
  149. package/src/core/orchestrator.js +0 -171
  150. package/src/core/registry/command-registry.js +0 -28
  151. package/src/core/registry/index.js +0 -8
  152. package/src/core/registry/validator-registry.js +0 -204
  153. package/src/core/state/index.js +0 -8
  154. package/src/core/templates/index.js +0 -9
  155. package/src/core/templates/template-data-sources.js +0 -325
  156. package/src/core/templates/template-validator.js +0 -296
  157. package/src/core/workflows/index.js +0 -7
  158. package/src/generator/config-generator.js +0 -206
  159. package/src/generator/templates/config.json.template +0 -40
  160. package/src/generator/templates/project.md.template +0 -67
  161. package/src/lib/agents/micro-agent-factory.js +0 -161
  162. package/src/lib/analysis/complexity-analyzer.js +0 -441
  163. package/src/lib/analysis/index.js +0 -7
  164. package/src/lib/analytics/analytics-engine.js +0 -345
  165. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  166. package/src/lib/checkpoints/index.js +0 -7
  167. package/src/lib/context/context-bundler.js +0 -241
  168. package/src/lib/context/context-optimizer.js +0 -212
  169. package/src/lib/context/context-tracker.js +0 -273
  170. package/src/lib/context/core-four-tracker.js +0 -201
  171. package/src/lib/context/mcp-optimizer.js +0 -200
  172. package/src/lib/detectors/config-detector.js +0 -223
  173. package/src/lib/detectors/standards-generator.js +0 -335
  174. package/src/lib/detectors/structure-detector.js +0 -275
  175. package/src/lib/execution/fusion-executor.js +0 -304
  176. package/src/lib/execution/parallel-executor.js +0 -270
  177. package/src/lib/hooks/stop-hook-executor.js +0 -286
  178. package/src/lib/hops/hop-composer.js +0 -221
  179. package/src/lib/monitor/agent-resolver.js +0 -144
  180. package/src/lib/monitor/renderer.js +0 -230
  181. package/src/lib/orchestration/index.js +0 -7
  182. package/src/lib/orchestration/team-orchestrator.js +0 -404
  183. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  184. package/src/lib/threads/thread-coordinator.js +0 -238
  185. package/src/lib/threads/thread-manager.js +0 -317
  186. package/src/lib/tracking/artifact-trail.js +0 -202
  187. package/src/sanitizer/context-sanitizer.js +0 -221
  188. package/src/sanitizer/patterns.js +0 -163
  189. package/src/scanner/project-scanner.js +0 -242
  190. package/src/ui/diff-display.js +0 -91
  191. package/src/ui/interactive-wizard.js +0 -96
  192. package/src/ui/user-review.js +0 -211
  193. package/src/ui/wizard-questions.js +0 -188
  194. package/src/utils/color-utils.js +0 -70
  195. package/src/utils/process-handler.js +0 -97
  196. package/src/writer/file-writer.js +0 -86
  197. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  198. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  199. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  200. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  201. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  202. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  203. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  204. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  205. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  206. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  207. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  208. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  209. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  210. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  211. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  212. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  213. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  214. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -11,22 +11,36 @@
11
11
 
12
12
  import chalk from 'chalk';
13
13
  import { loadState, saveState, getFeature, getApprovalGate, derivePhase } from '../../core/state/state-manager.js';
14
- import { PHASES, validatePhase } from './validate-phase.js';
14
+ import { PHASES, validatePhase } from '../../lib/phase-chain/phase-validator.js';
15
15
  import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
16
- import { getOutputPath } from '../../core/paths/output-schema.js';
17
- import { shouldAutoApprove, autoCalculateTrust } from '../../lib/trust/trust-manager.js';
16
+ import { getOutputPath, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
17
+ import { shouldAutoApprove } from '../../lib/trust/trust-manager.js';
18
18
  import { validateSpec } from '../../lib/validators/spec-validator.js';
19
- import { validateTransition, getPhaseDisplayName } from '../../core/state/phase-state-machine.js';
20
- import { validateSpecContent, validateTasksContent, validateFeatureOutputs } from '../../lib/validators/content/content-validator.js';
19
+ import { validateTransition } from '../../core/state/phase-state-machine.js';
20
+ import { validateSpecContent, validateTasksContent } from '../../lib/validators/content/content-validator.js';
21
21
  import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
22
+ import { getStackProfile } from '../../lib/stack/stack-profile.js';
22
23
  import { readFileSync, existsSync } from 'fs';
23
24
  import { join, dirname } from 'path';
24
25
  import { fileURLToPath } from 'url';
26
+ import { emitValidatorDispatch } from '../../lib/validators/shared/emit-validator-dispatch.js';
25
27
 
26
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
27
29
 
28
30
  // Phase order for advancing (skips optional phases unless active)
29
- const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
31
+ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', '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
+ };
30
44
 
31
45
  /**
32
46
  * Get the next phase after the current one.
@@ -66,13 +80,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
66
80
 
67
81
  // Check if this phase is part of a combined phase
68
82
  if (workflowConfig.phases.combined) {
83
+ let isCombined = false;
69
84
  for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
70
85
  if (phases.includes(candidate) && candidate !== combinedName) {
71
86
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
72
87
  skippedPhases.push(candidate);
73
- continue;
88
+ isCombined = true;
89
+ break;
74
90
  }
75
91
  }
92
+ if (isCombined) continue;
76
93
  }
77
94
 
78
95
  // Check if workflow only runs specific phases
@@ -119,14 +136,11 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
119
136
  }
120
137
  }
121
138
 
122
- // Skip sync for simple workflows
123
- if (candidate === 'sync') {
124
- const isSimple = feature.workflow === 'fast-track';
125
- if (isSimple && skipOptional !== false) {
126
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: fast-track workflow`));
127
- skippedPhases.push(candidate);
128
- continue;
129
- }
139
+ // sync is optional — only skip if --skip-optional is set
140
+ if (candidate === 'sync' && skipOptional) {
141
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: optional phase`));
142
+ skippedPhases.push(candidate);
143
+ continue;
130
144
  }
131
145
  }
132
146
 
@@ -189,13 +203,15 @@ export async function advancePhaseCommand(feature, options = {}) {
189
203
 
190
204
  // === GATE 2: Approval Gate Check ===
191
205
  // Check if current phase requires approval before advancing
192
- const approvalGateMap = {
193
- 'design': 'design',
194
- 'tasks': 'tasks',
195
- 'uiux': 'uiux'
196
- };
197
-
198
- const requiredGate = approvalGateMap[currentPhase];
206
+ // Track gates that get approved implicitly so we can persist them after state load.
207
+ let gateApprovedImplicitly = null; // { gate, approvedBy, reason }
208
+
209
+ const requiredGate = APPROVAL_GATE_MAP[currentPhase];
210
+ if (requiredGate && options.skipApproval) {
211
+ console.log(chalk.yellow(`\n⚠ --skip-approval: Approval gate "${requiredGate}" bypassed for phase "${currentPhase}"`));
212
+ console.log(chalk.gray(' Intended for CI/automation only. Run without --skip-approval for normal workflow.\n'));
213
+ gateApprovedImplicitly = { gate: requiredGate, approvedBy: 'skip-approval' };
214
+ }
199
215
  if (requiredGate && !options.skipApproval) {
200
216
  const gateStatus = getApprovalGate(feature, requiredGate);
201
217
 
@@ -206,6 +222,7 @@ export async function advancePhaseCommand(feature, options = {}) {
206
222
  if (trustResult.autoApprove) {
207
223
  console.log(chalk.green(`\n✓ Auto-approved gate "${requiredGate}" (${trustResult.reason})`));
208
224
  // Proceed — trust bypasses manual gate requirement
225
+ gateApprovedImplicitly = { gate: requiredGate, approvedBy: `trust:${trustResult.level}` };
209
226
  } else {
210
227
  console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
211
228
  console.log(chalk.gray(` Trust level: ${trustResult.level} (${trustResult.reason})`));
@@ -245,9 +262,10 @@ export async function advancePhaseCommand(feature, options = {}) {
245
262
  // === GATE 4: Content Validation ===
246
263
  // Validate spec.md and contracts.cs when advancing from design phase
247
264
  if (currentPhase === 'design' && (nextPhase === 'clarify' || nextPhase === 'tasks')) {
248
- // Check spec.md structure and content
249
- if (featureData.outputs?.spec?.created) {
250
- const specContentValidation = validateSpecContent(featureData.outputs.spec.path);
265
+ // Check spec.md structure and content (filesystem-based)
266
+ const specAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'spec');
267
+ if (existsSync(specAbsPath)) {
268
+ const specContentValidation = validateSpecContent(specAbsPath);
251
269
 
252
270
  if (!specContentValidation.valid) {
253
271
  console.log(chalk.red('\n✗ Spec content validation failed:'));
@@ -294,8 +312,9 @@ export async function advancePhaseCommand(feature, options = {}) {
294
312
  // === GATE 5: Tasks Content Validation ===
295
313
  // Validate tasks.md structure when advancing to implement
296
314
  if (currentPhase === 'tasks' && nextPhase === 'implement') {
297
- if (featureData.outputs?.tasks?.created) {
298
- const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
315
+ const tasksAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'tasks');
316
+ if (existsSync(tasksAbsPath)) {
317
+ const tasksContentValidation = validateTasksContent(tasksAbsPath);
299
318
 
300
319
  if (!tasksContentValidation.valid) {
301
320
  console.log(chalk.red('\n✗ Tasks content validation failed:'));
@@ -318,6 +337,19 @@ export async function advancePhaseCommand(feature, options = {}) {
318
337
  }
319
338
  }
320
339
 
340
+ // === GATE 6: Clarifications Required ===
341
+ // When advancing FROM clarify, clarifications.md must exist.
342
+ // This enforces that morph:phase-clarify was actually run (not just skipped).
343
+ if (currentPhase === 'clarify') {
344
+ const clarPath = join(process.cwd(), '.morph', 'features', feature, '1-design', 'clarifications.md');
345
+ if (!existsSync(clarPath)) {
346
+ console.log(chalk.red('\n✗ Cannot advance from clarify — clarifications.md is missing'));
347
+ console.log(chalk.yellow(' Run the phase-clarify skill first: /morph:phase-clarify'));
348
+ console.log(chalk.gray(' Output expected at: .morph/features/' + feature + '/1-design/clarifications.md\n'));
349
+ process.exit(1);
350
+ }
351
+ }
352
+
321
353
  // Gate: Check design system when advancing to implement with UI agents
322
354
  if (nextPhase === 'implement') {
323
355
  const gateResult = designSystemGate(feature, process.cwd());
@@ -345,6 +377,40 @@ export async function advancePhaseCommand(feature, options = {}) {
345
377
  const existingSkipped = state.features[feature].skippedPhases || [];
346
378
  state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
347
379
  }
380
+
381
+ // Ensure approvalGates object exists
382
+ if (!state.features[feature].approvalGates) {
383
+ state.features[feature].approvalGates = {};
384
+ }
385
+
386
+ const now = new Date().toISOString();
387
+ const gates = state.features[feature].approvalGates;
388
+
389
+ // Auto-approve proposal gate when advancing past the proposal phase.
390
+ // The act of advancing is itself the approval — no explicit gate check needed.
391
+ if (currentPhase === 'proposal' && !gates.proposal?.approved) {
392
+ gates.proposal = { approved: true, timestamp: now, approvedBy: 'workflow-advance' };
393
+ }
394
+
395
+ // Persist gates approved implicitly (trust auto-approve or --skip-approval bypass).
396
+ if (gateApprovedImplicitly && !gates[gateApprovedImplicitly.gate]?.approved) {
397
+ gates[gateApprovedImplicitly.gate] = {
398
+ approved: true,
399
+ timestamp: now,
400
+ approvedBy: gateApprovedImplicitly.approvedBy
401
+ };
402
+ }
403
+
404
+ // Auto-approve gates for phases that were legitimately skipped.
405
+ // Skipped phases never run their explicit approve step, so their gate would
406
+ // otherwise stay false in state — making approval-status misleading.
407
+ for (const skippedPhase of skippedPhases) {
408
+ const skippedGate = APPROVAL_GATE_MAP[skippedPhase];
409
+ if (skippedGate && !gates[skippedGate]?.approved) {
410
+ gates[skippedGate] = { approved: true, timestamp: now, approvedBy: 'phase-skipped' };
411
+ }
412
+ }
413
+
348
414
  saveState(state);
349
415
 
350
416
  // Run PhaseAdvanced agent-teams hook (non-blocking)
@@ -369,6 +435,9 @@ export async function advancePhaseCommand(feature, options = {}) {
369
435
  await emitValidatorDispatch(feature, validationPhase, process.cwd());
370
436
  }
371
437
 
438
+ // Emit SKILL DISPATCH for any phase that has requiredSkills
439
+ await emitSkillDispatch(nextPhase, process.cwd());
440
+
372
441
  console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
373
442
 
374
443
  // Show what's needed in the new phase
@@ -407,15 +476,23 @@ function getPhaseGuidance(phase, feature) {
407
476
  'Provide layout references and preferences',
408
477
  'Review wireframes before proceeding'
409
478
  ],
410
- 'design': [
411
- `Resume spec pipeline: /morph-proposal ${feature}`,
412
- 'Review DECISION POINTS carefully',
413
- 'Approve spec.md and contracts.cs'
414
- ],
479
+ 'design': (() => {
480
+ const { isDotnet } = getStackProfile();
481
+ return [
482
+ `Resume spec pipeline: /morph-proposal ${feature}`,
483
+ 'Review DECISION POINTS carefully',
484
+ isDotnet ? 'Approve spec.md and contracts.cs' : 'Approve spec.md and contracts.ts (TypeScript + Zod schemas)'
485
+ ];
486
+ })(),
415
487
  'clarify': [
416
488
  `Resume spec pipeline: /morph-proposal ${feature}`,
417
489
  'Resolve edge cases and unknowns'
418
490
  ],
491
+ 'plan': [
492
+ `Resume spec pipeline: /morph-proposal ${feature}`,
493
+ 'Review implementation plan for completeness',
494
+ 'Approve plan before generating task breakdown'
495
+ ],
419
496
  'tasks': [
420
497
  `Resume spec pipeline: /morph-proposal ${feature}`,
421
498
  'Review task order and dependencies',
@@ -428,7 +505,7 @@ function getPhaseGuidance(phase, feature) {
428
505
  ],
429
506
  'sync': [
430
507
  'Review decisions.md for standards to promote',
431
- `Sync: morph-spec sync --path ${getOutputPath(feature, 'decisions')}`
508
+ `Decisions file: ${getOutputPath(feature, 'decisions')}`
432
509
  ]
433
510
  };
434
511
 
@@ -466,7 +543,7 @@ function designSystemGate(feature, projectPath = '.') {
466
543
  'Create a design system with one of these options:',
467
544
  ' 1. Project-level: .morph/context/design-system.md (shared across features)',
468
545
  ` 2. Feature-level: ${getOutputPath(feature, 'uiDesignSystem')} (feature-specific)`,
469
- ' 3. Auto-generate: morph-spec generate design-system (scans existing CSS)',
546
+ ' 3. Run /morph:phase-uiux skill to auto-generate from existing codebase',
470
547
  '',
471
548
  'Or remove UI agents if they are not needed:',
472
549
  ` morph-spec state remove-agent ${feature} blazor-builder`
@@ -475,49 +552,36 @@ function designSystemGate(feature, projectPath = '.') {
475
552
  }
476
553
 
477
554
  /**
478
- * Emit Tier-4 validation dispatch block after phase advance.
479
- * Outputs a JSON block listing active Tier-4 validators for the LLM to dispatch
480
- * as read-only subagents before proceeding to implementation.
555
+ * Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
556
+ * Outputs structured JSON for the LLM to read and follow.
481
557
  * Non-blocking — fails silently.
482
558
  *
483
- * @param {string} featureName
484
- * @param {string} phase
485
- * @param {string} cwd
559
+ * @param {string} nextPhase - The phase being advanced into
560
+ * @param {string} cwd - Project root path (for locating phases.json)
486
561
  */
487
- async function emitValidatorDispatch(featureName, phase, cwd) {
562
+ async function emitSkillDispatch(nextPhase, cwd) {
488
563
  try {
489
- const { buildDispatchConfig } = await import('../agents/dispatch-agents.js');
490
- const config = await buildDispatchConfig(cwd, featureName, phase, { mode: 'validate' });
491
- const validators = config.agents?.filter(a => a.tier === 4);
492
- if (!validators || validators.length === 0) return;
493
-
494
- const agentsPath = join(__dirname, '../../../framework/agents.json');
495
- const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
496
- const allAgents = agentsData.agents || {};
497
-
498
- const validatorEntries = validators.map(v => {
499
- const agentData = allAgents[v.id];
500
- return {
501
- id: v.id,
502
- title: v.title,
503
- severity: agentData?.hook_behavior?.severity || 'error',
504
- blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
505
- checks: agentData?.hook_behavior?.validates || [],
506
- taskPrompt: v.taskPrompt
507
- };
508
- });
564
+ // Try project-local phases.json first, fall back to package-level
565
+ const localPath = join(cwd, 'framework', 'phases.json');
566
+ const packagePath = join(__dirname, '../../../framework/phases.json');
567
+ const resolvedPath = existsSync(localPath) ? localPath : packagePath;
568
+
569
+ if (!existsSync(resolvedPath)) return;
570
+
571
+ const phasesData = JSON.parse(readFileSync(resolvedPath, 'utf8'));
572
+ const skills = phasesData?.phases?.[nextPhase]?.requiredSkills;
573
+ if (!skills || skills.length === 0) return;
509
574
 
510
575
  const dispatch = {
511
- validationRequired: true,
512
- phase,
513
- feature: featureName,
514
- validators: validatorEntries,
515
- instruction: 'Dispatch these validators as READ-ONLY subagents before continuing. Each must output { "passed": boolean, "issues": [] }. Blocking validators (blocksOnFail: true) must pass before marking phase complete.'
576
+ skillDispatch: true,
577
+ phase: nextPhase,
578
+ skills,
579
+ instruction: `These skills are MANDATORY for phase '${nextPhase}'. Invoke them at the specified trigger points using Skill(). Do NOT skip them.`,
516
580
  };
517
581
 
518
- console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
582
+ console.log(chalk.cyan('\n--- SKILL DISPATCH ---'));
519
583
  console.log(JSON.stringify(dispatch, null, 2));
520
- console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
584
+ console.log(chalk.cyan('--- END SKILL DISPATCH ---\n'));
521
585
  } catch {
522
586
  // Non-blocking — fail silently
523
587
  }
@@ -25,7 +25,7 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
25
25
  return;
26
26
  }
27
27
 
28
- setApprovalGate(featureName, gateName, false, {
28
+ await setApprovalGate(featureName, gateName, false, {
29
29
  revokedBy: options.revoker || process.env.USER || process.env.USERNAME || 'user',
30
30
  revokedAt: new Date().toISOString(),
31
31
  reason: options.reason
@@ -85,7 +85,7 @@ export async function approveCommand(featureName, gateName, options = {}) {
85
85
  }
86
86
 
87
87
  // Set approval
88
- setApprovalGate(featureName, gateName, true, {
88
+ await setApprovalGate(featureName, gateName, true, {
89
89
  approvedBy: options.approver || process.env.USER || process.env.USERNAME || 'user',
90
90
  approvedAt: new Date().toISOString(),
91
91
  notes: options.notes
@@ -1,8 +1,7 @@
1
- /**
2
- * State Management Commands
3
- */
4
- export { stateCommand } from './state.js';
5
- export { advancePhaseCommand } from './advance-phase.js';
6
- export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
7
- export { validatePhaseCommand } from './validate-phase.js';
8
- export { phaseRunCommand } from './phase-runner.js';
1
+ /**
2
+ * State Management Commands
3
+ */
4
+ export { stateCommand } from './state.js';
5
+ export { advancePhaseCommand } from './advance-phase.js';
6
+ export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
7
+ export { phaseRunCommand } from './phase-runner.js';
@@ -23,7 +23,7 @@
23
23
 
24
24
  import chalk from 'chalk';
25
25
  import { join } from 'path';
26
- import { checkPhaseEligibility, computePassRate } from '../../lib/phase-chain/eligibility-checker.js';
26
+ import { checkPhaseEligibility, computePassRate } from '../../lib/phase-chain/phase-validator.js';
27
27
  import { advancePhaseCommand } from './advance-phase.js';
28
28
  import { loadState } from '../../core/state/state-manager.js';
29
29
  import { derivePhase } from '../../core/state/state-manager.js';
@@ -11,6 +11,36 @@ import { logger } from '../../utils/logger.js';
11
11
  import * as StateManager from '../../core/state/state-manager.js';
12
12
  import { derivePhase } from '../../core/state/state-manager.js';
13
13
 
14
+ // ============================================================================
15
+ // Phase Protection
16
+ // ============================================================================
17
+
18
+ /**
19
+ * Phases that have approval gates and cannot be set directly via `state set`.
20
+ * These phases must be reached through `morph-spec phase advance` which enforces
21
+ * all validation gates (approval, output requirements, spec content, etc.).
22
+ *
23
+ * Lightweight phases (proposal, setup, clarify, sync) are not in this list
24
+ * because they have no approval gates and are safe to set directly.
25
+ */
26
+ export const PROTECTED_PHASES = ['design', 'tasks', 'uiux', 'implement'];
27
+
28
+ /**
29
+ * Validate a direct phase-set request.
30
+ *
31
+ * @param {string} phase - The target phase value
32
+ * @returns {{ blocked: boolean, message?: string }}
33
+ */
34
+ export function validatePhaseSet(phase) {
35
+ if (PROTECTED_PHASES.includes(phase)) {
36
+ return {
37
+ blocked: true,
38
+ message: `Phase "${phase}" has approval gates — use "morph-spec phase advance" instead of setting it directly.`
39
+ };
40
+ }
41
+ return { blocked: false };
42
+ }
43
+
14
44
  // ============================================================================
15
45
  // Command Functions
16
46
  // ============================================================================
@@ -174,10 +204,30 @@ async function setCommand(featureName, key, value, options) {
174
204
  }
175
205
 
176
206
  try {
177
- // Warn when bypassing the validated phase advance flow
207
+ // Guard: block protected phases unless --force is explicitly passed
178
208
  if (key === 'phase') {
179
- logger.warn(`Direct phase override — all validation gates will be bypassed.`);
180
- logger.dim(` Recommended: morph-spec phase advance ${featureName}`);
209
+ const phaseValidation = validatePhaseSet(value);
210
+ if (phaseValidation.blocked && !options.force) {
211
+ logger.error(phaseValidation.message);
212
+ logger.blank();
213
+ logger.dim(` Protected phases (have approval gates): ${PROTECTED_PHASES.join(', ')}`);
214
+ logger.dim(` Use the validated advance flow instead:`);
215
+ logger.dim(` morph-spec phase advance ${featureName}`);
216
+ logger.blank();
217
+ logger.dim(` To bypass all gates (expert use only):`);
218
+ logger.dim(` morph-spec state set ${featureName} phase ${value} --force`);
219
+ process.exit(1);
220
+ }
221
+
222
+ if (phaseValidation.blocked) {
223
+ logger.warn(`FORCED phase override to "${value}" — all approval gates bypassed.`);
224
+ } else {
225
+ logger.warn(`Direct phase override — validation gates may be bypassed.`);
226
+ logger.dim(` The actual phase is derived from filesystem subdirectories`);
227
+ logger.dim(` (0-proposal/, 1-design/, 4-tasks/, 5-implement/). This sets a state.json`);
228
+ logger.dim(` override used only for lightweight phases (setup, clarify, sync).`);
229
+ logger.dim(` Recommended: morph-spec phase advance ${featureName}`);
230
+ }
181
231
  logger.blank();
182
232
  }
183
233
 
@@ -328,13 +378,18 @@ async function markOutputCommand(featureName, outputType, options) {
328
378
  logger.dim(' Usage: morph-spec state mark-output <feature> <output-type>');
329
379
  logger.blank();
330
380
  logger.dim(' Valid types:');
331
- logger.dim(' Standard: proposal, spec, contracts, tasks, decisions, recap');
332
- logger.dim(' UI/UX: uiDesignSystem, uiMockups, uiComponents, uiFlows');
381
+ logger.dim(' Proposal: proposal');
382
+ logger.dim(' Design: schemaAnalysis, spec, contracts, contractsVsa, decisions');
383
+ logger.dim(' Clarify: clarifications');
384
+ logger.dim(' Tasks: tasks');
385
+ logger.dim(' UI/UX: uiDesignSystem, uiMockups, uiComponents, uiFlows');
386
+ logger.dim(' Implement: recap');
333
387
  logger.blank();
334
- logger.dim(' Note: UI types also accept kebab-case (e.g., ui-design-system)');
388
+ logger.dim(' Note: kebab-case aliases accepted (e.g., ui-design-system, schema-analysis)');
335
389
  logger.blank();
336
390
  logger.dim(' Examples:');
337
391
  logger.dim(' morph-spec state mark-output my-feature spec');
392
+ logger.dim(' morph-spec state mark-output my-feature schemaAnalysis');
338
393
  logger.dim(' morph-spec state mark-output my-feature uiDesignSystem');
339
394
  logger.dim(' morph-spec state mark-output my-feature ui-design-system');
340
395
  process.exit(1);
@@ -0,0 +1,100 @@
1
+ /**
2
+ * MORPH-SPEC Task Expand Command
3
+ *
4
+ * Expands a task into sub-tasks for low-impact scope escalation.
5
+ * The original task gets status "expanded" and sub-tasks are appended to taskList.
6
+ *
7
+ * Usage:
8
+ * morph-spec task expand <feature> <task-id> --into "T017a: Title" "T017b: Title"
9
+ */
10
+
11
+ import chalk from 'chalk';
12
+ import { loadState, saveState } from '../../core/state/state-manager.js';
13
+
14
+ /**
15
+ * Parse a sub-task string "T017a: Title" into { id, title }.
16
+ */
17
+ function parseSubTask(str) {
18
+ const match = str.match(/^(T\w+)\s*[:\-\u2014]\s*(.+)$/);
19
+ if (match) return { id: match[1], title: match[2].trim() };
20
+ // Fallback: use entire string as title, no id parseable
21
+ return { id: str.split(/\s/)[0], title: str };
22
+ }
23
+
24
+ /**
25
+ * Core expansion logic — exported for testing.
26
+ *
27
+ * @param {string} featureName
28
+ * @param {string} taskId - ID of the task to expand
29
+ * @param {string[]} subTaskStrings - Sub-task definitions ("T017a: Title")
30
+ * @returns {{ success: boolean, error?: string }}
31
+ */
32
+ export function expandTask(featureName, taskId, subTaskStrings) {
33
+ const state = loadState();
34
+ const feature = state.features[featureName];
35
+
36
+ if (!feature) {
37
+ return { success: false, error: `Feature "${featureName}" not found` };
38
+ }
39
+
40
+ if (!feature.taskList) {
41
+ return { success: false, error: `No taskList found for "${featureName}"` };
42
+ }
43
+
44
+ const task = feature.taskList.find(t => t.id === taskId);
45
+ if (!task) {
46
+ return { success: false, error: `Task "${taskId}" not found in taskList` };
47
+ }
48
+
49
+ if (task.status === 'done' || task.status === 'completed') {
50
+ return { success: false, error: `Task "${taskId}" is already completed — cannot expand` };
51
+ }
52
+
53
+ // Parse sub-tasks
54
+ const subTasks = subTaskStrings.map(parseSubTask);
55
+ const subTaskIds = subTasks.map(s => s.id);
56
+
57
+ // Mark original as expanded
58
+ task.status = 'expanded';
59
+ task.subTasks = subTaskIds;
60
+ task.expandedAt = new Date().toISOString();
61
+
62
+ // Append sub-tasks to taskList
63
+ for (const sub of subTasks) {
64
+ feature.taskList.push({
65
+ id: sub.id,
66
+ title: sub.title,
67
+ status: 'pending',
68
+ dependencies: [],
69
+ files: [],
70
+ checkpoint: null,
71
+ parentTask: taskId
72
+ });
73
+ }
74
+
75
+ feature.updatedAt = new Date().toISOString();
76
+ saveState(state);
77
+
78
+ return { success: true, subTaskIds };
79
+ }
80
+
81
+ /**
82
+ * CLI command handler
83
+ */
84
+ export async function taskExpandCommand(feature, taskId, options) {
85
+ if (!options.into || options.into.length === 0) {
86
+ console.error(chalk.red('--into is required with at least one sub-task'));
87
+ process.exit(1);
88
+ }
89
+
90
+ const result = expandTask(feature, taskId, options.into);
91
+
92
+ if (!result.success) {
93
+ console.error(chalk.red(`\n ${result.error}`));
94
+ process.exit(1);
95
+ }
96
+
97
+ console.log(chalk.green(`\n Task ${taskId} expanded into ${result.subTaskIds.length} sub-tasks:`));
98
+ result.subTaskIds.forEach(id => console.log(chalk.gray(` - ${id}`)));
99
+ console.log('');
100
+ }