@polymorphism-tech/morph-spec 4.9.0 → 4.10.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 (164) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +30 -0
  3. package/bin/task-manager.js +34 -22
  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 +35 -98
  8. package/framework/agents/backend/api-designer.md +3 -0
  9. package/framework/agents/backend/dotnet-senior.md +3 -0
  10. package/framework/agents/backend/ef-modeler.md +2 -0
  11. package/framework/agents/backend/hangfire-orchestrator.md +2 -0
  12. package/framework/agents/backend/ms-agent-expert.md +2 -0
  13. package/framework/agents/frontend/blazor-builder.md +2 -0
  14. package/framework/agents/frontend/nextjs-expert.md +2 -0
  15. package/framework/agents/infrastructure/azure-architect.md +2 -0
  16. package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
  17. package/framework/agents/infrastructure/bicep-architect.md +2 -0
  18. package/framework/agents/infrastructure/container-specialist.md +2 -0
  19. package/framework/agents/infrastructure/devops-engineer.md +3 -0
  20. package/framework/agents/infrastructure/infra-architect.md +3 -0
  21. package/framework/agents/integrations/asaas-financial.md +2 -0
  22. package/framework/agents/integrations/azure-identity.md +2 -0
  23. package/framework/agents/integrations/clerk-auth.md +3 -0
  24. package/framework/agents/integrations/hangfire-integration.md +2 -0
  25. package/framework/agents/integrations/resend-email.md +2 -0
  26. package/framework/agents.json +37 -7
  27. package/framework/commands/commit.md +166 -0
  28. package/framework/commands/morph-apply.md +156 -155
  29. package/framework/commands/morph-archive.md +33 -27
  30. package/framework/commands/morph-infra.md +83 -77
  31. package/framework/commands/morph-preflight.md +97 -55
  32. package/framework/commands/morph-proposal.md +131 -58
  33. package/framework/commands/morph-status.md +36 -30
  34. package/framework/commands/morph-troubleshoot.md +68 -59
  35. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  36. package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
  37. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
  38. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  39. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  40. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  41. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
  42. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  43. package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
  44. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  45. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  46. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  47. package/framework/hooks/shared/compact-restore.js +100 -0
  48. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  49. package/framework/hooks/shared/phase-utils.js +9 -5
  50. package/framework/hooks/shared/state-reader.js +27 -3
  51. package/framework/phases.json +30 -7
  52. package/framework/rules/csharp-standards.md +3 -0
  53. package/framework/rules/frontend-standards.md +2 -0
  54. package/framework/rules/infrastructure-standards.md +3 -0
  55. package/framework/rules/morph-workflow.md +143 -86
  56. package/framework/rules/nextjs-standards.md +2 -0
  57. package/framework/rules/testing-standards.md +3 -0
  58. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  59. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
  60. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
  61. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
  62. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
  63. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
  64. package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
  65. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
  66. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
  67. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
  68. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
  69. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
  70. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
  71. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  72. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -0
  73. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
  74. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
  75. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  76. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
  77. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  78. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  80. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +246 -0
  81. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
  82. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  83. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
  84. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  85. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
  86. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
  87. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  88. package/framework/templates/docs/onboarding.md +2 -2
  89. package/package.json +3 -4
  90. package/src/commands/agents/dispatch-agents.js +50 -3
  91. package/src/commands/mcp/mcp-setup.js +39 -2
  92. package/src/commands/phase/phase-reset.js +74 -0
  93. package/src/commands/project/doctor.js +26 -7
  94. package/src/commands/project/update.js +4 -4
  95. package/src/commands/scope/escalate.js +215 -0
  96. package/src/commands/state/advance-phase.js +27 -53
  97. package/src/commands/state/state.js +1 -1
  98. package/src/commands/task/expand.js +100 -0
  99. package/src/core/paths/output-schema.js +4 -3
  100. package/src/core/state/phase-state-machine.js +7 -4
  101. package/src/core/state/state-manager.js +4 -3
  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 +22 -16
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack-filter.js +58 -0
  110. package/src/lib/tasks/task-parser.js +1 -1
  111. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  112. package/src/scripts/setup-infra.js +68 -18
  113. package/src/utils/agents-installer.js +51 -17
  114. package/src/utils/claude-md-injector.js +90 -0
  115. package/src/utils/file-copier.js +0 -1
  116. package/src/utils/hooks-installer.js +16 -5
  117. package/src/utils/skills-installer.js +67 -7
  118. package/CLAUDE.md +0 -98
  119. package/framework/memory/patterns-learned.md +0 -766
  120. package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
  121. package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
  122. package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
  123. package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
  124. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
  125. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
  126. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
  127. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
  128. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
  129. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
  130. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
  131. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
  132. package/src/commands/project/index.js +0 -8
  133. package/src/core/index.js +0 -10
  134. package/src/core/state/index.js +0 -8
  135. package/src/core/templates/index.js +0 -9
  136. package/src/core/templates/template-data-sources.js +0 -325
  137. package/src/core/workflows/index.js +0 -7
  138. package/src/lib/detectors/config-detector.js +0 -223
  139. package/src/lib/detectors/standards-generator.js +0 -335
  140. package/src/lib/detectors/structure-detector.js +0 -275
  141. package/src/lib/monitor/agent-resolver.js +0 -144
  142. package/src/lib/monitor/renderer.js +0 -230
  143. package/src/lib/orchestration/index.js +0 -7
  144. package/src/lib/orchestration/team-orchestrator.js +0 -404
  145. package/src/sanitizer/context-sanitizer.js +0 -221
  146. package/src/sanitizer/patterns.js +0 -163
  147. package/src/writer/file-writer.js +0 -86
  148. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  149. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  150. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  151. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  152. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  153. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  154. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  155. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  156. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  157. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  158. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  159. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  160. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  161. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  162. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  163. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  164. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * MORPH-SPEC Scope Escalate Command
3
+ *
4
+ * Handles mid-implementation scope discovery by analyzing impact,
5
+ * regressing phase or expanding tasks, and creating an audit trail.
6
+ *
7
+ * Usage:
8
+ * morph-spec scope escalate <feature> --task <id> --reason "..."
9
+ * morph-spec scope escalate <feature> --task <id> --reason "..." --dry-run
10
+ * morph-spec scope escalate <feature> --task <id> --reason "..." --target design
11
+ */
12
+
13
+ import chalk from 'chalk';
14
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
15
+ import { join } from 'path';
16
+ import { loadState, saveState } from '../../core/state/state-manager.js';
17
+ import { analyzeImpact } from '../../lib/scope/impact-analyzer.js';
18
+
19
+ /**
20
+ * Core escalation logic — exported for testing.
21
+ *
22
+ * @param {string} featureName
23
+ * @param {Object} options
24
+ * @param {string} options.task - Trigger task ID
25
+ * @param {string} options.reason - Reason for escalation
26
+ * @param {string} [options.target] - Override target phase
27
+ * @param {boolean} [options.dryRun] - Don't execute, just recommend
28
+ * @returns {Promise<{ success: boolean, error?: string, recommendation?: Object }>}
29
+ */
30
+ export async function escalateScope(featureName, options) {
31
+ const state = loadState();
32
+ const feature = state.features[featureName];
33
+
34
+ if (!feature) {
35
+ return { success: false, error: `Feature "${featureName}" not found in state.json` };
36
+ }
37
+
38
+ if (feature.phase !== 'implement') {
39
+ return { success: false, error: `Scope escalation only available during implement phase (current: ${feature.phase})` };
40
+ }
41
+
42
+ // Read tasks.md
43
+ const tasksPath = join(process.cwd(), '.morph', 'features', featureName, '4-tasks', 'tasks.md');
44
+ let tasksMd = '';
45
+ try {
46
+ tasksMd = readFileSync(tasksPath, 'utf-8');
47
+ } catch {
48
+ return { success: false, error: 'tasks.md not found' };
49
+ }
50
+
51
+ // Read spec.md (optional — not all features have it)
52
+ const specPath = join(process.cwd(), '.morph', 'features', featureName, '1-design', 'spec.md');
53
+ let specMd = '';
54
+ try { specMd = readFileSync(specPath, 'utf-8'); } catch { /* ok */ }
55
+
56
+ // Analyze impact
57
+ const analysis = analyzeImpact({
58
+ triggerTaskId: options.task,
59
+ tasksMd,
60
+ specMd,
61
+ reason: options.reason
62
+ });
63
+
64
+ // Apply target override
65
+ if (options.target) {
66
+ analysis.recommendation = `regress-${options.target}`;
67
+ analysis.targetPhase = options.target;
68
+ if (analysis.targetPhase === 'design') analysis.impact = 'high';
69
+ else if (analysis.targetPhase === 'tasks') analysis.impact = 'medium';
70
+ }
71
+
72
+ // Dry-run: return recommendation without modifying state
73
+ if (options.dryRun) {
74
+ return { success: true, recommendation: analysis };
75
+ }
76
+
77
+ // Build escalation record
78
+ const escId = `ESC-${String((feature.escalations?.length || 0) + 1).padStart(3, '0')}`;
79
+ const record = {
80
+ id: escId,
81
+ triggerTask: options.task,
82
+ reason: options.reason,
83
+ impact: analysis.impact,
84
+ action: analysis.recommendation,
85
+ affectedTasks: analysis.affectedTasks,
86
+ reviewedTasks: [],
87
+ targetPhase: analysis.targetPhase || 'tasks',
88
+ previousPhase: 'implement',
89
+ createdAt: new Date().toISOString(),
90
+ resolvedAt: null
91
+ };
92
+
93
+ // Execute based on recommendation
94
+ if (analysis.recommendation.startsWith('regress')) {
95
+ const targetPhase = analysis.targetPhase || 'tasks';
96
+
97
+ // Flag completed tasks as needsReview (supports both v2 'done' and v3 'completed')
98
+ if (feature.taskList) {
99
+ for (const task of feature.taskList) {
100
+ if (task.status === 'done' || task.status === 'completed') {
101
+ task.needsReview = true;
102
+ }
103
+ }
104
+ }
105
+
106
+ // Reset phase
107
+ feature.phase = targetPhase;
108
+
109
+ // Reset relevant approval gates
110
+ if (feature.approvalGates) {
111
+ if (targetPhase === 'tasks' && feature.approvalGates.tasks) {
112
+ feature.approvalGates.tasks = { approved: false, timestamp: null, approvedBy: null };
113
+ }
114
+ if (targetPhase === 'design') {
115
+ if (feature.approvalGates.design) {
116
+ feature.approvalGates.design = { approved: false, timestamp: null, approvedBy: null };
117
+ }
118
+ if (feature.approvalGates.tasks) {
119
+ feature.approvalGates.tasks = { approved: false, timestamp: null, approvedBy: null };
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ // Write escalation record to state
126
+ if (!feature.escalations) feature.escalations = [];
127
+ feature.escalations.push(record);
128
+ feature.updatedAt = new Date().toISOString();
129
+
130
+ saveState(state);
131
+
132
+ // Create/append escalation-log.md
133
+ writeEscalationLog(featureName, record);
134
+
135
+ return { success: true, recommendation: analysis, escalationId: escId };
136
+ }
137
+
138
+ /**
139
+ * Write escalation log entry to 4-tasks/escalation-log.md
140
+ */
141
+ function writeEscalationLog(featureName, record) {
142
+ const logDir = join(process.cwd(), '.morph', 'features', featureName, '4-tasks');
143
+ mkdirSync(logDir, { recursive: true });
144
+ const logPath = join(logDir, 'escalation-log.md');
145
+
146
+ const doneTasks = record.reviewedTasks?.length > 0
147
+ ? record.reviewedTasks.join(', ')
148
+ : '(all completed tasks flagged)';
149
+
150
+ const entry = `
151
+ ## ${record.id} — ${record.createdAt.split('T')[0]}
152
+ - **Trigger:** ${record.triggerTask}
153
+ - **Discovery:** ${record.reason}
154
+ - **Impact:** ${record.impact} (${record.affectedTasks.length} tasks affected: ${record.affectedTasks.join(', ')})
155
+ - **Action:** ${record.action} → ${record.targetPhase} phase
156
+ - **Affected completed tasks flagged for review:** ${doneTasks}
157
+ `;
158
+
159
+ let existing = '';
160
+ try { existing = readFileSync(logPath, 'utf-8'); } catch { /* new file */ }
161
+
162
+ if (!existing) {
163
+ writeFileSync(logPath, `# Scope Escalation Log\n${entry}`);
164
+ } else {
165
+ writeFileSync(logPath, existing + entry);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * CLI command handler
171
+ */
172
+ export async function scopeEscalateCommand(feature, options) {
173
+ console.log(chalk.cyan('\n========================================'));
174
+ console.log(chalk.cyan(' MORPH-SPEC SCOPE ESCALATION'));
175
+ console.log(chalk.cyan('========================================\n'));
176
+
177
+ if (!options.task) {
178
+ console.error(chalk.red('--task <id> is required'));
179
+ process.exit(1);
180
+ }
181
+ if (!options.reason) {
182
+ console.error(chalk.red('--reason "..." is required'));
183
+ process.exit(1);
184
+ }
185
+
186
+ const result = await escalateScope(feature, options);
187
+
188
+ if (!result.success) {
189
+ console.error(chalk.red(`\n ${result.error}`));
190
+ process.exit(1);
191
+ }
192
+
193
+ const rec = result.recommendation;
194
+ console.log(chalk.gray('Trigger task:'), rec.triggerTask);
195
+ console.log(chalk.gray('Impact:'), rec.impact);
196
+ console.log(chalk.gray('Affected tasks:'), rec.affectedTasks.join(', '));
197
+ console.log(chalk.gray('Recommendation:'), rec.recommendation);
198
+ if (rec.targetPhase) console.log(chalk.gray('Target phase:'), rec.targetPhase);
199
+
200
+ if (options.dryRun) {
201
+ console.log(chalk.cyan('\n Dry-run — no changes made'));
202
+ process.exit(2);
203
+ }
204
+
205
+ if (rec.recommendation === 'expand') {
206
+ console.log(chalk.green(`\n Escalation ${result.escalationId} recorded (low impact)`));
207
+ console.log(chalk.yellow(` Action: Use task expand to split the task:`));
208
+ console.log(chalk.gray(` morph-spec task expand ${feature} ${options.task} --into "ID: Title" ...`));
209
+ } else {
210
+ console.log(chalk.green(`\n Escalation ${result.escalationId} executed`));
211
+ console.log(chalk.yellow(` Phase regressed to: ${rec.targetPhase || 'tasks'}`));
212
+ }
213
+ console.log(chalk.gray(` Escalation log: .morph/features/${feature}/4-tasks/escalation-log.md`));
214
+ console.log('');
215
+ }
@@ -16,18 +16,19 @@ import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/desig
16
16
  import { getOutputPath, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
17
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
22
  import { getStackProfile } from '../../lib/stack/stack-profile.js';
23
23
  import { readFileSync, existsSync } from 'fs';
24
24
  import { join, dirname } from 'path';
25
25
  import { fileURLToPath } from 'url';
26
+ import { emitValidatorDispatch } from '../../lib/validators/shared/emit-validator-dispatch.js';
26
27
 
27
28
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
29
 
29
30
  // Phase order for advancing (skips optional phases unless active)
30
- const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
31
+ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'plan', 'tasks', 'implement', 'sync'];
31
32
 
32
33
  /**
33
34
  * Phases that require an explicit approval gate before advancing.
@@ -36,6 +37,7 @@ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks',
36
37
  */
37
38
  export const APPROVAL_GATE_MAP = {
38
39
  'design': 'design',
40
+ 'plan': 'plan',
39
41
  'tasks': 'tasks',
40
42
  'uiux': 'uiux'
41
43
  };
@@ -78,13 +80,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
78
80
 
79
81
  // Check if this phase is part of a combined phase
80
82
  if (workflowConfig.phases.combined) {
83
+ let isCombined = false;
81
84
  for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
82
85
  if (phases.includes(candidate) && candidate !== combinedName) {
83
86
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
84
87
  skippedPhases.push(candidate);
85
- continue;
88
+ isCombined = true;
89
+ break;
86
90
  }
87
91
  }
92
+ if (isCombined) continue;
88
93
  }
89
94
 
90
95
  // Check if workflow only runs specific phases
@@ -332,6 +337,19 @@ export async function advancePhaseCommand(feature, options = {}) {
332
337
  }
333
338
  }
334
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
+
335
353
  // Gate: Check design system when advancing to implement with UI agents
336
354
  if (nextPhase === 'implement') {
337
355
  const gateResult = designSystemGate(feature, process.cwd());
@@ -470,6 +488,11 @@ function getPhaseGuidance(phase, feature) {
470
488
  `Resume spec pipeline: /morph-proposal ${feature}`,
471
489
  'Resolve edge cases and unknowns'
472
490
  ],
491
+ 'plan': [
492
+ `Resume spec pipeline: /morph-proposal ${feature}`,
493
+ 'Review implementation plan for completeness',
494
+ 'Approve plan before generating task breakdown'
495
+ ],
473
496
  'tasks': [
474
497
  `Resume spec pipeline: /morph-proposal ${feature}`,
475
498
  'Review task order and dependencies',
@@ -528,55 +551,6 @@ function designSystemGate(feature, projectPath = '.') {
528
551
  };
529
552
  }
530
553
 
531
- /**
532
- * Emit Tier-4 validation dispatch block after phase advance.
533
- * Outputs a JSON block listing active Tier-4 validators for the LLM to dispatch
534
- * as read-only subagents before proceeding to implementation.
535
- * Non-blocking — fails silently.
536
- *
537
- * @param {string} featureName
538
- * @param {string} phase
539
- * @param {string} cwd
540
- */
541
- async function emitValidatorDispatch(featureName, phase, cwd) {
542
- try {
543
- const { buildDispatchConfig } = await import('../agents/dispatch-agents.js');
544
- const config = await buildDispatchConfig(cwd, featureName, phase, { mode: 'validate' });
545
- const validators = config.agents?.filter(a => a.tier === 4);
546
- if (!validators || validators.length === 0) return;
547
-
548
- const agentsPath = join(__dirname, '../../../framework/agents.json');
549
- const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
550
- const allAgents = agentsData.agents || {};
551
-
552
- const validatorEntries = validators.map(v => {
553
- const agentData = allAgents[v.id];
554
- return {
555
- id: v.id,
556
- title: v.title,
557
- severity: agentData?.hook_behavior?.severity || 'error',
558
- blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
559
- checks: agentData?.hook_behavior?.validates || [],
560
- taskPrompt: v.taskPrompt
561
- };
562
- });
563
-
564
- const dispatch = {
565
- validationRequired: true,
566
- phase,
567
- feature: featureName,
568
- validators: validatorEntries,
569
- 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.'
570
- };
571
-
572
- console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
573
- console.log(JSON.stringify(dispatch, null, 2));
574
- console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
575
- } catch {
576
- // Non-blocking — fail silently
577
- }
578
- }
579
-
580
554
  /**
581
555
  * Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
582
556
  * Outputs structured JSON for the LLM to read and follow.
@@ -224,7 +224,7 @@ async function setCommand(featureName, key, value, options) {
224
224
  } else {
225
225
  logger.warn(`Direct phase override — validation gates may be bypassed.`);
226
226
  logger.dim(` The actual phase is derived from filesystem subdirectories`);
227
- logger.dim(` (0-proposal/, 1-design/, 3-tasks/, 4-implement/). This sets a state.json`);
227
+ logger.dim(` (0-proposal/, 1-design/, 4-tasks/, 5-implement/). This sets a state.json`);
228
228
  logger.dim(` override used only for lightweight phases (setup, clarify, sync).`);
229
229
  logger.dim(` Recommended: morph-spec phase advance ${featureName}`);
230
230
  }
@@ -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
+ }
@@ -26,17 +26,18 @@ export const OUTPUT_SCHEMA = {
26
26
  proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
27
27
  schemaAnalysis:{ filename: 'schema-analysis.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
28
28
  spec: { filename: 'spec.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
29
- clarifications:{ filename: 'clarifications.md', phaseDir: '2-clarify', phase: 'clarify', protected: false },
29
+ clarifications:{ filename: 'clarifications.md', phaseDir: '1-design', phase: 'clarify', protected: false },
30
30
  contracts: { filename: 'contracts.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
31
31
  contractsTs: { filename: 'contracts.ts', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
32
32
  contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
33
- tasks: { filename: 'tasks.md', phaseDir: '3-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
33
+ plan: { filename: 'plan.md', phaseDir: '3-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
34
+ tasks: { filename: 'tasks.md', phaseDir: '4-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
34
35
  uiDesignSystem:{ filename: 'design-system.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
35
36
  uiMockups: { filename: 'mockups.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
36
37
  uiComponents: { filename: 'components.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
37
38
  uiFlows: { filename: 'flows.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
38
39
  decisions: { filename: 'decisions.md', phaseDir: '1-design', phase: 'design', protected: false },
39
- recap: { filename: 'recap.md', phaseDir: '4-implement', phase: 'implement', protected: false },
40
+ recap: { filename: 'recap.md', phaseDir: '5-implement', phase: 'implement', protected: false },
40
41
  };
41
42
 
42
43
  // ============================================================================
@@ -14,7 +14,8 @@ const VALID_TRANSITIONS = {
14
14
  'setup': ['uiux', 'design'], // Can skip UI/UX if no frontend
15
15
  'uiux': ['design'],
16
16
  'design': ['clarify'],
17
- 'clarify': ['tasks'],
17
+ 'clarify': ['plan'],
18
+ 'plan': ['tasks'],
18
19
  'tasks': ['implement'],
19
20
  'implement': ['sync', 'archived'], // Can skip sync
20
21
  'sync': ['archived']
@@ -29,9 +30,10 @@ const PHASE_NAMES = {
29
30
  'uiux': 'UI/UX Design (Phase 1.5)',
30
31
  'design': 'Design (Phase 2)',
31
32
  'clarify': 'Clarify (Phase 3)',
32
- 'tasks': 'Tasks (Phase 4)',
33
- 'implement': 'Implement (Phase 5)',
34
- 'sync': 'Sync (Phase 6)',
33
+ 'plan': 'Plan (Phase 4)',
34
+ 'tasks': 'Tasks (Phase 5)',
35
+ 'implement': 'Implement (Phase 6)',
36
+ 'sync': 'Sync (Phase 7)',
35
37
  'archived': 'Archived'
36
38
  };
37
39
 
@@ -124,6 +126,7 @@ export function getPhaseSequence() {
124
126
  'uiux', // optional
125
127
  'design',
126
128
  'clarify',
129
+ 'plan',
127
130
  'tasks',
128
131
  'implement',
129
132
  'sync', // optional
@@ -27,12 +27,13 @@ export function getStatePath() {
27
27
  * Returns the phase corresponding to the highest-numbered folder present.
28
28
  *
29
29
  * @param {string} featurePath - Absolute path to .morph/features/{feature}/
30
- * @returns {string} Phase name: 'implement' | 'tasks' | 'uiux' | 'design' | 'proposal' | 'setup'
30
+ * @returns {string} Phase name: 'implement' | 'tasks' | 'plan' | 'uiux' | 'design' | 'proposal' | 'setup'
31
31
  */
32
32
  export function derivePhase(featurePath) {
33
33
  const phaseMap = [
34
- ['4-implement', 'implement'],
35
- ['3-tasks', 'tasks'],
34
+ ['5-implement', 'implement'],
35
+ ['4-tasks', 'tasks'],
36
+ ['3-plan', 'plan'],
36
37
  ['2-ui', 'uiux'],
37
38
  ['1-design', 'design'],
38
39
  ['0-proposal', 'proposal'],