@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.
- package/README.md +2 -2
- package/bin/morph-spec.js +23 -2
- package/bin/task-manager.js +202 -14
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +113 -116
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +27 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +17 -2
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
- package/framework/hooks/shared/phase-utils.js +1 -1
- package/framework/hooks/shared/state-reader.js +1 -0
- package/framework/skills/README.md +1 -0
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
- package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
- package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
- package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
- package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
- package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
- package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
- package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
- package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
- package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
- package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
- package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
- package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
- package/framework/squad-templates/backend-only.json +14 -1
- package/framework/squad-templates/frontend-only.json +14 -1
- package/framework/squad-templates/full-stack.json +25 -8
- package/framework/standards/STANDARDS.json +631 -86
- package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
- package/framework/templates/project/validate.js +122 -0
- package/framework/workflows/configs/zero-touch.json +7 -0
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +53 -10
- package/src/commands/state/advance-phase.js +56 -0
- package/src/commands/state/index.js +2 -1
- package/src/commands/state/phase-runner.js +215 -0
- package/src/commands/tasks/task.js +23 -2
- package/src/core/paths/output-schema.js +1 -1
- package/src/lib/generators/recap-generator.js +16 -0
- package/src/lib/orchestration/team-orchestrator.js +171 -89
- package/src/lib/phase-chain/eligibility-checker.js +243 -0
- package/src/lib/standards/digest-builder.js +231 -0
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
- package/src/lib/validators/nextjs/next-component-validator.js +2 -0
- package/src/lib/validators/validation-runner.js +2 -2
- package/src/utils/file-copier.js +1 -0
- 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
|
@@ -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
|
-
|
|
274
|
-
if (!agentData.teammate?.spawn_prompt) continue;
|
|
281
|
+
const isTier4 = agentData.tier === 4;
|
|
275
282
|
|
|
276
|
-
//
|
|
277
|
-
if (!
|
|
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
|
|
286
|
-
role: agentData.teammate.
|
|
309
|
+
icon: agentData.teammate?.icon || '🤖',
|
|
310
|
+
role: agentData.teammate?.role || agentData.description || '',
|
|
287
311
|
parallelGroup: group,
|
|
288
312
|
canRunParallel: true,
|
|
289
|
-
|
|
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
|
|
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,
|
|
6
|
+
export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
|
|
7
7
|
export { validatePhaseCommand } from './validate-phase.js';
|
|
8
|
+
export { phaseRunCommand } from './phase-runner.js';
|