@polymorphism-tech/morph-spec 9.6.0 → 9.7.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 (65) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +12 -5
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/agents.json +3 -3
  7. package/framework/docs/CLI.md +35 -7
  8. package/framework/evals/review-checklist.md +2 -2
  9. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  10. package/framework/hooks/claude-code/core/README.md +4 -0
  11. package/framework/hooks/claude-code/core/test-budget.js +89 -0
  12. package/framework/hooks/claude-code/statusline.sh +11 -11
  13. package/framework/hooks/shared/e2e-check.js +0 -0
  14. package/framework/hooks/shared/heavy-command.js +60 -1
  15. package/framework/hooks/shared/host-services.js +433 -0
  16. package/framework/schemas/config.schema.json +53 -1
  17. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  18. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  19. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  20. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  21. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  22. package/framework/skills/core/morph-media/SKILL.md +1 -1
  23. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  24. package/framework/skills/core/morph-review/SKILL.md +3 -2
  25. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  26. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  27. package/framework/standards/STANDARDS.json +2 -1
  28. package/framework/standards/infrastructure/docker/local-compose-isolation.md +14 -1
  29. package/framework/templates/IDE-SNIPPETS.md +262 -262
  30. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  31. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  32. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  33. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  34. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  35. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  36. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  37. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  38. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  39. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  40. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  41. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  42. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  43. package/framework/templates/state.template.json +17 -17
  44. package/framework/templates/ui/design-system.css +226 -226
  45. package/package.json +1 -1
  46. package/src/commands/e2e/e2e.js +47 -24
  47. package/src/commands/observability/cost.js +12 -0
  48. package/src/commands/project/worktree.js +7 -3
  49. package/src/commands/state/advance.js +3 -0
  50. package/src/commands/state/create.js +3 -3
  51. package/src/commands/state/delete.js +1 -1
  52. package/src/commands/state/finish.js +3 -1
  53. package/src/commands/validation/plan-check.js +1 -0
  54. package/src/dashboard/app/main.jsx +13 -13
  55. package/src/dashboard/vite.config.js +24 -24
  56. package/src/lib/e2e/stack-teardown.js +19 -4
  57. package/src/lib/review-reports.js +21 -10
  58. package/src/lib/tasks/test-runner.js +3 -0
  59. package/src/lib/telemetry/metrics.js +47 -3
  60. package/src/lib/validators/packages/package-validator.js +2 -2
  61. package/src/utils/agents-installer.js +4 -1
  62. package/src/utils/file-copier.js +26 -4
  63. package/src/utils/hooks-installer.js +4 -4
  64. package/framework/templates/.idea/morph-templates.xml +0 -92
  65. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
@@ -1,226 +1,226 @@
1
- /**
2
- * Design System - {Project Name}
3
- *
4
- * CSS Variables generated from design-system.md
5
- * Use estas variáveis em todo o projeto para manter consistência visual.
6
- *
7
- * Template MORPH-SPEC by Polymorphism Tech
8
- */
9
-
10
- :root {
11
- /* ========================================
12
- * CORES PRINCIPAIS
13
- * ======================================== */
14
-
15
- --color-primary: #{hex da cor primária};
16
- --color-primary-hover: #{hex da cor primária mais escura};
17
- --color-primary-active: #{hex da cor primária ainda mais escura};
18
-
19
- --color-secondary: #{hex da cor secundária};
20
- --color-secondary-hover: #{hex};
21
- --color-secondary-active: #{hex};
22
-
23
- /* ========================================
24
- * CORES DE ESTADO
25
- * ======================================== */
26
-
27
- --color-success: #{hex};
28
- --color-success-bg: #{hex com transparência ou shade mais claro};
29
- --color-success-border: #{hex};
30
-
31
- --color-error: #{hex};
32
- --color-error-bg: #{hex};
33
- --color-error-border: #{hex};
34
-
35
- --color-warning: #{hex};
36
- --color-warning-bg: #{hex};
37
- --color-warning-border: #{hex};
38
-
39
- --color-info: #{hex};
40
- --color-info-bg: #{hex};
41
- --color-info-border: #{hex};
42
-
43
- /* ========================================
44
- * ESCALA DE NEUTROS (Cinzas)
45
- * ======================================== */
46
-
47
- --color-gray-50: #{hex};
48
- --color-gray-100: #{hex};
49
- --color-gray-200: #{hex};
50
- --color-gray-300: #{hex};
51
- --color-gray-400: #{hex};
52
- --color-gray-500: #{hex};
53
- --color-gray-600: #{hex};
54
- --color-gray-700: #{hex};
55
- --color-gray-800: #{hex};
56
- --color-gray-900: #{hex};
57
-
58
- /* ========================================
59
- * TIPOGRAFIA - FONTES
60
- * ======================================== */
61
-
62
- --font-sans: '{Font Name}', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
63
- --font-serif: '{Font Name}', Georgia, 'Times New Roman', serif;
64
- --font-mono: '{Font Name}', 'Fira Code', 'Courier New', Consolas, monospace;
65
-
66
- /* ========================================
67
- * TIPOGRAFIA - TAMANHOS
68
- * ======================================== */
69
-
70
- --text-xs: 0.75rem; /* 12px */
71
- --text-sm: 0.875rem; /* 14px */
72
- --text-base: 1rem; /* 16px */
73
- --text-lg: 1.125rem; /* 18px */
74
- --text-xl: 1.25rem; /* 20px */
75
- --text-2xl: 1.5rem; /* 24px */
76
- --text-3xl: 1.875rem; /* 30px */
77
- --text-4xl: 2.25rem; /* 36px */
78
- --text-5xl: 3rem; /* 48px */
79
-
80
- /* ========================================
81
- * TIPOGRAFIA - LINE HEIGHTS
82
- * ======================================== */
83
-
84
- --leading-none: 1;
85
- --leading-tight: 1.25;
86
- --leading-snug: 1.375;
87
- --leading-normal: 1.5;
88
- --leading-relaxed: 1.625;
89
- --leading-loose: 2;
90
-
91
- /* ========================================
92
- * TIPOGRAFIA - FONT WEIGHTS
93
- * ======================================== */
94
-
95
- --font-light: 300;
96
- --font-regular: 400;
97
- --font-medium: 500;
98
- --font-semibold: 600;
99
- --font-bold: 700;
100
- --font-extrabold: 800;
101
-
102
- /* ========================================
103
- * ESPAÇAMENTO
104
- * Baseado em múltiplos de {base}px (ex: 4px)
105
- * ======================================== */
106
-
107
- --space-0: 0;
108
- --space-1: 0.25rem; /* 4px */
109
- --space-2: 0.5rem; /* 8px */
110
- --space-3: 0.75rem; /* 12px */
111
- --space-4: 1rem; /* 16px */
112
- --space-5: 1.25rem; /* 20px */
113
- --space-6: 1.5rem; /* 24px */
114
- --space-7: 1.75rem; /* 28px */
115
- --space-8: 2rem; /* 32px */
116
- --space-10: 2.5rem; /* 40px */
117
- --space-12: 3rem; /* 48px */
118
- --space-16: 4rem; /* 64px */
119
- --space-20: 5rem; /* 80px */
120
- --space-24: 6rem; /* 96px */
121
-
122
- /* ========================================
123
- * BORDER RADIUS
124
- * ======================================== */
125
-
126
- --rounded-none: 0;
127
- --rounded-sm: 0.125rem; /* 2px */
128
- --rounded-md: 0.375rem; /* 6px */
129
- --rounded-lg: 0.5rem; /* 8px */
130
- --rounded-xl: 0.75rem; /* 12px */
131
- --rounded-2xl: 1rem; /* 16px */
132
- --rounded-full: 9999px;
133
-
134
- /* ========================================
135
- * SOMBRAS (Shadows)
136
- * ======================================== */
137
-
138
- --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
139
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
140
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
141
- --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
142
- --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
143
-
144
- /* ========================================
145
- * Z-INDEX LAYERS
146
- * ======================================== */
147
-
148
- --z-0: 0;
149
- --z-10: 10;
150
- --z-20: 20;
151
- --z-30: 30;
152
- --z-40: 40;
153
- --z-50: 50; /* Modals, dropdowns */
154
- --z-100: 100; /* Tooltips */
155
- --z-max: 9999; /* Toasts, notifications */
156
-
157
- /* ========================================
158
- * TRANSITIONS / ANIMATIONS
159
- * ======================================== */
160
-
161
- --transition-fast: 150ms ease-in-out;
162
- --transition-base: 250ms ease-in-out;
163
- --transition-slow: 350ms ease-in-out;
164
-
165
- --ease-in: cubic-bezier(0.4, 0, 1, 1);
166
- --ease-out: cubic-bezier(0, 0, 0.2, 1);
167
- --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
168
-
169
- /* ========================================
170
- * BREAKPOINTS (informativo - usar em media queries)
171
- * ======================================== */
172
-
173
- /* @media (min-width: 640px) - sm */
174
- /* @media (min-width: 768px) - md */
175
- /* @media (min-width: 1024px) - lg */
176
- /* @media (min-width: 1280px) - xl */
177
- /* @media (min-width: 1536px) - 2xl */
178
- }
179
-
180
- /* ========================================
181
- * MODO ESCURO (Dark Mode)
182
- * Opcional - ajuste as cores para dark theme
183
- * ======================================== */
184
-
185
- @media (prefers-color-scheme: dark) {
186
- :root {
187
- /* Inverter cores conforme necessário */
188
- --color-gray-50: #{hex do gray-900};
189
- --color-gray-900: #{hex do gray-50};
190
- /* ... outras inversões */
191
- }
192
- }
193
-
194
- /* ========================================
195
- * CLASSES UTILITÁRIAS (Exemplo)
196
- * ======================================== */
197
-
198
- /* Exemplo de uso das variáveis */
199
- .btn-primary {
200
- background-color: var(--color-primary);
201
- color: white;
202
- padding: var(--space-2) var(--space-4);
203
- border-radius: var(--rounded-md);
204
- font-weight: var(--font-semibold);
205
- transition: background-color var(--transition-base);
206
- }
207
-
208
- .btn-primary:hover {
209
- background-color: var(--color-primary-hover);
210
- }
211
-
212
- .card {
213
- background-color: white;
214
- padding: var(--space-6);
215
- border-radius: var(--rounded-lg);
216
- box-shadow: var(--shadow-md);
217
- }
218
-
219
- .text-muted {
220
- color: var(--color-gray-600);
221
- font-size: var(--text-sm);
222
- }
223
-
224
- /* ========================================
225
- * FIM DO DESIGN SYSTEM
226
- * ======================================== */
1
+ /**
2
+ * Design System - {Project Name}
3
+ *
4
+ * CSS Variables generated from design-system.md
5
+ * Use estas variáveis em todo o projeto para manter consistência visual.
6
+ *
7
+ * Template MORPH-SPEC by Polymorphism Tech
8
+ */
9
+
10
+ :root {
11
+ /* ========================================
12
+ * CORES PRINCIPAIS
13
+ * ======================================== */
14
+
15
+ --color-primary: #{hex da cor primária};
16
+ --color-primary-hover: #{hex da cor primária mais escura};
17
+ --color-primary-active: #{hex da cor primária ainda mais escura};
18
+
19
+ --color-secondary: #{hex da cor secundária};
20
+ --color-secondary-hover: #{hex};
21
+ --color-secondary-active: #{hex};
22
+
23
+ /* ========================================
24
+ * CORES DE ESTADO
25
+ * ======================================== */
26
+
27
+ --color-success: #{hex};
28
+ --color-success-bg: #{hex com transparência ou shade mais claro};
29
+ --color-success-border: #{hex};
30
+
31
+ --color-error: #{hex};
32
+ --color-error-bg: #{hex};
33
+ --color-error-border: #{hex};
34
+
35
+ --color-warning: #{hex};
36
+ --color-warning-bg: #{hex};
37
+ --color-warning-border: #{hex};
38
+
39
+ --color-info: #{hex};
40
+ --color-info-bg: #{hex};
41
+ --color-info-border: #{hex};
42
+
43
+ /* ========================================
44
+ * ESCALA DE NEUTROS (Cinzas)
45
+ * ======================================== */
46
+
47
+ --color-gray-50: #{hex};
48
+ --color-gray-100: #{hex};
49
+ --color-gray-200: #{hex};
50
+ --color-gray-300: #{hex};
51
+ --color-gray-400: #{hex};
52
+ --color-gray-500: #{hex};
53
+ --color-gray-600: #{hex};
54
+ --color-gray-700: #{hex};
55
+ --color-gray-800: #{hex};
56
+ --color-gray-900: #{hex};
57
+
58
+ /* ========================================
59
+ * TIPOGRAFIA - FONTES
60
+ * ======================================== */
61
+
62
+ --font-sans: '{Font Name}', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
63
+ --font-serif: '{Font Name}', Georgia, 'Times New Roman', serif;
64
+ --font-mono: '{Font Name}', 'Fira Code', 'Courier New', Consolas, monospace;
65
+
66
+ /* ========================================
67
+ * TIPOGRAFIA - TAMANHOS
68
+ * ======================================== */
69
+
70
+ --text-xs: 0.75rem; /* 12px */
71
+ --text-sm: 0.875rem; /* 14px */
72
+ --text-base: 1rem; /* 16px */
73
+ --text-lg: 1.125rem; /* 18px */
74
+ --text-xl: 1.25rem; /* 20px */
75
+ --text-2xl: 1.5rem; /* 24px */
76
+ --text-3xl: 1.875rem; /* 30px */
77
+ --text-4xl: 2.25rem; /* 36px */
78
+ --text-5xl: 3rem; /* 48px */
79
+
80
+ /* ========================================
81
+ * TIPOGRAFIA - LINE HEIGHTS
82
+ * ======================================== */
83
+
84
+ --leading-none: 1;
85
+ --leading-tight: 1.25;
86
+ --leading-snug: 1.375;
87
+ --leading-normal: 1.5;
88
+ --leading-relaxed: 1.625;
89
+ --leading-loose: 2;
90
+
91
+ /* ========================================
92
+ * TIPOGRAFIA - FONT WEIGHTS
93
+ * ======================================== */
94
+
95
+ --font-light: 300;
96
+ --font-regular: 400;
97
+ --font-medium: 500;
98
+ --font-semibold: 600;
99
+ --font-bold: 700;
100
+ --font-extrabold: 800;
101
+
102
+ /* ========================================
103
+ * ESPAÇAMENTO
104
+ * Baseado em múltiplos de {base}px (ex: 4px)
105
+ * ======================================== */
106
+
107
+ --space-0: 0;
108
+ --space-1: 0.25rem; /* 4px */
109
+ --space-2: 0.5rem; /* 8px */
110
+ --space-3: 0.75rem; /* 12px */
111
+ --space-4: 1rem; /* 16px */
112
+ --space-5: 1.25rem; /* 20px */
113
+ --space-6: 1.5rem; /* 24px */
114
+ --space-7: 1.75rem; /* 28px */
115
+ --space-8: 2rem; /* 32px */
116
+ --space-10: 2.5rem; /* 40px */
117
+ --space-12: 3rem; /* 48px */
118
+ --space-16: 4rem; /* 64px */
119
+ --space-20: 5rem; /* 80px */
120
+ --space-24: 6rem; /* 96px */
121
+
122
+ /* ========================================
123
+ * BORDER RADIUS
124
+ * ======================================== */
125
+
126
+ --rounded-none: 0;
127
+ --rounded-sm: 0.125rem; /* 2px */
128
+ --rounded-md: 0.375rem; /* 6px */
129
+ --rounded-lg: 0.5rem; /* 8px */
130
+ --rounded-xl: 0.75rem; /* 12px */
131
+ --rounded-2xl: 1rem; /* 16px */
132
+ --rounded-full: 9999px;
133
+
134
+ /* ========================================
135
+ * SOMBRAS (Shadows)
136
+ * ======================================== */
137
+
138
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
139
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
140
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
141
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
142
+ --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
143
+
144
+ /* ========================================
145
+ * Z-INDEX LAYERS
146
+ * ======================================== */
147
+
148
+ --z-0: 0;
149
+ --z-10: 10;
150
+ --z-20: 20;
151
+ --z-30: 30;
152
+ --z-40: 40;
153
+ --z-50: 50; /* Modals, dropdowns */
154
+ --z-100: 100; /* Tooltips */
155
+ --z-max: 9999; /* Toasts, notifications */
156
+
157
+ /* ========================================
158
+ * TRANSITIONS / ANIMATIONS
159
+ * ======================================== */
160
+
161
+ --transition-fast: 150ms ease-in-out;
162
+ --transition-base: 250ms ease-in-out;
163
+ --transition-slow: 350ms ease-in-out;
164
+
165
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
166
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
167
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
168
+
169
+ /* ========================================
170
+ * BREAKPOINTS (informativo - usar em media queries)
171
+ * ======================================== */
172
+
173
+ /* @media (min-width: 640px) - sm */
174
+ /* @media (min-width: 768px) - md */
175
+ /* @media (min-width: 1024px) - lg */
176
+ /* @media (min-width: 1280px) - xl */
177
+ /* @media (min-width: 1536px) - 2xl */
178
+ }
179
+
180
+ /* ========================================
181
+ * MODO ESCURO (Dark Mode)
182
+ * Opcional - ajuste as cores para dark theme
183
+ * ======================================== */
184
+
185
+ @media (prefers-color-scheme: dark) {
186
+ :root {
187
+ /* Inverter cores conforme necessário */
188
+ --color-gray-50: #{hex do gray-900};
189
+ --color-gray-900: #{hex do gray-50};
190
+ /* ... outras inversões */
191
+ }
192
+ }
193
+
194
+ /* ========================================
195
+ * CLASSES UTILITÁRIAS (Exemplo)
196
+ * ======================================== */
197
+
198
+ /* Exemplo de uso das variáveis */
199
+ .btn-primary {
200
+ background-color: var(--color-primary);
201
+ color: white;
202
+ padding: var(--space-2) var(--space-4);
203
+ border-radius: var(--rounded-md);
204
+ font-weight: var(--font-semibold);
205
+ transition: background-color var(--transition-base);
206
+ }
207
+
208
+ .btn-primary:hover {
209
+ background-color: var(--color-primary-hover);
210
+ }
211
+
212
+ .card {
213
+ background-color: white;
214
+ padding: var(--space-6);
215
+ border-radius: var(--rounded-lg);
216
+ box-shadow: var(--shadow-md);
217
+ }
218
+
219
+ .text-muted {
220
+ color: var(--color-gray-600);
221
+ font-size: var(--text-sm);
222
+ }
223
+
224
+ /* ========================================
225
+ * FIM DO DESIGN SYSTEM
226
+ * ======================================== */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "9.6.0",
3
+ "version": "9.7.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -8,7 +8,7 @@ import { existsSync, readFileSync } from 'fs';
8
8
  import { join, resolve } from 'path';
9
9
 
10
10
  import {
11
- bringUpStack,
11
+ bringUpE2e,
12
12
  composeProjectName,
13
13
  computeSourceStamp,
14
14
  decideIsolation,
@@ -17,13 +17,13 @@ import {
17
17
  getStackStatus,
18
18
  isDockerAvailable,
19
19
  lintComposeIsolation,
20
- prepareAuth,
21
20
  readStamp,
22
21
  resolveComposeFile,
23
22
  resolveSpecCommand,
24
23
  tearDownStack,
25
24
  } from '../../../framework/hooks/shared/e2e-check.js';
26
25
  import { isSecondaryWorktree } from '../../../framework/hooks/shared/git-paths.js';
26
+ import { hostServicesStatus, stopHostServices } from '../../../framework/hooks/shared/host-services.js';
27
27
  import { buildComposeEnv } from '../../lib/e2e/compose-env.js';
28
28
  import { findWorktreeForFeature, resolvePrimaryRoot } from '../project/worktree.js';
29
29
  import { createHeavyGate, HeavyBusyError } from '../../lib/heavy/gate.js';
@@ -105,7 +105,8 @@ export async function e2eUpCommand(featureName, options = {}) {
105
105
  console.log(chalk.yellow(`⚠️ ${isolation.reason}`));
106
106
  }
107
107
 
108
- if (!isDockerAvailable(ctx.cwd)) {
108
+ const exec = options._exec ?? defaultExec;
109
+ if (!isDockerAvailable(ctx.cwd, exec)) {
109
110
  console.error(chalk.red('\n❌ Docker is not available in this environment.\n'));
110
111
  process.exit(EXIT_USAGE);
111
112
  }
@@ -114,13 +115,14 @@ export async function e2eUpCommand(featureName, options = {}) {
114
115
  let result;
115
116
  try {
116
117
  gate.enter('e2e');
117
- result = bringUpStack(ctx.cwd, {
118
+ result = bringUpE2e(ctx.cwd, {
119
+ config: ctx.config,
118
120
  composeFile: ctx.composeFile,
119
121
  projectName: ctx.projectName,
120
122
  gitCommonDir: ctx.gitCommonDir,
121
123
  env: ctx.env,
122
124
  forceFresh: Boolean(options.fresh),
123
- });
125
+ }, exec);
124
126
  } catch (err) {
125
127
  if (!(err instanceof HeavyBusyError)) throw err;
126
128
  if (options.json) {
@@ -134,40 +136,42 @@ export async function e2eUpCommand(featureName, options = {}) {
134
136
  gate.release();
135
137
  }
136
138
 
137
- const auth =
138
- result.status === 'pass'
139
- ? prepareAuth(ctx.cwd, { config: ctx.config, env: ctx.env, forceSeed: Boolean(options.fresh) })
140
- : { status: 'skip', reason: null };
141
-
139
+ const { auth, hostServices } = result;
142
140
  // A seedCommand that `mode` switches off is reported, never swallowed.
143
141
  const warnings = [];
144
142
  if (isolation.status === 'warn') warnings.push(isolation.reason);
145
- if (auth.warning) warnings.push(auth.warning);
143
+ warnings.push(...result.warnings);
146
144
 
147
- const failed = result.status === 'fail' || auth.status === 'fail';
145
+ const failed = result.status === 'fail';
148
146
  const payload = {
149
147
  feature: featureName,
150
148
  project: ctx.projectName,
151
149
  tree: ctx.tree,
152
150
  composeFile: ctx.composeFile,
153
151
  status: failed ? 'fail' : 'pass',
154
- reused: result.reused,
152
+ reused: result.stack.reused,
155
153
  reason: result.reason,
156
154
  auth: { status: auth.status, reason: auth.reason },
155
+ ...(hostServices.status === 'skip' ? {} : { hostServices: hostServices.services }),
157
156
  ...(warnings.length ? { warnings } : {}),
157
+ ...(failed && result.output ? { output: result.output } : {}),
158
158
  };
159
159
 
160
160
  if (options.json) {
161
161
  console.log(JSON.stringify(payload, null, 2));
162
162
  } else if (failed) {
163
- console.error(chalk.red(`\n✗ ${result.reason || auth.reason}\n`));
163
+ console.error(chalk.red(`\n✗ ${result.reason}\n`));
164
164
  if (result.output) console.error(result.output);
165
+ for (const w of result.warnings) console.error(chalk.yellow(`⚠️ ${w}`));
165
166
  } else {
166
- const how = result.reused ? chalk.gray(`(${result.reason})`) : chalk.gray('(built)');
167
+ const how = result.stack.reused ? chalk.gray(`(${result.stack.reason})`) : chalk.gray('(built)');
167
168
  console.log(chalk.green(`\n✓ Stack up — ${ctx.projectName} ${how}`));
168
169
  console.log(chalk.gray(` tree: ${ctx.tree}`));
170
+ for (const s of hostServices.services) {
171
+ console.log(chalk.gray(` host: ${s.name} pid ${s.pid} (${s.reused ? 'reused' : 'started'}) — log ${s.log}`));
172
+ }
169
173
  if (auth.reason) console.log(chalk.gray(` auth: ${auth.reason}`));
170
- if (auth.warning) console.log(chalk.yellow(`⚠️ ${auth.warning}`));
174
+ for (const w of result.warnings) console.log(chalk.yellow(`⚠️ ${w}`));
171
175
  console.log('');
172
176
  }
173
177
 
@@ -185,17 +189,19 @@ export async function e2eStatusCommand(featureName, options = {}) {
185
189
  const ctx = resolveContext(featureName, options);
186
190
  if (!ctx) process.exit(EXIT_USAGE);
187
191
 
192
+ const exec = options._exec ?? defaultExec;
188
193
  const status = getStackStatus(ctx.cwd, {
189
194
  composeFile: ctx.composeFile,
190
195
  projectName: ctx.projectName,
191
196
  env: ctx.env,
192
- });
197
+ }, exec);
193
198
 
194
- const stamp = computeSourceStamp(ctx.cwd);
199
+ const stamp = computeSourceStamp(ctx.cwd, exec);
195
200
  const previous = ctx.gitCommonDir ? readStamp(ctx.gitCommonDir, ctx.projectName) : null;
196
201
  const fresh = Boolean(stamp && previous?.stamp === stamp);
197
202
 
198
203
  const spec = resolveSpecCommand(ctx.cwd, ctx.config, ctx.config?.project?.frontendPath);
204
+ const hostServices = ctx.gitCommonDir ? hostServicesStatus(ctx.gitCommonDir, ctx.projectName) : [];
199
205
 
200
206
  const payload = {
201
207
  feature: featureName,
@@ -213,6 +219,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
213
219
  state: s.State ?? null,
214
220
  health: s.Health || null,
215
221
  })),
222
+ ...(hostServices.length ? { hostServices } : {}),
216
223
  };
217
224
 
218
225
  if (options.json) {
@@ -233,6 +240,10 @@ export async function e2eStatusCommand(featureName, options = {}) {
233
240
  for (const s of payload.services) {
234
241
  console.log(chalk.gray(` · ${s.name}: ${s.state}${s.health ? ` / ${s.health}` : ''}`));
235
242
  }
243
+ for (const h of hostServices) {
244
+ const health = h.health === null ? 'no health check' : h.health ? 'healthy' : 'unhealthy';
245
+ console.log(chalk.gray(` · host ${h.name}: pid ${h.pid} ${h.alive ? 'alive' : 'dead'} / ${health}`));
246
+ }
236
247
  console.log('');
237
248
  }
238
249
 
@@ -245,30 +256,42 @@ export async function e2eDownCommand(featureName, options = {}) {
245
256
  const ctx = resolveContext(featureName, options);
246
257
  if (!ctx) process.exit(EXIT_USAGE);
247
258
 
259
+ const hostServices = stopHostServices(ctx.gitCommonDir, ctx.projectName);
248
260
  const result = tearDownStack(ctx.cwd, {
249
261
  composeFile: ctx.composeFile,
250
262
  projectName: ctx.projectName,
251
263
  env: ctx.env,
252
264
  volumes: Boolean(options.volumes),
253
- });
265
+ }, options._exec ?? defaultExec);
254
266
  const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
255
267
  const cleanup = afterHeavyRun({ projectPath: ctx.cwd, config: readHeavyConfig(ctx.cwd) });
256
268
 
257
269
  if (options.json) {
258
270
  console.log(JSON.stringify(
259
- { feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted, cleanup },
271
+ {
272
+ feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted, cleanup,
273
+ ...(stoppedAnything(hostServices) ? { hostServices } : {}),
274
+ },
260
275
  null, 2,
261
276
  ));
262
- } else if (result.status === 'pass') {
263
- console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}`));
264
- console.log(chalk.gray(` tree: ${ctx.tree}\n`));
265
277
  } else {
266
- console.log(chalk.yellow(`\n⚠️ Teardown reported a problem (not fatal):\n${result.output}\n`));
278
+ if (result.status === 'pass') {
279
+ console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}`));
280
+ console.log(chalk.gray(` tree: ${ctx.tree}\n`));
281
+ } else {
282
+ console.log(chalk.yellow(`\n⚠️ Teardown reported a problem (not fatal):\n${result.output}\n`));
283
+ }
284
+ if (hostServices.killed.length) console.log(chalk.gray(` host services stopped: ${hostServices.killed.join(', ')}`));
285
+ for (const w of hostServices.warnings) console.log(chalk.yellow(`⚠️ ${w}`));
267
286
  }
268
287
 
269
288
  process.exit(0);
270
289
  }
271
290
 
291
+ function stoppedAnything({ killed, skipped, warnings }) {
292
+ return killed.length + skipped.length + warnings.length > 0;
293
+ }
294
+
272
295
  function readConfig(cwd) {
273
296
  try {
274
297
  const path = join(cwd, '.morph', 'config', 'config.json');
@@ -108,6 +108,18 @@ function printMetricsBody(m) {
108
108
  console.log(` ${klass.padEnd(12)} ${fmtDuration(t.measuredMs / 1000).padStart(8)} ${t.runs} execução(ões)${unmeasured}`);
109
109
  }
110
110
 
111
+ const tov = m.testOutsideVerify;
112
+ const timed = (t) => `${String(t.runs).padStart(4)}× ${fmtDuration(t.measuredMs / 1000).padStart(8)}`
113
+ + (t.unmeasured ? chalk.gray(` (${t.unmeasured} sem duração)`) : '');
114
+ console.log('\n' + chalk.white.bold('Teste fora do verify (sub-agentes)')
115
+ + chalk.gray(` · de ${tov.total.runs} testes+verifies, ${fmtDuration(tov.total.measuredMs / 1000)} cronometrados`
116
+ + (tov.total.unmeasured ? `, ${tov.total.unmeasured} sem duração` : '')));
117
+ console.log(` sub-agente rodando verify cheio ${timed(tov.subagentVerify)}`);
118
+ console.log(` implementador, 3ª rodada em diante ${timed(tov.implementerRound3Plus)}`);
119
+ console.log(` agente de review rodando teste ${timed(tov.reviewTests)}`);
120
+ console.log(chalk.gray(` ${tov.reviewTests.byPersona.runs}× pela persona morph-* (o que o hook test-budget reconhece);`
121
+ + ' o resto veio de um nome de dispatch (ver-, rev-, gate3-…)'));
122
+
111
123
  console.log('\n' + chalk.white.bold('Rerun do mesmo comando em ≤10 min (mesma feature; o filtro de saída não conta)'));
112
124
  console.log(` total: ${pct(m.reruns.rate)} repetidas (${m.reruns.reruns}/${m.reruns.runs}) · ${pct(m.reruns.burstRate)} em rajada — testes, builds e verifies`);
113
125
  for (const [kind, r] of Object.entries(m.rerunsByKind).sort((a, b) => b[1].runs - a[1].runs)) {