@polymorphism-tech/morph-spec 10.2.0 → 10.5.3

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 (61) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +9 -0
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/MORPH.md +1 -1
  7. package/framework/agents.json +1 -1
  8. package/framework/docs/CLI.md +22 -2
  9. package/framework/evals/review-checklist.md +5 -5
  10. package/framework/hooks/claude-code/core/heavy-gate.js +21 -2
  11. package/framework/hooks/shared/background-spawn.js +20 -0
  12. package/framework/hooks/shared/graph-refresh.js +2 -1
  13. package/framework/hooks/shared/heavy-config.js +21 -0
  14. package/framework/hooks/shared/host-services.js +9 -6
  15. package/framework/hooks/shared/next-step.js +11 -7
  16. package/framework/rules/plan-grammar.md +4 -1
  17. package/framework/schemas/config.schema.json +39 -0
  18. package/framework/schemas/feature.schema.json +14 -0
  19. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  20. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  21. package/framework/skills/core/morph-apply/SKILL.md +7 -4
  22. package/framework/skills/core/morph-apply/references/machine.md +3 -2
  23. package/framework/skills/core/morph-apply/references/review.md +5 -3
  24. package/framework/skills/core/morph-plan/SKILL.md +4 -3
  25. package/framework/skills/core/morph-plan/references/critique.md +1 -1
  26. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  27. package/framework/templates/IDE-SNIPPETS.md +262 -262
  28. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  29. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  30. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  31. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  32. package/framework/templates/docs/plan.md +2 -1
  33. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  34. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  35. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  36. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  37. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  38. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  39. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  40. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  41. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  42. package/framework/templates/state.template.json +17 -17
  43. package/framework/templates/ui/design-system.css +226 -226
  44. package/package.json +1 -1
  45. package/src/commands/project/heavy.js +13 -0
  46. package/src/commands/validation/verify.js +51 -5
  47. package/src/dashboard/app/main.jsx +13 -13
  48. package/src/dashboard/vite.config.js +24 -24
  49. package/src/lib/heavy/gate.js +34 -2
  50. package/src/lib/heavy/reclaim.js +107 -0
  51. package/src/lib/heavy/watchdog.js +46 -0
  52. package/src/lib/next-step.js +1 -0
  53. package/src/lib/plan/checks.js +1 -1
  54. package/src/lib/procs/prune.js +23 -1
  55. package/src/lib/tasks/test-nodes.js +5 -0
  56. package/src/lib/validators/verify-runner.js +34 -4
  57. package/src/lib/verify/affected.js +141 -0
  58. package/src/lib/verify/detached.js +156 -0
  59. package/src/utils/hooks-installer.js +8 -0
  60. package/framework/templates/.idea/morph-templates.xml +0 -92
  61. 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": "10.2.0",
3
+ "version": "10.5.3",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -7,9 +7,22 @@ import { describeHolder, heavyDir, heavyStatus } from '../../../framework/hooks/
7
7
  import { resolveSpawn } from '../../../framework/hooks/shared/spawn-command.js';
8
8
  import { heavyClass, heavyKind } from '../../../framework/hooks/shared/heavy-command.js';
9
9
  import { memoryBusyHint } from '../../lib/heavy/live-stacks.js';
10
+ import { reclaimUnlessDisabled } from '../../lib/heavy/reclaim.js';
10
11
 
11
12
  const EXIT_BUSY = 3;
12
13
 
14
+ export function heavyReclaimCommand(options = {}) {
15
+ const projectPath = process.cwd();
16
+ const result = reclaimUnlessDisabled({ projectPath, config: readHeavyConfig(projectPath) });
17
+ if (options.json) {
18
+ console.log(JSON.stringify(result));
19
+ return result;
20
+ }
21
+ const gone = [...result.freed, ...result.containers];
22
+ console.log(gone.length ? `Derrubado: ${gone.join(', ')}` : 'Nada ocioso para derrubar.');
23
+ return result;
24
+ }
25
+
13
26
  export function heavyStatusCommand(options = {}) {
14
27
  const config = readHeavyConfig(process.cwd());
15
28
  const status = heavyStatus({ dir: heavyDir(), maxHoldMs: config.maxHoldMinutes * 60_000 });
@@ -15,6 +15,25 @@ import { memoryBusyHint } from '../../lib/heavy/live-stacks.js';
15
15
  import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
16
16
  import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
17
17
  import { syncStackRules } from '../../lib/installers/stack-sync.js';
18
+ import {
19
+ DETACH_RUN_ENV, DETACH_STAGE_ENV, DETACH_WORKER_ENV, launchWorker, markWorkerExit, runFilesOf, superviseVerify,
20
+ } from '../../lib/verify/detached.js';
21
+ import { watchRun } from '../../lib/heavy/watchdog.js';
22
+
23
+ const detachedArgs = () => process.argv.slice(2).filter((arg) => arg !== '--detach');
24
+
25
+ async function runDetached(featureName, options) {
26
+ const result = await superviseVerify({ feature: featureName, args: detachedArgs() });
27
+ if (result.stderr) process.stderr.write(result.stderr);
28
+ if (result.died) {
29
+ const reason = 'a rodada destacada parou sem resultado';
30
+ if (options.json) console.log(JSON.stringify({ status: 'busy', persisted: false, reason }));
31
+ else console.error(chalk.yellow(`⏳ ${reason}: sem stamp, não é vermelho.`));
32
+ } else if (result.stdout) {
33
+ process.stdout.write(result.stdout);
34
+ }
35
+ process.exit(result.exitCode);
36
+ }
18
37
 
19
38
  function statusIcon(status) {
20
39
  if (status === 'pass') return chalk.green('✓');
@@ -109,7 +128,7 @@ const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'],
109
128
  const CI_EXCLUSIVE_FLAGS = [
110
129
  ['filter', '--filter'], ['skipTests', '--skip-tests'], ['skipBuild', '--skip-build'], ['skipE2e', '--skip-e2e'],
111
130
  ['freshE2e', '--fresh-e2e'], ['linux', '--linux'], ['recordBaseline', '--record-baseline'], ['refreshCache', '--refresh-cache'],
112
- ['noPersist', '--no-persist'], ['fresh', '--fresh'],
131
+ ['noPersist', '--no-persist'], ['fresh', '--fresh'], ['affected', '--affected'],
113
132
  ];
114
133
 
115
134
  async function runLocalVerify(featureName, options, fingerprint) {
@@ -117,6 +136,7 @@ async function runLocalVerify(featureName, options, fingerprint) {
117
136
  return await runVerify(process.cwd(), featureName, {
118
137
  fingerprint,
119
138
  fresh: options.fresh,
139
+ affected: options.affected,
120
140
  timeout: options.timeout,
121
141
  skipTests: options.skipTests,
122
142
  skipBuild: options.skipBuild,
@@ -166,6 +186,28 @@ function exitWithReusedStamp(featureName, reused, why, options) {
166
186
  // Exit 2 is "the command cannot mean what you typed" (unknown feature, `--filter` matching
167
187
  // nothing, `--record-baseline` without config.json, `--linux` without Docker): never a verdict.
168
188
  export async function verifyCommand(featureName, options = {}) {
189
+ const stage = process.env[DETACH_STAGE_ENV];
190
+ if (stage === 'launch') {
191
+ launchWorker({ argv: process.argv.slice(2) });
192
+ process.exit(0);
193
+ }
194
+ if (stage === 'watchdog') {
195
+ const { exit, err } = runFilesOf(process.env[DETACH_RUN_ENV]);
196
+ await watchRun({
197
+ workerPid: Number(process.env[DETACH_WORKER_ENV]), exitFile: exit, errFile: err,
198
+ projectPath: process.cwd(), feature: featureName, config: readHeavyConfig(process.cwd()),
199
+ });
200
+ process.exit(0);
201
+ }
202
+ if (stage === 'worker') {
203
+ markWorkerExit(process.env[DETACH_RUN_ENV]);
204
+ // A verify nested in a test suite must not report as this run.
205
+ delete process.env[DETACH_STAGE_ENV];
206
+ delete process.env[DETACH_RUN_ENV];
207
+ // Out of a shell's reach there is no call timeout to beat, so the queue never needs to refuse.
208
+ process.env.MORPH_HEAVY_WAIT_SEC = String(readHeavyConfig(process.cwd()).detachedWaitMinutes * 60);
209
+ }
210
+
169
211
  let feature;
170
212
  try {
171
213
  feature = getFeature(featureName);
@@ -176,6 +218,7 @@ export async function verifyCommand(featureName, options = {}) {
176
218
  console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
177
219
  process.exit(2);
178
220
  }
221
+ if (options.detach) await runDetached(featureName, options);
179
222
 
180
223
  syncStackRules(process.cwd(), { persistTags: false, log: (line) => console.error(chalk.gray(line)) });
181
224
 
@@ -224,7 +267,9 @@ export async function verifyCommand(featureName, options = {}) {
224
267
  onlyUnderPressure: true,
225
268
  });
226
269
 
227
- const historyKey = 'verifications.feature';
270
+ // Only a complete run may stand in for the proof the review gate asks for: the affected one keeps its own record.
271
+ const scopedRun = result.scope === 'affected';
272
+ const historyKey = scopedRun ? 'verifications.affected' : 'verifications.feature';
228
273
  // A run that measured only part of the suite must not stand in for the proof a gate asks for:
229
274
  // --filter and --skip-*, or a --record-baseline that recorded nothing; --no-persist keeps its own reason.
230
275
  const partialRun = options.noPersist ? null : PARTIAL_RUN_FLAGS.find(([flag]) => options[flag])?.[1] ?? null;
@@ -237,7 +282,7 @@ export async function verifyCommand(featureName, options = {}) {
237
282
 
238
283
  let compact = null;
239
284
  if (persist) {
240
- const prevEntry = feature.verifications?.feature;
285
+ const prevEntry = feature.verifications?.[scopedRun ? 'affected' : 'feature'];
241
286
  const now = new Date().toISOString();
242
287
  compact = {
243
288
  status: result.status,
@@ -245,6 +290,7 @@ export async function verifyCommand(featureName, options = {}) {
245
290
  attempts: (prevEntry?.attempts ?? 0) + 1,
246
291
  failStreak: result.status === 'fail' ? (prevEntry?.failStreak ?? 0) + 1 : 0,
247
292
  updatedAt: now,
293
+ ...(scopedRun ? { sha: getHeadSha(process.cwd()) } : {}),
248
294
  };
249
295
  await updateFeature(featureName, historyKey, compact);
250
296
 
@@ -260,7 +306,7 @@ export async function verifyCommand(featureName, options = {}) {
260
306
  ...(result.flaky?.length ? { flaky: result.flaky } : {}),
261
307
  ...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
262
308
  };
263
- await updateFeature(featureName, 'verification', stamp);
309
+ if (!scopedRun) await updateFeature(featureName, 'verification', stamp);
264
310
  }
265
311
 
266
312
  // A run over the exact tree of the previous one proves nothing
@@ -270,7 +316,7 @@ export async function verifyCommand(featureName, options = {}) {
270
316
  const cwd = process.cwd();
271
317
  if (persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, fingerprint });
272
318
  const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
273
- recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
319
+ recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist && !scopedRun, reason: scopedRun ? 'affected' : reason, fingerprint, source: ci ? 'ci' : 'local' }));
274
320
  } catch { /* telemetry never changes the verdict */ }
275
321
 
276
322
  // The refusal always goes to stderr, so it survives --json (whose stdout has
@@ -1,13 +1,13 @@
1
- import { StrictMode } from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import { BrowserRouter } from 'react-router-dom';
4
- import App from './App.jsx';
5
- import './styles/index.css';
6
-
7
- createRoot(document.getElementById('root')).render(
8
- <StrictMode>
9
- <BrowserRouter>
10
- <App />
11
- </BrowserRouter>
12
- </StrictMode>
13
- );
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import App from './App.jsx';
5
+ import './styles/index.css';
6
+
7
+ createRoot(document.getElementById('root')).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>
13
+ );