@polymorphism-tech/morph-spec 4.8.18 → 4.9.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 (138) hide show
  1. package/CLAUDE.md +98 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +15 -56
  4. package/bin/task-manager.js +115 -14
  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 +21 -0
  10. package/framework/agents.json +758 -164
  11. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  12. package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
  13. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
  14. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
  15. package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
  16. package/framework/hooks/claude-code/statusline.py +76 -30
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  18. package/framework/hooks/shared/activity-logger.js +0 -24
  19. package/framework/hooks/shared/phase-utils.js +3 -0
  20. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  21. package/framework/hooks/shared/stale-task-reset.js +57 -0
  22. package/framework/hooks/shared/state-reader.js +2 -2
  23. package/framework/hooks/shared/worktree-helpers.js +53 -0
  24. package/framework/phases.json +40 -8
  25. package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
  26. package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
  27. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
  28. package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  30. package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
  31. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  32. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  33. package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
  34. package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
  42. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
  43. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
  44. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
  45. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
  46. package/framework/standards/STANDARDS.json +640 -88
  47. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  48. package/framework/templates/REGISTRY.json +1825 -1909
  49. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  50. package/framework/templates/docs/onboarding.md +1 -5
  51. package/framework/workflows/configs/nodejs-cli.json +40 -0
  52. package/package.json +2 -6
  53. package/src/commands/agents/dispatch-agents.js +55 -4
  54. package/src/commands/project/doctor.js +16 -47
  55. package/src/commands/project/init.js +1 -1
  56. package/src/commands/project/status.js +2 -2
  57. package/src/commands/project/update.js +381 -365
  58. package/src/commands/project/worktree.js +154 -0
  59. package/src/commands/state/advance-phase.js +120 -30
  60. package/src/commands/state/approve.js +2 -2
  61. package/src/commands/state/index.js +7 -8
  62. package/src/commands/state/phase-runner.js +1 -1
  63. package/src/commands/state/state.js +61 -6
  64. package/src/commands/tasks/task.js +78 -99
  65. package/src/commands/templates/template-render.js +93 -173
  66. package/src/commands/trust/trust.js +26 -21
  67. package/src/core/paths/output-schema.js +15 -0
  68. package/src/core/state/state-manager.js +28 -54
  69. package/src/core/workflows/workflow-detector.js +9 -87
  70. package/src/lib/phase-chain/phase-validator.js +330 -0
  71. package/src/lib/stack/stack-profile.js +88 -0
  72. package/src/lib/tasks/task-classifier.js +16 -0
  73. package/src/lib/tasks/test-runner.js +77 -0
  74. package/src/lib/trust/trust-manager.js +32 -144
  75. package/src/lib/validators/spec-validator.js +58 -4
  76. package/src/lib/validators/validation-runner.js +23 -11
  77. package/src/scripts/setup-infra.js +240 -224
  78. package/src/utils/agents-installer.js +2 -2
  79. package/src/utils/banner.js +1 -1
  80. package/src/utils/claude-settings-manager.js +1 -1
  81. package/src/utils/file-copier.js +1 -0
  82. package/src/utils/hooks-installer.js +258 -8
  83. package/framework/hooks/dev/check-sync-health.js +0 -117
  84. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  85. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  86. package/framework/hooks/dev/sync-template-registry.js +0 -60
  87. package/framework/hooks/dev/validate-skill-format.js +0 -70
  88. package/framework/hooks/dev/validate-standard-format.js +0 -73
  89. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  90. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  91. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  92. package/framework/workflows/configs/design-impl.json +0 -49
  93. package/framework/workflows/configs/express.json +0 -45
  94. package/framework/workflows/configs/fast-track.json +0 -42
  95. package/framework/workflows/configs/full-morph.json +0 -79
  96. package/framework/workflows/configs/fusion.json +0 -39
  97. package/framework/workflows/configs/long-running.json +0 -33
  98. package/framework/workflows/configs/spec-only.json +0 -43
  99. package/framework/workflows/configs/ui-refresh.json +0 -49
  100. package/framework/workflows/configs/zero-touch.json +0 -82
  101. package/src/commands/project/monitor.js +0 -295
  102. package/src/commands/project/tutorial.js +0 -115
  103. package/src/commands/state/validate-phase.js +0 -238
  104. package/src/commands/templates/generate-contracts.js +0 -445
  105. package/src/core/orchestrator.js +0 -171
  106. package/src/core/registry/command-registry.js +0 -28
  107. package/src/core/registry/index.js +0 -8
  108. package/src/core/registry/validator-registry.js +0 -204
  109. package/src/core/templates/template-validator.js +0 -296
  110. package/src/generator/config-generator.js +0 -206
  111. package/src/generator/templates/config.json.template +0 -40
  112. package/src/generator/templates/project.md.template +0 -67
  113. package/src/lib/agents/micro-agent-factory.js +0 -161
  114. package/src/lib/analysis/complexity-analyzer.js +0 -441
  115. package/src/lib/analysis/index.js +0 -7
  116. package/src/lib/analytics/analytics-engine.js +0 -345
  117. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  118. package/src/lib/checkpoints/index.js +0 -7
  119. package/src/lib/context/context-bundler.js +0 -241
  120. package/src/lib/context/context-optimizer.js +0 -212
  121. package/src/lib/context/context-tracker.js +0 -273
  122. package/src/lib/context/core-four-tracker.js +0 -201
  123. package/src/lib/context/mcp-optimizer.js +0 -200
  124. package/src/lib/execution/fusion-executor.js +0 -304
  125. package/src/lib/execution/parallel-executor.js +0 -270
  126. package/src/lib/hooks/stop-hook-executor.js +0 -286
  127. package/src/lib/hops/hop-composer.js +0 -221
  128. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  129. package/src/lib/threads/thread-coordinator.js +0 -238
  130. package/src/lib/threads/thread-manager.js +0 -317
  131. package/src/lib/tracking/artifact-trail.js +0 -202
  132. package/src/scanner/project-scanner.js +0 -242
  133. package/src/ui/diff-display.js +0 -91
  134. package/src/ui/interactive-wizard.js +0 -96
  135. package/src/ui/user-review.js +0 -211
  136. package/src/ui/wizard-questions.js +0 -188
  137. package/src/utils/color-utils.js +0 -70
  138. package/src/utils/process-handler.js +0 -97
@@ -0,0 +1,154 @@
1
+ /**
2
+ * MORPH-SPEC Worktree Setup Command
3
+ *
4
+ * Creates a git worktree at .worktrees/{feature}/ on branch morph/{feature}.
5
+ * Used by SessionStart hook to isolate feature work from the main branch.
6
+ *
7
+ * Usage:
8
+ * morph-spec worktree setup <feature>
9
+ * morph-spec worktree setup <feature> --fresh
10
+ *
11
+ * Exit codes:
12
+ * 0 — success (created) or fail-open (not a git repo, git error)
13
+ * 2 — worktree already exists (signals resume scenario to hook caller)
14
+ */
15
+
16
+ import { execSync } from 'child_process';
17
+ import { existsSync, mkdirSync } from 'fs';
18
+ import { join } from 'path';
19
+
20
+ // ── Pure helpers (exported for tests) ──────────────────────────────────────
21
+
22
+ /**
23
+ * Check if a directory is inside a git repository.
24
+ * @param {string} cwd
25
+ * @returns {boolean}
26
+ */
27
+ export function isGitRepo(cwd = process.cwd()) {
28
+ try {
29
+ execSync('git rev-parse --git-dir', { cwd, stdio: 'pipe' });
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Check if a branch exists locally.
38
+ * @param {string} branch
39
+ * @param {string} cwd
40
+ * @returns {boolean}
41
+ */
42
+ export function branchExists(branch, cwd = process.cwd()) {
43
+ try {
44
+ execSync(`git show-ref --verify --quiet refs/heads/${branch}`, { cwd, stdio: 'pipe' });
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Check if a worktree path is already registered with git.
53
+ * @param {string} worktreePath Relative or absolute path
54
+ * @param {string} cwd
55
+ * @returns {boolean}
56
+ */
57
+ export function worktreeExists(worktreePath, cwd = process.cwd()) {
58
+ try {
59
+ const output = execSync('git worktree list --porcelain', { cwd, stdio: 'pipe' }).toString();
60
+ const normalizedPath = worktreePath.replace(/\\/g, '/');
61
+ const absPath = normalizedPath.startsWith('/') ? normalizedPath : `${cwd.replace(/\\/g, '/')}/${normalizedPath}`;
62
+ return output.replace(/\\/g, '/').includes(absPath);
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Build the worktree directory path (always forward slashes).
70
+ * @param {string} feature
71
+ * @param {string} cwd
72
+ * @param {{ fresh?: boolean }} options
73
+ * @returns {string}
74
+ */
75
+ export function buildWorktreePath(feature, cwd = process.cwd(), options = {}) {
76
+ const suffix = options.fresh ? `-${getDateSuffix()}` : '';
77
+ const normalizedCwd = cwd.replace(/\\/g, '/');
78
+ return `${normalizedCwd}/.worktrees/${feature}${suffix}`;
79
+ }
80
+
81
+ /**
82
+ * Build the branch name for a feature worktree.
83
+ * @param {string} feature
84
+ * @param {{ fresh?: boolean }} options
85
+ * @returns {string}
86
+ */
87
+ export function buildBranchName(feature, options = {}) {
88
+ const suffix = options.fresh ? `-${getDateSuffix()}` : '';
89
+ return `morph/${feature}${suffix}`;
90
+ }
91
+
92
+ function getDateSuffix() {
93
+ return new Date().toISOString().slice(0, 10).replace(/-/g, '');
94
+ }
95
+
96
+ // ── Command handler ─────────────────────────────────────────────────────────
97
+
98
+ /**
99
+ * Main command handler for `morph-spec worktree setup <feature>`
100
+ * @param {string} feature
101
+ * @param {{ fresh?: boolean, cwd?: string }} options
102
+ */
103
+ export async function worktreeSetupCommand(feature, options = {}) {
104
+ const cwd = options.cwd || process.cwd();
105
+
106
+ // Gate 1: Must be a git repo
107
+ if (!isGitRepo(cwd)) {
108
+ console.log(JSON.stringify({ created: false, error: 'not-a-git-repo', feature }));
109
+ process.exit(0); // Fail-open
110
+ }
111
+
112
+ const branch = buildBranchName(feature, options);
113
+ const worktreePath = buildWorktreePath(feature, cwd, options);
114
+
115
+ // Gate 2: If worktree already registered and not --fresh, signal resume
116
+ if (!options.fresh && worktreeExists(worktreePath, cwd)) {
117
+ console.log(JSON.stringify({ created: false, alreadyExists: true, path: worktreePath, branch, feature }));
118
+ process.exit(2); // Convention: 2 = already exists
119
+ }
120
+
121
+ try {
122
+ // Create branch if needed (based off current HEAD)
123
+ if (!branchExists(branch, cwd)) {
124
+ execSync(`git branch ${branch}`, { cwd, stdio: 'pipe' });
125
+ }
126
+
127
+ // Create worktree directory parent if needed
128
+ const parentDir = join(cwd, '.worktrees');
129
+ if (!existsSync(parentDir)) {
130
+ mkdirSync(parentDir, { recursive: true });
131
+ }
132
+
133
+ // Add worktree
134
+ execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd, stdio: 'pipe' });
135
+
136
+ // Persist in state (non-blocking — feature may not be in state yet)
137
+ try {
138
+ const { loadState, saveState } = await import('../../lib/state/state-manager.js');
139
+ const state = loadState(cwd);
140
+ if (state?.features?.[feature]) {
141
+ state.features[feature].worktree = { path: worktreePath, branch, createdAt: new Date().toISOString() };
142
+ saveState(state, cwd);
143
+ }
144
+ } catch {
145
+ // Non-blocking
146
+ }
147
+
148
+ console.log(JSON.stringify({ created: true, path: worktreePath, branch, feature }));
149
+ process.exit(0);
150
+ } catch (err) {
151
+ console.log(JSON.stringify({ created: false, error: (err.message || 'unknown').slice(0, 200), feature }));
152
+ process.exit(0); // Fail-open
153
+ }
154
+ }
@@ -11,14 +11,15 @@
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
19
  import { validateTransition, getPhaseDisplayName } from '../../core/state/phase-state-machine.js';
20
20
  import { validateSpecContent, validateTasksContent, validateFeatureOutputs } 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';
@@ -28,6 +29,17 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
28
29
  // Phase order for advancing (skips optional phases unless active)
29
30
  const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
30
31
 
32
+ /**
33
+ * Phases that require an explicit approval gate before advancing.
34
+ * When --skip-approval is passed, these gates are bypassed and a warning is logged.
35
+ * Exported for use in tests and in phase-runner warnings.
36
+ */
37
+ export const APPROVAL_GATE_MAP = {
38
+ 'design': 'design',
39
+ 'tasks': 'tasks',
40
+ 'uiux': 'uiux'
41
+ };
42
+
31
43
  /**
32
44
  * Get the next phase after the current one.
33
45
  * Returns { nextPhase, skippedPhases } so callers can persist skipped phases in state.
@@ -119,14 +131,11 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
119
131
  }
120
132
  }
121
133
 
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
- }
134
+ // sync is optional — only skip if --skip-optional is set
135
+ if (candidate === 'sync' && skipOptional) {
136
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: optional phase`));
137
+ skippedPhases.push(candidate);
138
+ continue;
130
139
  }
131
140
  }
132
141
 
@@ -189,13 +198,15 @@ export async function advancePhaseCommand(feature, options = {}) {
189
198
 
190
199
  // === GATE 2: Approval Gate Check ===
191
200
  // 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];
201
+ // Track gates that get approved implicitly so we can persist them after state load.
202
+ let gateApprovedImplicitly = null; // { gate, approvedBy, reason }
203
+
204
+ const requiredGate = APPROVAL_GATE_MAP[currentPhase];
205
+ if (requiredGate && options.skipApproval) {
206
+ console.log(chalk.yellow(`\n⚠ --skip-approval: Approval gate "${requiredGate}" bypassed for phase "${currentPhase}"`));
207
+ console.log(chalk.gray(' Intended for CI/automation only. Run without --skip-approval for normal workflow.\n'));
208
+ gateApprovedImplicitly = { gate: requiredGate, approvedBy: 'skip-approval' };
209
+ }
199
210
  if (requiredGate && !options.skipApproval) {
200
211
  const gateStatus = getApprovalGate(feature, requiredGate);
201
212
 
@@ -206,6 +217,7 @@ export async function advancePhaseCommand(feature, options = {}) {
206
217
  if (trustResult.autoApprove) {
207
218
  console.log(chalk.green(`\n✓ Auto-approved gate "${requiredGate}" (${trustResult.reason})`));
208
219
  // Proceed — trust bypasses manual gate requirement
220
+ gateApprovedImplicitly = { gate: requiredGate, approvedBy: `trust:${trustResult.level}` };
209
221
  } else {
210
222
  console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
211
223
  console.log(chalk.gray(` Trust level: ${trustResult.level} (${trustResult.reason})`));
@@ -245,9 +257,10 @@ export async function advancePhaseCommand(feature, options = {}) {
245
257
  // === GATE 4: Content Validation ===
246
258
  // Validate spec.md and contracts.cs when advancing from design phase
247
259
  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);
260
+ // Check spec.md structure and content (filesystem-based)
261
+ const specAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'spec');
262
+ if (existsSync(specAbsPath)) {
263
+ const specContentValidation = validateSpecContent(specAbsPath);
251
264
 
252
265
  if (!specContentValidation.valid) {
253
266
  console.log(chalk.red('\n✗ Spec content validation failed:'));
@@ -294,8 +307,9 @@ export async function advancePhaseCommand(feature, options = {}) {
294
307
  // === GATE 5: Tasks Content Validation ===
295
308
  // Validate tasks.md structure when advancing to implement
296
309
  if (currentPhase === 'tasks' && nextPhase === 'implement') {
297
- if (featureData.outputs?.tasks?.created) {
298
- const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
310
+ const tasksAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'tasks');
311
+ if (existsSync(tasksAbsPath)) {
312
+ const tasksContentValidation = validateTasksContent(tasksAbsPath);
299
313
 
300
314
  if (!tasksContentValidation.valid) {
301
315
  console.log(chalk.red('\n✗ Tasks content validation failed:'));
@@ -345,6 +359,40 @@ export async function advancePhaseCommand(feature, options = {}) {
345
359
  const existingSkipped = state.features[feature].skippedPhases || [];
346
360
  state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
347
361
  }
362
+
363
+ // Ensure approvalGates object exists
364
+ if (!state.features[feature].approvalGates) {
365
+ state.features[feature].approvalGates = {};
366
+ }
367
+
368
+ const now = new Date().toISOString();
369
+ const gates = state.features[feature].approvalGates;
370
+
371
+ // Auto-approve proposal gate when advancing past the proposal phase.
372
+ // The act of advancing is itself the approval — no explicit gate check needed.
373
+ if (currentPhase === 'proposal' && !gates.proposal?.approved) {
374
+ gates.proposal = { approved: true, timestamp: now, approvedBy: 'workflow-advance' };
375
+ }
376
+
377
+ // Persist gates approved implicitly (trust auto-approve or --skip-approval bypass).
378
+ if (gateApprovedImplicitly && !gates[gateApprovedImplicitly.gate]?.approved) {
379
+ gates[gateApprovedImplicitly.gate] = {
380
+ approved: true,
381
+ timestamp: now,
382
+ approvedBy: gateApprovedImplicitly.approvedBy
383
+ };
384
+ }
385
+
386
+ // Auto-approve gates for phases that were legitimately skipped.
387
+ // Skipped phases never run their explicit approve step, so their gate would
388
+ // otherwise stay false in state — making approval-status misleading.
389
+ for (const skippedPhase of skippedPhases) {
390
+ const skippedGate = APPROVAL_GATE_MAP[skippedPhase];
391
+ if (skippedGate && !gates[skippedGate]?.approved) {
392
+ gates[skippedGate] = { approved: true, timestamp: now, approvedBy: 'phase-skipped' };
393
+ }
394
+ }
395
+
348
396
  saveState(state);
349
397
 
350
398
  // Run PhaseAdvanced agent-teams hook (non-blocking)
@@ -369,6 +417,9 @@ export async function advancePhaseCommand(feature, options = {}) {
369
417
  await emitValidatorDispatch(feature, validationPhase, process.cwd());
370
418
  }
371
419
 
420
+ // Emit SKILL DISPATCH for any phase that has requiredSkills
421
+ await emitSkillDispatch(nextPhase, process.cwd());
422
+
372
423
  console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
373
424
 
374
425
  // Show what's needed in the new phase
@@ -407,11 +458,14 @@ function getPhaseGuidance(phase, feature) {
407
458
  'Provide layout references and preferences',
408
459
  'Review wireframes before proceeding'
409
460
  ],
410
- 'design': [
411
- `Resume spec pipeline: /morph-proposal ${feature}`,
412
- 'Review DECISION POINTS carefully',
413
- 'Approve spec.md and contracts.cs'
414
- ],
461
+ 'design': (() => {
462
+ const { isDotnet } = getStackProfile();
463
+ return [
464
+ `Resume spec pipeline: /morph-proposal ${feature}`,
465
+ 'Review DECISION POINTS carefully',
466
+ isDotnet ? 'Approve spec.md and contracts.cs' : 'Approve spec.md and contracts.ts (TypeScript + Zod schemas)'
467
+ ];
468
+ })(),
415
469
  'clarify': [
416
470
  `Resume spec pipeline: /morph-proposal ${feature}`,
417
471
  'Resolve edge cases and unknowns'
@@ -428,7 +482,7 @@ function getPhaseGuidance(phase, feature) {
428
482
  ],
429
483
  'sync': [
430
484
  'Review decisions.md for standards to promote',
431
- `Sync: morph-spec sync --path ${getOutputPath(feature, 'decisions')}`
485
+ `Decisions file: ${getOutputPath(feature, 'decisions')}`
432
486
  ]
433
487
  };
434
488
 
@@ -466,7 +520,7 @@ function designSystemGate(feature, projectPath = '.') {
466
520
  'Create a design system with one of these options:',
467
521
  ' 1. Project-level: .morph/context/design-system.md (shared across features)',
468
522
  ` 2. Feature-level: ${getOutputPath(feature, 'uiDesignSystem')} (feature-specific)`,
469
- ' 3. Auto-generate: morph-spec generate design-system (scans existing CSS)',
523
+ ' 3. Run /morph:phase-uiux skill to auto-generate from existing codebase',
470
524
  '',
471
525
  'Or remove UI agents if they are not needed:',
472
526
  ` morph-spec state remove-agent ${feature} blazor-builder`
@@ -522,3 +576,39 @@ async function emitValidatorDispatch(featureName, phase, cwd) {
522
576
  // Non-blocking — fail silently
523
577
  }
524
578
  }
579
+
580
+ /**
581
+ * Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
582
+ * Outputs structured JSON for the LLM to read and follow.
583
+ * Non-blocking — fails silently.
584
+ *
585
+ * @param {string} nextPhase - The phase being advanced into
586
+ * @param {string} cwd - Project root path (for locating phases.json)
587
+ */
588
+ async function emitSkillDispatch(nextPhase, cwd) {
589
+ try {
590
+ // Try project-local phases.json first, fall back to package-level
591
+ const localPath = join(cwd, 'framework', 'phases.json');
592
+ const packagePath = join(__dirname, '../../../framework/phases.json');
593
+ const resolvedPath = existsSync(localPath) ? localPath : packagePath;
594
+
595
+ if (!existsSync(resolvedPath)) return;
596
+
597
+ const phasesData = JSON.parse(readFileSync(resolvedPath, 'utf8'));
598
+ const skills = phasesData?.phases?.[nextPhase]?.requiredSkills;
599
+ if (!skills || skills.length === 0) return;
600
+
601
+ const dispatch = {
602
+ skillDispatch: true,
603
+ phase: nextPhase,
604
+ skills,
605
+ instruction: `These skills are MANDATORY for phase '${nextPhase}'. Invoke them at the specified trigger points using Skill(). Do NOT skip them.`,
606
+ };
607
+
608
+ console.log(chalk.cyan('\n--- SKILL DISPATCH ---'));
609
+ console.log(JSON.stringify(dispatch, null, 2));
610
+ console.log(chalk.cyan('--- END SKILL DISPATCH ---\n'));
611
+ } catch {
612
+ // Non-blocking — fail silently
613
+ }
614
+ }
@@ -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/, 3-tasks/, 4-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);