@polymorphism-tech/morph-spec 9.5.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 (80) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +13 -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 +43 -11
  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/heavy-gate.js +5 -3
  12. package/framework/hooks/claude-code/core/test-budget.js +89 -0
  13. package/framework/hooks/claude-code/statusline.sh +11 -11
  14. package/framework/hooks/shared/e2e-check.js +0 -0
  15. package/framework/hooks/shared/feature-locator.js +7 -5
  16. package/framework/hooks/shared/heavy-command.js +72 -1
  17. package/framework/hooks/shared/heavy-config.js +28 -1
  18. package/framework/hooks/shared/heavy-slots.js +41 -5
  19. package/framework/hooks/shared/host-services.js +433 -0
  20. package/framework/schemas/config.schema.json +65 -2
  21. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  22. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  23. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  24. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  25. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  26. package/framework/skills/core/morph-media/SKILL.md +1 -1
  27. package/framework/skills/core/morph-plan/SKILL.md +3 -3
  28. package/framework/skills/core/morph-review/SKILL.md +3 -2
  29. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  30. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  31. package/framework/standards/STANDARDS.json +2 -1
  32. package/framework/standards/infrastructure/docker/local-compose-isolation.md +15 -1
  33. package/framework/templates/IDE-SNIPPETS.md +262 -262
  34. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  35. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  36. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  37. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  38. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  39. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  40. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  41. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  42. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  43. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  44. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  45. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  46. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  47. package/framework/templates/state.template.json +17 -17
  48. package/framework/templates/ui/design-system.css +226 -226
  49. package/package.json +1 -1
  50. package/src/commands/e2e/e2e.js +50 -24
  51. package/src/commands/observability/cost.js +12 -0
  52. package/src/commands/project/env.js +3 -3
  53. package/src/commands/project/heavy.js +10 -3
  54. package/src/commands/project/worktree.js +9 -4
  55. package/src/commands/state/advance.js +3 -0
  56. package/src/commands/state/create.js +3 -3
  57. package/src/commands/state/delete.js +1 -1
  58. package/src/commands/state/finish.js +20 -4
  59. package/src/commands/validation/mutate.js +2 -2
  60. package/src/commands/validation/plan-check.js +1 -0
  61. package/src/commands/validation/verify.js +3 -0
  62. package/src/dashboard/app/main.jsx +13 -13
  63. package/src/dashboard/vite.config.js +24 -24
  64. package/src/lib/e2e/compose-env.js +26 -3
  65. package/src/lib/e2e/stack-teardown.js +19 -4
  66. package/src/lib/heavy/gate.js +23 -4
  67. package/src/lib/heavy/live-stacks.js +64 -0
  68. package/src/lib/review-reports.js +21 -10
  69. package/src/lib/risk-detector.js +9 -1
  70. package/src/lib/tasks/test-runner.js +3 -0
  71. package/src/lib/telemetry/metrics.js +47 -3
  72. package/src/lib/validators/packages/package-validator.js +2 -2
  73. package/src/lib/worktree/docker-audit.js +7 -3
  74. package/src/lib/worktree/env-allocator.js +27 -5
  75. package/src/lib/worktree/ignored-triage.js +8 -0
  76. package/src/utils/agents-installer.js +4 -1
  77. package/src/utils/file-copier.js +26 -4
  78. package/src/utils/hooks-installer.js +4 -4
  79. package/framework/templates/.idea/morph-templates.xml +0 -92
  80. 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.5.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,17 +17,18 @@ 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';
30
30
  import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
31
+ import { memoryBusyHint } from '../../lib/heavy/live-stacks.js';
31
32
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
32
33
 
33
34
  const EXIT_USAGE = 2;
@@ -104,7 +105,8 @@ export async function e2eUpCommand(featureName, options = {}) {
104
105
  console.log(chalk.yellow(`⚠️ ${isolation.reason}`));
105
106
  }
106
107
 
107
- if (!isDockerAvailable(ctx.cwd)) {
108
+ const exec = options._exec ?? defaultExec;
109
+ if (!isDockerAvailable(ctx.cwd, exec)) {
108
110
  console.error(chalk.red('\n❌ Docker is not available in this environment.\n'));
109
111
  process.exit(EXIT_USAGE);
110
112
  }
@@ -113,58 +115,63 @@ export async function e2eUpCommand(featureName, options = {}) {
113
115
  let result;
114
116
  try {
115
117
  gate.enter('e2e');
116
- result = bringUpStack(ctx.cwd, {
118
+ result = bringUpE2e(ctx.cwd, {
119
+ config: ctx.config,
117
120
  composeFile: ctx.composeFile,
118
121
  projectName: ctx.projectName,
119
122
  gitCommonDir: ctx.gitCommonDir,
120
123
  env: ctx.env,
121
124
  forceFresh: Boolean(options.fresh),
122
- });
125
+ }, exec);
123
126
  } catch (err) {
124
127
  if (!(err instanceof HeavyBusyError)) throw err;
125
128
  if (options.json) {
126
129
  console.log(JSON.stringify({ status: 'busy', feature: featureName, busy: { reason: err.reason, waitedMs: err.waitedMs } }, null, 2));
127
130
  }
128
131
  console.error(chalk.yellow(`\n⏳ e2e up não rodou: ${err.message}. Não é vermelho — a fila já esperou o prazo dela: não repita em laço — siga sem esta rodada e avise o usuário (\`morph-spec heavy status\`).\n`));
132
+ const hint = memoryBusyHint(err, ctx.cwd);
133
+ if (hint) console.error(chalk.yellow(`${hint}\n`));
129
134
  process.exit(EXIT_BUSY);
130
135
  } finally {
131
136
  gate.release();
132
137
  }
133
138
 
134
- const auth =
135
- result.status === 'pass'
136
- ? prepareAuth(ctx.cwd, { config: ctx.config, env: ctx.env, forceSeed: Boolean(options.fresh) })
137
- : { status: 'skip', reason: null };
138
-
139
+ const { auth, hostServices } = result;
139
140
  // A seedCommand that `mode` switches off is reported, never swallowed.
140
141
  const warnings = [];
141
142
  if (isolation.status === 'warn') warnings.push(isolation.reason);
142
- if (auth.warning) warnings.push(auth.warning);
143
+ warnings.push(...result.warnings);
143
144
 
144
- const failed = result.status === 'fail' || auth.status === 'fail';
145
+ const failed = result.status === 'fail';
145
146
  const payload = {
146
147
  feature: featureName,
147
148
  project: ctx.projectName,
148
149
  tree: ctx.tree,
149
150
  composeFile: ctx.composeFile,
150
151
  status: failed ? 'fail' : 'pass',
151
- reused: result.reused,
152
+ reused: result.stack.reused,
152
153
  reason: result.reason,
153
154
  auth: { status: auth.status, reason: auth.reason },
155
+ ...(hostServices.status === 'skip' ? {} : { hostServices: hostServices.services }),
154
156
  ...(warnings.length ? { warnings } : {}),
157
+ ...(failed && result.output ? { output: result.output } : {}),
155
158
  };
156
159
 
157
160
  if (options.json) {
158
161
  console.log(JSON.stringify(payload, null, 2));
159
162
  } else if (failed) {
160
- console.error(chalk.red(`\n✗ ${result.reason || auth.reason}\n`));
163
+ console.error(chalk.red(`\n✗ ${result.reason}\n`));
161
164
  if (result.output) console.error(result.output);
165
+ for (const w of result.warnings) console.error(chalk.yellow(`⚠️ ${w}`));
162
166
  } else {
163
- 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)');
164
168
  console.log(chalk.green(`\n✓ Stack up — ${ctx.projectName} ${how}`));
165
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
+ }
166
173
  if (auth.reason) console.log(chalk.gray(` auth: ${auth.reason}`));
167
- if (auth.warning) console.log(chalk.yellow(`⚠️ ${auth.warning}`));
174
+ for (const w of result.warnings) console.log(chalk.yellow(`⚠️ ${w}`));
168
175
  console.log('');
169
176
  }
170
177
 
@@ -182,17 +189,19 @@ export async function e2eStatusCommand(featureName, options = {}) {
182
189
  const ctx = resolveContext(featureName, options);
183
190
  if (!ctx) process.exit(EXIT_USAGE);
184
191
 
192
+ const exec = options._exec ?? defaultExec;
185
193
  const status = getStackStatus(ctx.cwd, {
186
194
  composeFile: ctx.composeFile,
187
195
  projectName: ctx.projectName,
188
196
  env: ctx.env,
189
- });
197
+ }, exec);
190
198
 
191
- const stamp = computeSourceStamp(ctx.cwd);
199
+ const stamp = computeSourceStamp(ctx.cwd, exec);
192
200
  const previous = ctx.gitCommonDir ? readStamp(ctx.gitCommonDir, ctx.projectName) : null;
193
201
  const fresh = Boolean(stamp && previous?.stamp === stamp);
194
202
 
195
203
  const spec = resolveSpecCommand(ctx.cwd, ctx.config, ctx.config?.project?.frontendPath);
204
+ const hostServices = ctx.gitCommonDir ? hostServicesStatus(ctx.gitCommonDir, ctx.projectName) : [];
196
205
 
197
206
  const payload = {
198
207
  feature: featureName,
@@ -210,6 +219,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
210
219
  state: s.State ?? null,
211
220
  health: s.Health || null,
212
221
  })),
222
+ ...(hostServices.length ? { hostServices } : {}),
213
223
  };
214
224
 
215
225
  if (options.json) {
@@ -230,6 +240,10 @@ export async function e2eStatusCommand(featureName, options = {}) {
230
240
  for (const s of payload.services) {
231
241
  console.log(chalk.gray(` · ${s.name}: ${s.state}${s.health ? ` / ${s.health}` : ''}`));
232
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
+ }
233
247
  console.log('');
234
248
  }
235
249
 
@@ -242,30 +256,42 @@ export async function e2eDownCommand(featureName, options = {}) {
242
256
  const ctx = resolveContext(featureName, options);
243
257
  if (!ctx) process.exit(EXIT_USAGE);
244
258
 
259
+ const hostServices = stopHostServices(ctx.gitCommonDir, ctx.projectName);
245
260
  const result = tearDownStack(ctx.cwd, {
246
261
  composeFile: ctx.composeFile,
247
262
  projectName: ctx.projectName,
248
263
  env: ctx.env,
249
264
  volumes: Boolean(options.volumes),
250
- });
265
+ }, options._exec ?? defaultExec);
251
266
  const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
252
267
  const cleanup = afterHeavyRun({ projectPath: ctx.cwd, config: readHeavyConfig(ctx.cwd) });
253
268
 
254
269
  if (options.json) {
255
270
  console.log(JSON.stringify(
256
- { 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
+ },
257
275
  null, 2,
258
276
  ));
259
- } else if (result.status === 'pass') {
260
- console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}`));
261
- console.log(chalk.gray(` tree: ${ctx.tree}\n`));
262
277
  } else {
263
- 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}`));
264
286
  }
265
287
 
266
288
  process.exit(0);
267
289
  }
268
290
 
291
+ function stoppedAnything({ killed, skipped, warnings }) {
292
+ return killed.length + skipped.length + warnings.length > 0;
293
+ }
294
+
269
295
  function readConfig(cwd) {
270
296
  try {
271
297
  const path = join(cwd, '.morph', 'config', 'config.json');