@polymorphism-tech/morph-spec 4.8.12 → 4.8.15

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 (76) hide show
  1. package/README.md +379 -379
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/{task-manager.cjs → task-manager.js} +249 -172
  4. package/claude-plugin.json +14 -14
  5. package/docs/CHEATSHEET.md +203 -203
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +224 -140
  8. package/framework/hooks/README.md +202 -202
  9. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  10. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  11. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +12 -0
  12. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  13. package/framework/hooks/claude-code/session-start/inject-morph-context.js +34 -0
  14. package/framework/hooks/claude-code/statusline.py +6 -0
  15. package/framework/hooks/claude-code/stop/validate-completion.js +38 -4
  16. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  18. package/framework/hooks/shared/phase-utils.js +4 -1
  19. package/framework/hooks/shared/state-reader.js +1 -0
  20. package/framework/skills/README.md +1 -0
  21. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  22. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  23. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  24. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  25. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  26. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  27. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  28. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  30. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  31. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  32. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  33. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  34. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  36. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  37. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +50 -188
  38. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  39. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  40. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  41. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  42. package/framework/skills/level-1-workflows/phase-design/SKILL.md +71 -109
  43. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  44. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  45. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +171 -114
  46. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  47. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  48. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +35 -159
  49. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  50. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  51. package/framework/squad-templates/backend-only.json +14 -1
  52. package/framework/squad-templates/frontend-only.json +14 -1
  53. package/framework/squad-templates/full-stack.json +25 -8
  54. package/framework/standards/STANDARDS.json +631 -86
  55. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  56. package/framework/templates/project/validate.js +122 -0
  57. package/framework/workflows/configs/zero-touch.json +7 -0
  58. package/package.json +87 -87
  59. package/src/commands/agents/dispatch-agents.js +53 -10
  60. package/src/commands/state/advance-phase.js +88 -13
  61. package/src/commands/state/index.js +2 -1
  62. package/src/commands/state/phase-runner.js +215 -0
  63. package/src/commands/tasks/task.js +25 -4
  64. package/src/core/paths/output-schema.js +2 -1
  65. package/src/lib/detectors/design-system-detector.js +5 -4
  66. package/src/lib/generators/recap-generator.js +16 -0
  67. package/src/lib/orchestration/team-orchestrator.js +171 -89
  68. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  69. package/src/lib/standards/digest-builder.js +231 -0
  70. package/src/lib/tasks/task-parser.js +94 -0
  71. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  72. package/src/lib/validators/content/content-validator.js +34 -106
  73. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  74. package/src/lib/validators/validation-runner.js +2 -2
  75. package/src/utils/file-copier.js +1 -0
  76. package/src/utils/hooks-installer.js +31 -7
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import chalk from 'chalk';
13
- import { loadState, saveState, getFeature, getApprovalGate } from '../../core/state/state-manager.js';
13
+ import { loadState, saveState, getFeature, getApprovalGate, derivePhase } from '../../core/state/state-manager.js';
14
14
  import { PHASES, validatePhase } from './validate-phase.js';
15
15
  import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
16
16
  import { getOutputPath } from '../../core/paths/output-schema.js';
@@ -29,7 +29,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
29
29
  const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
30
30
 
31
31
  /**
32
- * Get the next phase after the current one
32
+ * Get the next phase after the current one.
33
+ * Returns { nextPhase, skippedPhases } so callers can persist skipped phases in state.
33
34
  */
34
35
  function getNextPhase(currentPhase, feature, skipOptional, featureName) {
35
36
  // Load workflow config if workflow is set
@@ -40,9 +41,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
40
41
 
41
42
  const currentIndex = PHASE_ORDER.indexOf(currentPhase);
42
43
  if (currentIndex === -1 || currentIndex >= PHASE_ORDER.length - 1) {
43
- return null;
44
+ return { nextPhase: null, skippedPhases: [] };
44
45
  }
45
46
 
47
+ // Agents that justify the uiux phase (wireframes, design system).
48
+ // Implementation-only agents (blazor-builder, nextjs-expert) are NOT included —
49
+ // they build components from existing designs but don't create the design artifacts.
50
+ const uiDesignAgents = ['uiux-designer', 'ui-ux-designer', 'ui-designer'];
51
+
52
+ const skippedPhases = [];
53
+
46
54
  for (let i = currentIndex + 1; i < PHASE_ORDER.length; i++) {
47
55
  const candidate = PHASE_ORDER[i];
48
56
  const phaseDef = PHASES[candidate];
@@ -52,6 +60,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
52
60
  // Check if workflow explicitly skips this phase
53
61
  if (workflowConfig.phases.skip && workflowConfig.phases.skip.includes(candidate)) {
54
62
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: workflow ${feature.workflow} skips this phase`));
63
+ skippedPhases.push(candidate);
55
64
  continue;
56
65
  }
57
66
 
@@ -60,6 +69,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
60
69
  for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
61
70
  if (phases.includes(candidate) && candidate !== combinedName) {
62
71
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
72
+ skippedPhases.push(candidate);
63
73
  continue;
64
74
  }
65
75
  }
@@ -72,6 +82,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
72
82
  Object.keys(workflowConfig.phases.combined).includes(candidate);
73
83
  if (!isCombinedPhase) {
74
84
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: not in workflow run list`));
85
+ skippedPhases.push(candidate);
75
86
  continue;
76
87
  }
77
88
  }
@@ -83,6 +94,7 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
83
94
  // Evaluate condition (simple conditions for now)
84
95
  if (condition === '!hasUIAgents' && !hasUIAgentsActive(process.cwd(), featureName)) {
85
96
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
97
+ skippedPhases.push(candidate);
86
98
  continue;
87
99
  }
88
100
  }
@@ -93,15 +105,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
93
105
  if (!workflowConfig) {
94
106
  // Skip optional phases if flag set or no relevant agents/outputs
95
107
  if (phaseDef?.optional && skipOptional) {
108
+ skippedPhases.push(candidate);
96
109
  continue;
97
110
  }
98
111
 
99
- // Skip uiux if no UI agents are active
112
+ // Skip uiux if no UI design agents are active
100
113
  if (candidate === 'uiux') {
101
- const uiAgents = ['blazor-builder', 'uiux-designer', 'nextjs-expert', 'ui-ux-designer'];
102
- const hasUiAgent = feature.activeAgents?.some(a => uiAgents.includes(a));
103
- if (!hasUiAgent && skipOptional !== false) {
104
- console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI agents active`));
114
+ const hasUiDesignAgent = feature.activeAgents?.some(a => uiDesignAgents.includes(a));
115
+ if (!hasUiDesignAgent && skipOptional !== false) {
116
+ console.log(chalk.gray(` ⏩ Skipping ${candidate}: no UI design agents active`));
117
+ skippedPhases.push(candidate);
105
118
  continue;
106
119
  }
107
120
  }
@@ -111,15 +124,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
111
124
  const isSimple = feature.workflow === 'fast-track';
112
125
  if (isSimple && skipOptional !== false) {
113
126
  console.log(chalk.gray(` ⏩ Skipping ${candidate}: fast-track workflow`));
127
+ skippedPhases.push(candidate);
114
128
  continue;
115
129
  }
116
130
  }
117
131
  }
118
132
 
119
- return candidate;
133
+ return { nextPhase: candidate, skippedPhases };
120
134
  }
121
135
 
122
- return null;
136
+ return { nextPhase: null, skippedPhases };
123
137
  }
124
138
 
125
139
  /**
@@ -138,7 +152,8 @@ export async function advancePhaseCommand(feature, options = {}) {
138
152
  process.exit(1);
139
153
  }
140
154
 
141
- const currentPhase = featureData.phase;
155
+ const featureFolderPath = join(process.cwd(), '.morph', 'features', feature);
156
+ const currentPhase = featureData.phase || derivePhase(featureFolderPath);
142
157
  const currentPhaseDef = PHASES[currentPhase];
143
158
 
144
159
  console.log(chalk.gray('Feature:'), feature);
@@ -146,7 +161,7 @@ export async function advancePhaseCommand(feature, options = {}) {
146
161
  console.log(chalk.gray('Workflow:'), featureData.workflow || 'auto');
147
162
 
148
163
  // Determine next phase
149
- const nextPhase = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
164
+ const { nextPhase, skippedPhases } = getNextPhase(currentPhase, featureData, options.skipOptional, feature);
150
165
 
151
166
  if (!nextPhase) {
152
167
  console.log(chalk.green('\n✓ Feature is at the final phase!'));
@@ -277,7 +292,7 @@ export async function advancePhaseCommand(feature, options = {}) {
277
292
  }
278
293
 
279
294
  // === GATE 5: Tasks Content Validation ===
280
- // Validate tasks.json structure when advancing to implement
295
+ // Validate tasks.md structure when advancing to implement
281
296
  if (currentPhase === 'tasks' && nextPhase === 'implement') {
282
297
  if (featureData.outputs?.tasks?.created) {
283
298
  const tasksContentValidation = validateTasksContent(featureData.outputs.tasks.path);
@@ -326,6 +341,10 @@ export async function advancePhaseCommand(feature, options = {}) {
326
341
  const state = loadState();
327
342
  state.features[feature].phase = nextPhase;
328
343
  state.features[feature].updatedAt = new Date().toISOString();
344
+ if (skippedPhases.length > 0) {
345
+ const existingSkipped = state.features[feature].skippedPhases || [];
346
+ state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
347
+ }
329
348
  saveState(state);
330
349
 
331
350
  // Run PhaseAdvanced agent-teams hook (non-blocking)
@@ -343,6 +362,13 @@ export async function advancePhaseCommand(feature, options = {}) {
343
362
  // Hook executor unavailable — non-blocking
344
363
  }
345
364
 
365
+ // Emit Tier-4 validator dispatch when advancing into implement or sync phase
366
+ const phasesNeedingValidation = ['implement', 'sync'];
367
+ if (phasesNeedingValidation.includes(nextPhase) || phasesNeedingValidation.includes(currentPhase)) {
368
+ const validationPhase = phasesNeedingValidation.includes(nextPhase) ? nextPhase : currentPhase;
369
+ await emitValidatorDispatch(feature, validationPhase, process.cwd());
370
+ }
371
+
346
372
  console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
347
373
 
348
374
  // Show what's needed in the new phase
@@ -447,3 +473,52 @@ function designSystemGate(feature, projectPath = '.') {
447
473
  ]
448
474
  };
449
475
  }
476
+
477
+ /**
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.
481
+ * Non-blocking — fails silently.
482
+ *
483
+ * @param {string} featureName
484
+ * @param {string} phase
485
+ * @param {string} cwd
486
+ */
487
+ async function emitValidatorDispatch(featureName, phase, cwd) {
488
+ 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
+ });
509
+
510
+ 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.'
516
+ };
517
+
518
+ console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
519
+ console.log(JSON.stringify(dispatch, null, 2));
520
+ console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
521
+ } catch {
522
+ // Non-blocking — fail silently
523
+ }
524
+ }
@@ -3,5 +3,6 @@
3
3
  */
4
4
  export { stateCommand } from './state.js';
5
5
  export { advancePhaseCommand } from './advance-phase.js';
6
- export { approveCommand, rejectCommand, approvalStatusCommand } from './approve.js';
6
+ export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
7
7
  export { validatePhaseCommand } from './validate-phase.js';
8
+ export { phaseRunCommand } from './phase-runner.js';
@@ -0,0 +1,215 @@
1
+ /**
2
+ * MORPH-SPEC Phase Runner
3
+ *
4
+ * Executes an automated phase chain loop for a feature.
5
+ * Reads eligibility at each step, auto-advances when eligible, and
6
+ * pauses on blockers (blocked_tasks, low_pass_rate, trust_too_low).
7
+ *
8
+ * Usage:
9
+ * morph-spec phase run <feature>
10
+ * morph-spec phase run <feature> --dry-run
11
+ * morph-spec phase run <feature> --max-phases 3
12
+ *
13
+ * Eligibility gates:
14
+ * - Required outputs present on disk
15
+ * - No blocked tasks in validationHistory
16
+ * - Validation pass rate >= minPassRate (from workflow config)
17
+ * - Trust level meets gate requirements (or gates already approved)
18
+ *
19
+ * Level 3 autonomy: when run with zero-touch workflow + trust=maximum,
20
+ * executes proposal → design → tasks → implement without human input,
21
+ * pausing only on escalation triggers.
22
+ */
23
+
24
+ import chalk from 'chalk';
25
+ import { join } from 'path';
26
+ import { checkPhaseEligibility, computePassRate } from '../../lib/phase-chain/eligibility-checker.js';
27
+ import { advancePhaseCommand } from './advance-phase.js';
28
+ import { loadState } from '../../core/state/state-manager.js';
29
+ import { derivePhase } from '../../core/state/state-manager.js';
30
+ import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
31
+
32
+ // Phases that trigger a pause regardless of eligibility
33
+ const PAUSE_TRIGGERS = ['blocked_tasks', 'trust_too_low'];
34
+
35
+ // Maximum auto-phases before stopping (safety valve)
36
+ const DEFAULT_MAX_PHASES = 6;
37
+
38
+ /**
39
+ * Render a decision tree for --dry-run mode.
40
+ */
41
+ function renderDecisionTree(featureName, eligibility, workflowConfig, dryRun) {
42
+ const { eligible, blockers, currentPhase, nextPhase, passRate } = eligibility;
43
+
44
+ console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
45
+ console.log(chalk.cyan('║ MORPH-SPEC PHASE CHAIN DECISION TREE ║'));
46
+ if (dryRun) console.log(chalk.yellow('║ *** DRY RUN *** ║'));
47
+ console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
48
+
49
+ console.log(chalk.gray('Feature:'), featureName);
50
+ console.log(chalk.gray('Current Phase:'), chalk.cyan(currentPhase));
51
+ console.log(chalk.gray('Next Phase:'), nextPhase ? chalk.cyan(nextPhase) : chalk.gray('(final)'));
52
+ if (passRate !== null) {
53
+ const rateColor = passRate >= 0.95 ? chalk.green : passRate >= 0.80 ? chalk.yellow : chalk.red;
54
+ console.log(chalk.gray('Pass Rate:'), rateColor(`${(passRate * 100).toFixed(0)}%`));
55
+ }
56
+
57
+ if (workflowConfig?.phaseChain) {
58
+ console.log(chalk.gray('Phase Chain:'), chalk.green('enabled'));
59
+ console.log(chalk.gray('Max Auto Phases:'), workflowConfig.phaseChain.maxAutoPhases ?? DEFAULT_MAX_PHASES);
60
+ }
61
+
62
+ console.log();
63
+
64
+ if (eligible) {
65
+ console.log(chalk.green(`✓ ELIGIBLE — can advance from ${currentPhase} → ${nextPhase}`));
66
+ } else {
67
+ console.log(chalk.red(`✗ BLOCKED — cannot advance from ${currentPhase}`));
68
+ console.log(chalk.yellow('\n Blockers:'));
69
+ for (const blocker of blockers) {
70
+ switch (blocker.type) {
71
+ case 'missing_outputs':
72
+ console.log(chalk.red(` • missing_outputs: ${blocker.items.join(', ')}`));
73
+ break;
74
+ case 'blocked_tasks':
75
+ console.log(chalk.red(` • blocked_tasks: ${blocker.items.join(', ')} (attempt ≥ 3, needs human review)`));
76
+ break;
77
+ case 'low_pass_rate':
78
+ console.log(chalk.red(` • low_pass_rate: ${(blocker.current * 100).toFixed(0)}% (required: ${(blocker.required * 100).toFixed(0)}%)`));
79
+ break;
80
+ case 'trust_too_low':
81
+ console.log(chalk.yellow(` • trust_too_low: ${blocker.items.join(', ')}`));
82
+ break;
83
+ default:
84
+ console.log(chalk.red(` • ${blocker.type}: ${(blocker.items || []).join(', ')}`));
85
+ }
86
+ }
87
+ }
88
+
89
+ console.log();
90
+ }
91
+
92
+ /**
93
+ * Main command handler for `morph-spec phase run <feature>`.
94
+ *
95
+ * @param {string} featureName - Feature to run
96
+ * @param {Object} options
97
+ * @param {boolean} [options.dryRun] - Show decision tree without executing
98
+ * @param {number} [options.maxPhases] - Max phases to auto-advance (default: 6)
99
+ * @param {boolean} [options.skipApproval] - Skip approval gate checks
100
+ */
101
+ export async function phaseRunCommand(featureName, options = {}) {
102
+ if (!featureName) {
103
+ console.error(chalk.red('Usage: morph-spec phase run <feature> [--dry-run] [--max-phases N]'));
104
+ process.exit(1);
105
+ }
106
+
107
+ const maxPhases = options.maxPhases ?? DEFAULT_MAX_PHASES;
108
+ const dryRun = options.dryRun ?? false;
109
+
110
+ // Load workflow config for phaseChain settings
111
+ const state = loadState(false);
112
+ const feature = state?.features?.[featureName];
113
+ let workflowConfig = null;
114
+ if (feature?.workflow && feature.workflow !== 'auto') {
115
+ try {
116
+ workflowConfig = getWorkflowConfig(feature.workflow);
117
+ } catch {
118
+ // Non-blocking
119
+ }
120
+ }
121
+
122
+ // Determine pause conditions from workflow
123
+ const pauseOnTypes = workflowConfig?.phaseChain?.pauseOn ?? PAUSE_TRIGGERS;
124
+ const autoChainMax = workflowConfig?.phaseChain?.maxAutoPhases ?? maxPhases;
125
+
126
+ if (!dryRun) {
127
+ console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
128
+ console.log(chalk.cyan('║ MORPH-SPEC PHASE CHAIN RUNNER ║'));
129
+ console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
130
+ console.log(chalk.gray('Feature:'), featureName);
131
+ console.log(chalk.gray('Max auto-phases:'), autoChainMax);
132
+ console.log();
133
+ }
134
+
135
+ let phasesAdvanced = 0;
136
+
137
+ while (phasesAdvanced < autoChainMax) {
138
+ // ── Check eligibility ──────────────────────────────────────────────────
139
+ const eligibility = checkPhaseEligibility(featureName, { projectPath: process.cwd() });
140
+
141
+ // Dry-run: just show the decision tree and exit
142
+ if (dryRun) {
143
+ renderDecisionTree(featureName, eligibility, workflowConfig, true);
144
+ return;
145
+ }
146
+
147
+ // Display current state
148
+ console.log(chalk.gray(`[${phasesAdvanced + 1}/${autoChainMax}] Checking phase: ${eligibility.currentPhase} → ${eligibility.nextPhase || '(final)'}`));
149
+
150
+ if (!eligibility.nextPhase) {
151
+ console.log(chalk.green('\n✓ Feature has reached the final phase!'));
152
+ console.log(chalk.gray(' Consider running: morph-spec generate recap ' + featureName));
153
+ break;
154
+ }
155
+
156
+ if (!eligibility.eligible) {
157
+ // Check if any blocker type triggers a pause
158
+ const shouldPause = eligibility.blockers.some(b => pauseOnTypes.includes(b.type));
159
+
160
+ console.log(chalk.yellow(`\n⚠ Not eligible to advance (${eligibility.currentPhase} → ${eligibility.nextPhase})`));
161
+ for (const blocker of eligibility.blockers) {
162
+ switch (blocker.type) {
163
+ case 'missing_outputs':
164
+ console.log(chalk.gray(` • Missing outputs: ${blocker.items.join(', ')}`));
165
+ break;
166
+ case 'blocked_tasks':
167
+ console.log(chalk.red(` • Blocked tasks: ${blocker.items.join(', ')} — requires human review`));
168
+ break;
169
+ case 'low_pass_rate':
170
+ console.log(chalk.yellow(` • Low pass rate: ${(blocker.current * 100).toFixed(0)}% < ${(blocker.required * 100).toFixed(0)}% required`));
171
+ break;
172
+ case 'trust_too_low':
173
+ console.log(chalk.yellow(` • Trust too low: ${blocker.items.join(', ')}`));
174
+ break;
175
+ default:
176
+ console.log(chalk.gray(` • ${blocker.type}: ${(blocker.items || []).join(', ')}`));
177
+ }
178
+ }
179
+
180
+ if (shouldPause) {
181
+ console.log(chalk.red('\n⛔ Pausing phase chain — human intervention required'));
182
+ console.log(chalk.gray(' Fix the blockers above, then re-run: morph-spec phase run ' + featureName));
183
+ process.exit(1);
184
+ }
185
+
186
+ // Non-pause blocker (e.g. missing_optional_outputs) — still stop
187
+ console.log(chalk.yellow('\n Stopping chain — resolve blockers before continuing'));
188
+ break;
189
+ }
190
+
191
+ // ── Advance the phase ─────────────────────────────────────────────────
192
+ console.log(chalk.green(`\n✓ Eligible — advancing to ${eligibility.nextPhase}`));
193
+
194
+ try {
195
+ await advancePhaseCommand(featureName, {
196
+ skipOptional: false,
197
+ skipApproval: options.skipApproval,
198
+ });
199
+ phasesAdvanced++;
200
+ } catch (err) {
201
+ console.error(chalk.red(`\n✗ Phase advance failed: ${err.message}`));
202
+ console.log(chalk.gray(' Fix the issue and re-run: morph-spec phase run ' + featureName));
203
+ process.exit(1);
204
+ }
205
+ }
206
+
207
+ if (phasesAdvanced >= autoChainMax) {
208
+ console.log(chalk.yellow(`\n⚠ Reached max auto-phases limit (${autoChainMax})`));
209
+ console.log(chalk.gray(' Use --max-phases N to increase the limit'));
210
+ }
211
+
212
+ if (phasesAdvanced > 0 && !dryRun) {
213
+ console.log(chalk.cyan(`\n ${phasesAdvanced} phase(s) advanced automatically`));
214
+ }
215
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Task management commands
3
- * Wrapper for bin/task-manager.js (CommonJS) to ESM
3
+ * Spawns bin/task-manager.js (ESM) as a subprocess.
4
4
  */
5
5
 
6
6
  import { spawn } from 'child_process';
@@ -9,7 +9,7 @@ import { dirname, join } from 'path';
9
9
  import chalk from 'chalk';
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.cjs');
12
+ const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.js');
13
13
 
14
14
  /**
15
15
  * Execute task-manager.js with given arguments
@@ -41,9 +41,30 @@ function executeTaskManager(args) {
41
41
  export async function taskDoneCommand(featureName, taskIds, options) {
42
42
  try {
43
43
  const args = ['done', featureName, ...taskIds];
44
- if (options.skipValidation) {
45
- args.push('--skip-validation');
44
+ if (options.skipValidation) args.push('--skip-validation');
45
+ if (options.dryRun) args.push('--dry-run');
46
+ await executeTaskManager(args);
47
+ } catch (error) {
48
+ console.error(chalk.red(`Error: ${error.message}`));
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Bulk-complete tasks command
55
+ */
56
+ export async function taskBulkDoneCommand(featureName, rangeArg, options) {
57
+ try {
58
+ const args = ['bulk-done', featureName];
59
+ if (options.all) {
60
+ args.push('--all');
61
+ } else if (options.from && options.to) {
62
+ args.push('--from', options.from, '--to', options.to);
63
+ } else if (rangeArg) {
64
+ args.push(rangeArg);
46
65
  }
66
+ if (options.skipValidation) args.push('--skip-validation');
67
+ if (options.dryRun) args.push('--dry-run');
47
68
  await executeTaskManager(args);
48
69
  } catch (error) {
49
70
  console.error(chalk.red(`Error: ${error.message}`));
@@ -26,8 +26,9 @@ 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: '1-design', phase: 'clarify', protected: false },
29
+ clarifications:{ filename: 'clarifications.md', phaseDir: '2-clarify', phase: 'clarify', protected: false },
30
30
  contracts: { filename: 'contracts.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
31
+ contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
31
32
  tasks: { filename: 'tasks.md', phaseDir: '3-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
32
33
  uiDesignSystem:{ filename: 'design-system.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
33
34
  uiMockups: { filename: 'mockups.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
@@ -136,12 +136,13 @@ export function hasUIAgentsActive(projectPath, featureName) {
136
136
  return false;
137
137
  }
138
138
 
139
- // UI agents that require design system
139
+ // UI design agents that require a design system before implement phase.
140
+ // Implementation-only agents (blazor-builder, nextjs-expert, css-specialist)
141
+ // build from existing designs and do NOT trigger this gate.
140
142
  const uiAgents = [
141
- 'blazor-builder',
142
143
  'ui-designer',
143
- 'css-specialist',
144
- 'nextjs-expert'
144
+ 'uiux-designer',
145
+ 'ui-ux-designer',
145
146
  ];
146
147
 
147
148
  return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
@@ -175,6 +175,22 @@ function buildRecapMarkdown(data) {
175
175
  }
176
176
  md += `\n`;
177
177
  }
178
+ md += `> **Note:** Errors above are from morph-spec static validators (may include false positives).\n`;
179
+ md += `> Verify actual build status with the commands in the section below.\n\n`;
180
+ }
181
+
182
+ // Suggested Quality Verification
183
+ const agents = agentsSummary;
184
+ const hasNextjs = agents.some(a => ['nextjs-expert', 'css-specialist', 'ui-designer'].includes(a));
185
+ const hasBlazor = agents.some(a => ['blazor-builder', 'blazor-specialist', 'dotnet-senior'].includes(a));
186
+ if (hasNextjs || hasBlazor) {
187
+ md += `## Suggested Quality Verification\n\n`;
188
+ if (hasNextjs) {
189
+ md += `**Next.js / TypeScript:**\n\`\`\`bash\nnpx tsc --noEmit\nnpx eslint . --max-warnings 0\n\`\`\`\n\n`;
190
+ }
191
+ if (hasBlazor) {
192
+ md += `**Blazor / .NET:**\n\`\`\`bash\ndotnet build\ndotnet test\n\`\`\`\n\n`;
193
+ }
178
194
  }
179
195
 
180
196
  // Contract Coverage