@polymorphism-tech/morph-spec 4.8.14 → 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 (71) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/task-manager.js +202 -14
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +113 -116
  8. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  9. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  10. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
  11. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  12. package/framework/hooks/claude-code/session-start/inject-morph-context.js +27 -0
  13. package/framework/hooks/claude-code/stop/validate-completion.js +17 -2
  14. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  15. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  16. package/framework/hooks/shared/phase-utils.js +1 -1
  17. package/framework/hooks/shared/state-reader.js +1 -0
  18. package/framework/skills/README.md +1 -0
  19. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  20. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  21. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  22. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  23. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  24. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  25. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  26. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  27. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  28. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  29. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  30. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  31. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  32. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  33. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  34. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  35. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
  36. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  37. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  38. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  39. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  40. package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
  41. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  42. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  43. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
  44. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  45. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  46. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
  47. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  48. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  49. package/framework/squad-templates/backend-only.json +14 -1
  50. package/framework/squad-templates/frontend-only.json +14 -1
  51. package/framework/squad-templates/full-stack.json +25 -8
  52. package/framework/standards/STANDARDS.json +631 -86
  53. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  54. package/framework/templates/project/validate.js +122 -0
  55. package/framework/workflows/configs/zero-touch.json +7 -0
  56. package/package.json +1 -1
  57. package/src/commands/agents/dispatch-agents.js +53 -10
  58. package/src/commands/state/advance-phase.js +56 -0
  59. package/src/commands/state/index.js +2 -1
  60. package/src/commands/state/phase-runner.js +215 -0
  61. package/src/commands/tasks/task.js +23 -2
  62. package/src/core/paths/output-schema.js +1 -1
  63. package/src/lib/generators/recap-generator.js +16 -0
  64. package/src/lib/orchestration/team-orchestrator.js +171 -89
  65. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  66. package/src/lib/standards/digest-builder.js +231 -0
  67. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  68. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  69. package/src/lib/validators/validation-runner.js +2 -2
  70. package/src/utils/file-copier.js +1 -0
  71. package/src/utils/hooks-installer.js +31 -7
@@ -0,0 +1,213 @@
1
+ # Aesthetic Direction
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 0 (always load)
5
+ > **Keywords:** design, aesthetic, typography, color, motion, identity, visual
6
+ > **Load When:** phase-uiux
7
+
8
+ ## Design Thinking Methodology
9
+
10
+ Antes de qualquer especificação técnica, responder 4 perguntas para definir direção estética:
11
+
12
+ 1. **Purpose** — Que problema resolve? Quem usa? Qual o contexto profissional?
13
+ 2. **Tone** — Qual direção? (ver tabela Aesthetic Directions abaixo)
14
+ 3. **Differentiation** — Qual o 1 elemento memorável desta interface?
15
+ 4. **Constraints** — Framework, performance budget, brand guidelines existentes
16
+
17
+ **CRITICAL:** Commitar à direção ANTES das specs técnicas. Documentar em `ui-design-system.md`
18
+ na seção `## Aesthetic Direction` usando o template no final deste standard.
19
+
20
+ ---
21
+
22
+ ## Aesthetic Directions (Enterprise-Adapted)
23
+
24
+ | Direction | Feel | Typography | Color | Motion |
25
+ |-----------|------|-----------|-------|--------|
26
+ | **Minimal Refined** | Luxuoso, silencioso, preciso | Serif elegante + sans leve | Monocromático + 1 accent sharp | Transitions suaves, sem floreios |
27
+ | **Editorial** | Magazine profissional, opinionado | Display forte + body serifado | Contrastes altos, tipo tipográfico | Reveals dramáticos, scroll-driven |
28
+ | **Soft Professional** | Acolhedor, confiável, humano | Sans rounded + body neutro | Pastéis quentes + accent vibrante | Entrada gentil, micro-interações sutis |
29
+ | **Industrial** | Técnico, funcional, direto | Mono/condensed + utility sans | Neutros + accent de alerta | Sem animação ou movimento mínimo |
30
+ | **Modern Luxury** | Exclusivo, refinado, material | Display itálico + sans premium | Escuro profundo + gold/copper accent | Parallax sutil, hover lift, shine effect |
31
+
32
+ > **Nota:** Sem "brutalist chaos" — este guia é adaptado para contexto enterprise.
33
+
34
+ ---
35
+
36
+ ## Typography
37
+
38
+ ### Princípios
39
+
40
+ - **Display font DISTINTIVA**: define o caráter da interface — evitar fontes genéricas como display
41
+ - **Body font LEGÍVEL**: otimizada para leitura de conteúdo, densidade de informação
42
+ - **Contraste de peso**: heading e body devem ter pesos visivelmente diferentes
43
+
44
+ ### Font Pairs por Direção
45
+
46
+ | Direction | Display | Body | Fonte |
47
+ |-----------|---------|------|-------|
48
+ | Minimal Refined | Cormorant Garamond | DM Sans | Google Fonts |
49
+ | Editorial | Playfair Display | Source Sans 3 | Google Fonts |
50
+ | Soft Professional | Nunito | Inter | Google Fonts |
51
+ | Industrial | JetBrains Mono | IBM Plex Sans | Google Fonts |
52
+ | Modern Luxury | Bodoni Moda | Sora | Google Fonts |
53
+
54
+ ### Anti-padrões
55
+
56
+ - ❌ Inter ou Roboto como fonte de display (genérico, sem caráter)
57
+ - ❌ System fonts sem razão arquitetural explícita
58
+ - ❌ Mesmo peso para heading e body (sem hierarquia)
59
+ - ❌ Mais de 2 famílias de fontes sem justificativa visual clara
60
+
61
+ ---
62
+
63
+ ## Color Philosophy
64
+
65
+ ### Estrutura
66
+
67
+ ```
68
+ 1 dominant (60%) + 1 sharp accent (10%) + semantic colors (30%)
69
+ ```
70
+
71
+ - **Dominant**: define o mood — não precisa ser "segura", precisa ser intencional
72
+ - **Sharp accent**: cria tensão e foco — deve contrastar com dominant
73
+ - **Semantic**: success, warning, error, info — seguir WCAG AA (4.5:1 mínimo)
74
+
75
+ ### Template CSS Variables
76
+
77
+ ```css
78
+ :root {
79
+ /* Dominant palette */
80
+ --color-primary: #1a1a2e;
81
+ --color-primary-light: #16213e;
82
+ --color-primary-dark: #0f3460;
83
+
84
+ /* Sharp accent */
85
+ --color-accent: #e94560;
86
+ --color-accent-hover: #c73652;
87
+
88
+ /* Surface */
89
+ --color-surface: #ffffff;
90
+ --color-surface-alt: #f8f9fa;
91
+ --color-border: rgba(0,0,0,0.08);
92
+
93
+ /* Semantic */
94
+ --color-success: #22c55e;
95
+ --color-warning: #f59e0b;
96
+ --color-error: #ef4444;
97
+ --color-info: #3b82f6;
98
+
99
+ /* Text */
100
+ --color-text-primary: #1a1a1a;
101
+ --color-text-secondary: #6b7280;
102
+ --color-text-muted: #9ca3af;
103
+ }
104
+ ```
105
+
106
+ ### Anti-padrões
107
+
108
+ - ❌ Gradiente roxo em fundo branco (AI cliché #1)
109
+ - ❌ Paleta de 5 cores de peso igual (sem dominant + accent)
110
+ - ❌ Cinza neutro como "cor segura" sem intenção estética
111
+ - ❌ Accent color igual ao primary (sem tensão visual)
112
+
113
+ ---
114
+
115
+ ## Motion Principles
116
+
117
+ ### Filosofia
118
+
119
+ > 1 page-load bem orquestrado > micro-interações espalhadas por todo lado
120
+
121
+ Criar delight através de momentos específicos, não movimento constante.
122
+
123
+ ### High-Impact Patterns
124
+
125
+ | Pattern | Implementação | Quando usar |
126
+ |---------|--------------|-------------|
127
+ | **Entry stagger** | `animation-delay: 0.05s * index`, `animation-fill-mode: backwards` | Listas, cards, grids |
128
+ | **Hover lift** | `transform: translateY(-4px)`, `box-shadow: var(--shadow-lg)` | Cards interativos, CTAs |
129
+ | **Success bounce** | `@keyframes bounce` com `cubic-bezier(0.68, -0.55, 0.265, 1.55)` | Confirmações, saves |
130
+ | **Data reveal** | `clip-path: inset(0 100% 0 0)` → `inset(0 0% 0 0)` | Métricas, progressos |
131
+
132
+ ### Acessibilidade (obrigatório)
133
+
134
+ ```css
135
+ @media (prefers-reduced-motion: reduce) {
136
+ *, *::before, *::after {
137
+ animation-duration: 0.01ms !important;
138
+ transition-duration: 0.01ms !important;
139
+ }
140
+ }
141
+ ```
142
+
143
+ > **Ref:** `framework/standards/frontend/design-system/animations.md` para keyframes e patterns completos.
144
+
145
+ ### Anti-padrões
146
+
147
+ - ❌ Animação em absolutamente tudo (motion fatigue)
148
+ - ❌ Transitions > 500ms para interações de UI comuns
149
+ - ❌ Sem `prefers-reduced-motion` (acessibilidade obrigatória)
150
+ - ❌ `animation-fill-mode` ausente em stagger (causa flash)
151
+
152
+ ---
153
+
154
+ ## Spatial Composition
155
+
156
+ ### Padrões por Tipo de Página
157
+
158
+ **Feature Hero** — Produto, landing, showcase:
159
+ - Elemento central oversized (tipografia ou visual)
160
+ - Generous negative space ao redor
161
+ - 1 elemento grid-breaking (overlap, diagonal, fora do ritmo)
162
+
163
+ **Dashboard** — Métricas, dados, monitoramento:
164
+ - Controlled density — cada cm² carrega informação
165
+ - Hierarquia clara: KPIs > charts > tabelas
166
+ - Micro-grid consistente (8px ou 4px base)
167
+
168
+ **Form Pages** — Input, configuração, onboarding:
169
+ - Campo único em foco (single-column preferred)
170
+ - Progress visual explícito
171
+ - Espaço generoso entre grupos de campos
172
+
173
+ ### Princípio Fundamental
174
+
175
+ > Generous negative space OU controlled density — nunca o "meio-termo confortável"
176
+
177
+ Interfaces no meio-termo parecem vazias E confusas ao mesmo tempo.
178
+
179
+ ---
180
+
181
+ ## Visual Atmosphere
182
+
183
+ Técnicas por direção para criar profundidade e caráter:
184
+
185
+ | Direction | Técnica Principal | Detalhe |
186
+ |-----------|-----------------|---------|
187
+ | Minimal Refined | Subtle noise texture | `filter: url(#noise)` SVG ou `backdrop-filter` |
188
+ | Editorial | Dramatic type shadows | `text-shadow` em display com offset grande |
189
+ | Soft Professional | Frosted glass cards | `backdrop-filter: blur(12px)`, `background: rgba(...)` |
190
+ | Industrial | Hard borders + grid lines | `border: 1px solid`, sem arredondamento, monospace |
191
+ | Modern Luxury | Gradient mesh background | Multi-stop radial gradients sobrepostos |
192
+
193
+ ---
194
+
195
+ ## Template de Documentação
196
+
197
+ Usar no `ui-design-system.md` de cada feature (seção `## Aesthetic Direction`):
198
+
199
+ ```markdown
200
+ ## Aesthetic Direction
201
+
202
+ - **Direction**: [Minimal Refined / Editorial / Soft Professional / Industrial / Modern Luxury]
203
+ - **Tone**: [1-2 frases sobre o feel — ex: "Sóbrio e confiante, como um produto B2B premium"]
204
+ - **Differentiator**: [O que torna memorável — ex: "Entrada staggered dos cards + tipografia editorial forte"]
205
+ - **Font Pair**: [Display font] + [Body font]
206
+ - **Color Philosophy**: [Dominant] + [Accent] — [rationale de 1 frase]
207
+ - **Motion Intent**: [Padrão de entrada + interações-chave]
208
+ - **Composition**: [Abordagem de layout — ex: "Dashboard controlled density, 8px grid"]
209
+ ```
210
+
211
+ ---
212
+
213
+ *MORPH-SPEC by Polymorphism Tech*
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/validate.js — generated by morph-spec setup-infra
4
+ *
5
+ * Thin validator entry-point called by morph-spec validate-completion hook:
6
+ * node bin/validate.js <validator> [--json]
7
+ *
8
+ * Supported validators: architecture, blazor, blazor-concurrency, nextjs-component,
9
+ * packages, design-system, ui-contrast, css
10
+ *
11
+ * Exits 0 on pass, 1 on validation errors.
12
+ * --json flag prints structured JSON to stdout.
13
+ */
14
+
15
+ const validator = process.argv[2];
16
+ const isJson = process.argv.includes('--json');
17
+
18
+ if (!validator) {
19
+ console.error('Usage: node bin/validate.js <validator> [--json]');
20
+ process.exit(1);
21
+ }
22
+
23
+ const { execSync } = await import('child_process');
24
+ const { existsSync } = await import('fs');
25
+ const { join } = await import('path');
26
+
27
+ const cwd = process.cwd();
28
+
29
+ const results = {
30
+ validator,
31
+ errors: 0,
32
+ warnings: 0,
33
+ passed: true,
34
+ issues: [],
35
+ };
36
+
37
+ /**
38
+ * Run a shell command and capture output.
39
+ * Returns { success, stdout, stderr }.
40
+ */
41
+ function runCommand(cmd, options = {}) {
42
+ try {
43
+ const stdout = execSync(cmd, { cwd, stdio: ['pipe', 'pipe', 'pipe'], ...options }).toString();
44
+ return { success: true, stdout, stderr: '' };
45
+ } catch (err) {
46
+ return {
47
+ success: false,
48
+ stdout: err.stdout?.toString() || '',
49
+ stderr: err.stderr?.toString() || err.message,
50
+ };
51
+ }
52
+ }
53
+
54
+ // ── Stack detection ───────────────────────────────────────────────────────────
55
+
56
+ const hasFrontend = existsSync(join(cwd, 'src/frontend')) || existsSync(join(cwd, 'frontend'));
57
+ const hasBackend = existsSync(join(cwd, 'src/backend')) || existsSync(join(cwd, 'backend'));
58
+ const hasSln = existsSync(join(cwd, 'src/backend/backend.sln')) ||
59
+ existsSync(join(cwd, 'src/backend/Backend.sln'));
60
+
61
+ const frontendDir = existsSync(join(cwd, 'src/frontend')) ? 'src/frontend' : 'frontend';
62
+ const backendDir = existsSync(join(cwd, 'src/backend')) ? 'src/backend' : 'backend';
63
+
64
+ // ── Validator dispatch ────────────────────────────────────────────────────────
65
+
66
+ switch (validator) {
67
+ case 'architecture':
68
+ case 'blazor':
69
+ case 'blazor-concurrency': {
70
+ if (hasBackend && hasSln) {
71
+ const slnFile = hasSln ? (
72
+ existsSync(join(cwd, 'src/backend/backend.sln')) ? 'backend.sln' : 'Backend.sln'
73
+ ) : '.';
74
+ const result = runCommand(`dotnet build ${slnFile} --no-restore -warnaserror`, { cwd: join(cwd, backendDir) });
75
+ if (!result.success) {
76
+ results.errors++;
77
+ results.passed = false;
78
+ results.issues.push({ level: 'error', message: `dotnet build failed: ${result.stderr.split('\n')[0]}` });
79
+ }
80
+ }
81
+ break;
82
+ }
83
+
84
+ case 'nextjs-component':
85
+ case 'packages':
86
+ case 'design-system': {
87
+ if (hasFrontend) {
88
+ const result = runCommand('npx tsc --noEmit', { cwd: join(cwd, frontendDir) });
89
+ if (!result.success) {
90
+ results.errors++;
91
+ results.passed = false;
92
+ results.issues.push({ level: 'error', message: `TypeScript errors found. Run: npx tsc --noEmit` });
93
+ }
94
+ }
95
+ break;
96
+ }
97
+
98
+ case 'ui-contrast':
99
+ case 'css': {
100
+ // CSS/contrast validators are visual — report as informational
101
+ results.issues.push({ level: 'info', message: `${validator} requires manual review` });
102
+ break;
103
+ }
104
+
105
+ default:
106
+ results.issues.push({ level: 'warning', message: `Unknown validator: ${validator}` });
107
+ break;
108
+ }
109
+
110
+ // ── Output ────────────────────────────────────────────────────────────────────
111
+
112
+ if (isJson) {
113
+ console.log(JSON.stringify(results, null, 2));
114
+ } else {
115
+ const icon = results.passed ? '✅' : '❌';
116
+ console.log(`${icon} Validator: ${validator} — ${results.passed ? 'PASSED' : `FAILED (${results.errors} errors)`}`);
117
+ for (const issue of results.issues) {
118
+ console.log(` [${issue.level}] ${issue.message}`);
119
+ }
120
+ }
121
+
122
+ process.exit(results.passed ? 0 : 1);
@@ -69,6 +69,13 @@
69
69
  "requireHumanForSchemaChanges": false
70
70
  },
71
71
 
72
+ "phaseChain": {
73
+ "enabled": true,
74
+ "pauseOn": ["blocked_tasks", "escalation_required", "low_pass_rate"],
75
+ "continueOn": ["missing_optional_outputs"],
76
+ "maxAutoPhases": 6
77
+ },
78
+
72
79
  "keywords": ["zero-touch", "autonomous", "fully automated", "hands-off", "auto-pilot"],
73
80
 
74
81
  "priority": 5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "4.8.14",
3
+ "version": "4.8.15",
4
4
  "description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -20,6 +20,7 @@ import { readFileSync, existsSync } from 'fs';
20
20
  import { join, dirname } from 'path';
21
21
  import { fileURLToPath } from 'url';
22
22
  import { loadState, stateExists } from '../../core/state/state-manager.js';
23
+ import { buildAgentBriefing, buildValidatorPrompt } from '../../lib/standards/digest-builder.js';
23
24
  import chalk from 'chalk';
24
25
 
25
26
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -115,7 +116,7 @@ function getAgentGroup(agentId, agentData) {
115
116
  * Check whether an agent should be dispatched for the given phase.
116
117
  *
117
118
  * Rules (in priority order):
118
- * 1. Tier-4 validators → never dispatch (no task work)
119
+ * 1. Tier-4 validators → never dispatch (no task work) [overridden by validate mode]
119
120
  * 2. Agent-level `active_phases` array → use that list
120
121
  * 3. Relationships-level `active_phases` array → use that list
121
122
  * 4. `always_active: true` → dispatchable for all DISPATCHABLE_PHASES
@@ -127,7 +128,7 @@ function getAgentGroup(agentId, agentData) {
127
128
  * @returns {boolean}
128
129
  */
129
130
  function isRelevantForPhase(agentId, agentData, phase) {
130
- // Tier-4 validators have no implementation tasks
131
+ // Tier-4 validators have no implementation tasks (validate mode bypasses this)
131
132
  if (agentData.tier === 4) return false;
132
133
 
133
134
  // Explicit active_phases on agent root
@@ -252,6 +253,7 @@ export async function buildDispatchConfig(projectPath, featureName, phase, opts
252
253
 
253
254
  // Collect dispatchable agents
254
255
  // Process all agents: active ones from state + always_active ones not yet in state
256
+ const isValidateMode = opts.mode === 'validate';
255
257
  const processed = new Set();
256
258
  const dispatchableAgents = [];
257
259
 
@@ -261,6 +263,12 @@ export async function buildDispatchConfig(projectPath, featureName, phase, opts
261
263
  ...Object.entries(allAgents)
262
264
  .filter(([id, data]) => !id.startsWith('_comment') && data.always_active)
263
265
  .map(([id]) => id),
266
+ // In validate mode, also include tier-4 validators
267
+ ...(isValidateMode
268
+ ? Object.entries(allAgents)
269
+ .filter(([id, data]) => !id.startsWith('_comment') && data.tier === 4)
270
+ .map(([id]) => id)
271
+ : []),
264
272
  ];
265
273
 
266
274
  for (const agentId of candidateIds) {
@@ -270,23 +278,40 @@ export async function buildDispatchConfig(projectPath, featureName, phase, opts
270
278
  const agentData = allAgents[agentId];
271
279
  if (!agentData || agentId.startsWith('_comment')) continue;
272
280
 
273
- // Must have a spawn_prompt to be dispatchable
274
- if (!agentData.teammate?.spawn_prompt) continue;
281
+ const isTier4 = agentData.tier === 4;
275
282
 
276
- // Phase relevance check
277
- if (!isRelevantForPhase(agentId, agentData, phase)) continue;
283
+ // Must have a spawn_prompt (or be a tier-4 with hook_behavior in validate mode)
284
+ if (!agentData.teammate?.spawn_prompt && !(isValidateMode && isTier4 && agentData.hook_behavior)) continue;
285
+
286
+ // Phase relevance check (tier-4 bypass in validate mode)
287
+ if (!isRelevantForPhase(agentId, agentData, phase) && !(isValidateMode && isTier4)) continue;
278
288
 
279
289
  const group = getAgentGroup(agentId, agentData);
280
290
 
291
+ // Build task prompt: validator prompt for tier-4 in validate mode, else spawn_prompt
292
+ let rawPrompt;
293
+ if (isValidateMode && isTier4) {
294
+ rawPrompt = buildValidatorPrompt(agentId) || agentData.teammate?.spawn_prompt || '';
295
+ } else {
296
+ rawPrompt = agentData.teammate.spawn_prompt;
297
+ }
298
+
299
+ // Append standards digest as Constraints block
300
+ const briefing = buildAgentBriefing(agentId, phase);
301
+ const fullTaskPrompt = briefing
302
+ ? `${rawPrompt}\n\nConstraints:\n${briefing}`
303
+ : rawPrompt;
304
+
281
305
  dispatchableAgents.push({
282
306
  id: agentId,
283
307
  title: agentData.title,
284
308
  tier: agentData.tier,
285
- icon: agentData.teammate.icon || '🤖',
286
- role: agentData.teammate.role,
309
+ icon: agentData.teammate?.icon || '🤖',
310
+ role: agentData.teammate?.role || agentData.description || '',
287
311
  parallelGroup: group,
288
312
  canRunParallel: true,
289
- taskPrompt: agentData.teammate.spawn_prompt,
313
+ canWrite: !isTier4,
314
+ taskPrompt: fullTaskPrompt,
290
315
  });
291
316
  }
292
317
 
@@ -302,6 +327,21 @@ export async function buildDispatchConfig(projectPath, featureName, phase, opts
302
327
 
303
328
  const shouldDispatch = dispatchableAgents.length >= 2;
304
329
 
330
+ // ── Execution mode recommendation ─────────────────────────────────────────
331
+ // Derive recommended mode from agent composition:
332
+ // - 'agent-teams' : 2+ distinct parallel groups AND 5+ total agents (critical multi-domain)
333
+ // - 'subagents' : shouldDispatch=true but below agent-teams threshold
334
+ // - 'single' : no dispatch benefit
335
+ const distinctGroups = new Set(dispatchableAgents.map(a => a.parallelGroup)).size;
336
+ let mode;
337
+ if (shouldDispatch && distinctGroups >= 2 && dispatchableAgents.length >= 5) {
338
+ mode = 'agent-teams';
339
+ } else if (shouldDispatch) {
340
+ mode = 'subagents';
341
+ } else {
342
+ mode = 'single';
343
+ }
344
+
305
345
  // ── Task groups ────────────────────────────────────────────────────────────
306
346
  const taskGroups = {};
307
347
 
@@ -340,6 +380,7 @@ export async function buildDispatchConfig(projectPath, featureName, phase, opts
340
380
  return {
341
381
  feature: featureName,
342
382
  phase,
383
+ mode,
343
384
  shouldDispatch,
344
385
  reason: shouldDispatch
345
386
  ? `${dispatchableAgents.length} dispatchable agents for phase '${phase}'`
@@ -377,7 +418,9 @@ export async function dispatchAgentsCommand(featureName, phase, options = {}) {
377
418
  }
378
419
 
379
420
  try {
380
- const config = await buildDispatchConfig(process.cwd(), featureName, normalizedPhase);
421
+ const dispatchOpts = {};
422
+ if (options.mode) dispatchOpts.mode = options.mode;
423
+ const config = await buildDispatchConfig(process.cwd(), featureName, normalizedPhase, dispatchOpts);
381
424
 
382
425
  if (options.table) {
383
426
  // Human-readable table format
@@ -362,6 +362,13 @@ export async function advancePhaseCommand(feature, options = {}) {
362
362
  // Hook executor unavailable — non-blocking
363
363
  }
364
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
+
365
372
  console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
366
373
 
367
374
  // Show what's needed in the new phase
@@ -466,3 +473,52 @@ function designSystemGate(feature, projectPath = '.') {
466
473
  ]
467
474
  };
468
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';