@polymorphism-tech/morph-spec 9.1.0 → 9.2.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 (76) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +7 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +1 -0
  7. package/framework/MORPH.md +2 -2
  8. package/framework/agents.json +8 -8
  9. package/framework/docs/CLI.md +16 -14
  10. package/framework/evals/review-checklist.md +12 -2
  11. package/framework/hooks/README.md +8 -44
  12. package/framework/hooks/claude-code/core/state-sync.js +1 -1
  13. package/framework/hooks/claude-code/statusline.sh +11 -11
  14. package/framework/rules/nextjs-standards.md +1 -1
  15. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  16. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  17. package/framework/skills/core/morph-apply/SKILL.md +3 -3
  18. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  19. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  20. package/framework/skills/core/morph-media/SKILL.md +1 -1
  21. package/framework/skills/core/morph-plan/SKILL.md +8 -6
  22. package/framework/skills/core/morph-review/SKILL.md +9 -6
  23. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  24. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  25. package/framework/standards/STANDARDS.json +2 -2
  26. package/framework/standards/backend/dotnet/testing.md +10 -4
  27. package/framework/standards/frontend/nextjs/testing.md +10 -6
  28. package/framework/templates/.idea/morph-templates.xml +92 -0
  29. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  30. package/framework/templates/IDE-SNIPPETS.md +262 -262
  31. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  32. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  33. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  34. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  35. package/framework/templates/docs/plan.md +3 -1
  36. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  37. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  38. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  39. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  40. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  41. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  42. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  43. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  44. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  45. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  46. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  47. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  48. package/framework/templates/state.template.json +17 -17
  49. package/framework/templates/ui/design-system.css +226 -226
  50. package/package.json +1 -1
  51. package/src/commands/state/approve.js +2 -2
  52. package/src/commands/validation/mutate.js +9 -6
  53. package/src/commands/validation/plan-check.js +4 -2
  54. package/src/commands/validation/tests-audit.js +7 -0
  55. package/src/commands/validation/validate-feature.js +4 -2
  56. package/src/commands/validation/verify.js +15 -15
  57. package/src/dashboard/app/main.jsx +13 -13
  58. package/src/dashboard/vite.config.js +24 -24
  59. package/src/lib/ci/workflow-scaffold.js +12 -0
  60. package/src/lib/e2e/applicability.js +37 -0
  61. package/src/lib/mutation/adapters.js +6 -17
  62. package/src/lib/mutation/runner.js +23 -75
  63. package/src/lib/mutation/trx.js +0 -1
  64. package/src/lib/plan/checks.js +29 -2
  65. package/src/lib/plan/plan-parser.js +6 -2
  66. package/src/lib/review-reports.js +6 -3
  67. package/src/lib/risk-detector.js +2 -0
  68. package/src/lib/tests-audit/audit.js +96 -0
  69. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  70. package/src/lib/validators/validation-runner.js +6 -38
  71. package/src/lib/validators/verify-runner.js +36 -21
  72. package/src/utils/agents-installer.js +1 -1
  73. package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
  74. package/framework/hooks/git/pre-commit/agents.sh +0 -25
  75. package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
  76. package/framework/hooks/git/pre-push/run-tests.sh +0 -44
@@ -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.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -19,10 +19,10 @@ export { REVIEWED_SHA_TOKEN };
19
19
 
20
20
  const SIGNED_OFF = { required: [], reports: {}, checks: { status: 'ok', total: 0, missing: [], notProven: [] } };
21
21
 
22
- // The worst answer across every report present wins. `skip` (no verifier report, no git) never
22
+ // The worst answer across every report present wins. `skip` (no report at all, no git) never
23
23
  // refuses falsely.
24
24
  function reviewStaleness(reports) {
25
- if (reports.verifier.status === 'missing') return { status: 'skip' };
25
+ if (Object.values(reports).every((r) => r.status === 'missing')) return { status: 'skip' };
26
26
  const cwd = process.cwd();
27
27
  let head;
28
28
  try {
@@ -15,7 +15,7 @@ import { groupByNode, loadPlan } from '../../lib/mutation/plan.js';
15
15
  import { dotnetSubset, jsSubset, mutatedTypesOf, testFilesCiting, testProjectDirsOf } from '../../lib/mutation/subset.js';
16
16
  import { isFinished } from '../../dashboard/scanner/feature-status.js';
17
17
  import { dotnetAdapter, genericAdapter, spawnExec } from '../../lib/mutation/adapters.js';
18
- import { runMutation } from '../../lib/mutation/runner.js';
18
+ import { noSubsetReason, runMutation } from '../../lib/mutation/runner.js';
19
19
  import { renderSection, summarize } from '../../lib/mutation/report.js';
20
20
  import { readJournal, restoreSnapshot } from '../../lib/mutation/snapshot.js';
21
21
 
@@ -101,8 +101,7 @@ function testDirsOf(node, root, { projectsOnly = false } = {}) {
101
101
  return testProjectDirsOf(root, projectsOnly ? targets.filter((t) => !/\.slnx?$/i.test(t)) : targets, cwd);
102
102
  }
103
103
 
104
- function subsetFor({ node, group, root, options }) {
105
- if (options.subset === false) return { usable: false, reason: '--no-subset' };
104
+ function subsetFor({ node, group, root }) {
106
105
  const mutatedFiles = group.regions.map((r) => r.file);
107
106
  if (node.runner === 'dotnet') {
108
107
  const base = detectDefaultBranch(root);
@@ -193,6 +192,12 @@ export async function mutateCommand(featureArg, options = {}) {
193
192
  return fail(`${named}\nNó e2e: true declarado: ${e2eIds}. ${e2eManualProtocol(feature, uncited[0].e2e[0])}`, EXIT.usage);
194
193
  }
195
194
 
195
+ const subsets = groups.map((group) => subsetFor({ node: group.node, group, root }));
196
+ const noSubset = subsets.findIndex((s) => !s.usable);
197
+ if (noSubset >= 0) {
198
+ return fail(`suíte "${groups[noSubset].node.id}": ${noSubsetReason(subsets[noSubset].reason)}.`, EXIT.usage);
199
+ }
200
+
196
201
  if (!options.allowDirty) {
197
202
  const dirty = gitOut(root, ['status', '--porcelain', '--', ...new Set(regions.map((r) => r.file))]).trim();
198
203
  if (dirty) return fail(`há alteração não commitada nos arquivos a mutar:\n${dirty}\nCommite o fix ANTES de mutar (ou use --allow-dirty sabendo que o snapshot é o único caminho de volta).`, EXIT.usage);
@@ -207,13 +212,11 @@ export async function mutateCommand(featureArg, options = {}) {
207
212
  const exec = options.exec ?? spawnExec;
208
213
 
209
214
  const merged = { wallMs: 0, mutants: [], timings: [], restore: { files: [], ok: true, treeClean: true }, binary: { proof: 'unavailable', projects: [] }, baseline: {}, method: { schemata: 0, rebuild: 0, subset: {} } };
210
- const subsets = [];
211
215
  try {
212
216
  for (const [index, group] of groups.entries()) {
213
217
  const workDir = join(base, runId, `g${index}`);
214
218
  mkdirSync(workDir, { recursive: true });
215
- const subset = subsetFor({ node: group.node, group, root, options });
216
- subsets.push(subset);
219
+ const subset = subsets[index];
217
220
  const timeoutMs = options.timeoutMs ?? group.node.timeoutMs ?? 900_000;
218
221
  const make = group.node.runner === 'dotnet' ? dotnetAdapter : genericAdapter;
219
222
  const adapter = make({ node: group.node, root, workDir, exec, subset, timeoutMs });
@@ -5,7 +5,7 @@ import { atomicWriteJson } from '../../core/state/state-manager.js';
5
5
  import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
6
6
  import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
7
7
  import { parsePlan } from '../../lib/plan/plan-parser.js';
8
- import { buildChecks, knownPersonas, parallelGroups, planHash, validatePlan } from '../../lib/plan/checks.js';
8
+ import { buildChecks, knownPersonas, parallelGroups, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
9
9
  import { knownSurfaces } from '../../lib/specs/screen-checklist.js';
10
10
  import { revocationWarnings } from '../../lib/specs/adr-index.js';
11
11
  import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
@@ -25,7 +25,9 @@ export function runPlanCheck(featureName, cwd = process.cwd()) {
25
25
 
26
26
  const text = readFileSync(planPath, 'utf8');
27
27
  const plan = parsePlan(text);
28
- const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd) });
28
+ const { errors, warnings } = validatePlan(plan, {
29
+ personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd), ...planRuleOptions(cwd, featureName),
30
+ });
29
31
  warnings.push(...revocationWarnings(cwd, plan));
30
32
  const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
31
33
  const hash = planHash(text);
@@ -0,0 +1,7 @@
1
+ import { auditTests, renderTestsAudit } from '../../lib/tests-audit/audit.js';
2
+
3
+ export function testsAuditCommand(options = {}) {
4
+ const result = auditTests(process.cwd());
5
+ console.log(options.json ? JSON.stringify(result, null, 2) : renderTestsAudit(result));
6
+ if (!result.ok) process.exit(2);
7
+ }
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { parsePlan } from '../../lib/plan/plan-parser.js';
5
- import { knownPersonas, planHash, validatePlan } from '../../lib/plan/checks.js';
5
+ import { knownPersonas, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
6
6
 
7
7
  // Matched against section markers (headings and line-start bold labels), never whole-file
8
8
  // substrings. required → ERROR, recommended → WARNING, optional → never penalized; an array is any-of.
@@ -115,7 +115,9 @@ function validateOutput(featurePath, outputFile) {
115
115
  // `planDir` is `.morph/features/{f}/2-plan`, so the project is four levels up.
116
116
  function validatePlanOutput(content, planDir, result) {
117
117
  const plan = parsePlan(content);
118
- const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(path.resolve(planDir, '..', '..', '..', '..')) });
118
+ const root = path.resolve(planDir, '..', '..', '..', '..');
119
+ const feature = path.basename(path.resolve(planDir, '..'));
120
+ const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(root), ...planRuleOptions(root, feature) });
119
121
  result.errors.push(...errors);
120
122
  result.warnings.push(...warnings);
121
123
  result.sections.found.push(...plan.sections.filter((x) => x.key).map((x) => x.name));
@@ -81,10 +81,8 @@ function decidePersistence({ cwd, featureName, noPersist }) {
81
81
  }
82
82
 
83
83
  function baselineStampBlock(baseline) {
84
- if (!baseline?.recorded) return baseline?.reason ? `record-baseline: ${baseline.reason}` : 'record-baseline';
85
- const fell = baseline.updates.filter((u) => u.from !== null && u.to < u.from);
86
- if (fell.length === 0) return null;
87
- return `record-baseline: a contagem caiu em ${fell.map((u) => `${u.id} (${u.from} → ${u.to})`).join(', ')}`;
84
+ if (baseline?.recorded) return null;
85
+ return baseline?.reason ? `record-baseline: ${baseline.reason}` : 'record-baseline';
88
86
  }
89
87
 
90
88
  // The single writer of both node maps the schema fixes (`verification.nodes` and the history
@@ -154,6 +152,17 @@ async function runLocalVerify(featureName, options) {
154
152
  }
155
153
  }
156
154
 
155
+ function exitWithReusedStamp(featureName, reused, why, options) {
156
+ console.error(chalk.green(`\n↪ ${why} (sha ${reused.sha.slice(0, 8)}, ${reused.ts}): nada mudou fora de .morph/ desde aquela rodada verde.\n`));
157
+ if (options.json) {
158
+ console.log(JSON.stringify({
159
+ feature: featureName, status: reused.status, nodes: reused.nodes, e2eReason: reused.e2eReason ?? null,
160
+ reused: { sha: reused.sha, ts: reused.ts, source: reused.source ?? 'local' }, persisted: false, reason: 'stamp-reused',
161
+ }, null, 2));
162
+ }
163
+ process.exit(0);
164
+ }
165
+
157
166
  // Exit 2 is "the command cannot mean what you typed" (unknown feature, `--filter` matching
158
167
  // nothing, `--record-baseline` without config.json, `--linux` without Docker): never a verdict.
159
168
  export async function verifyCommand(featureName, options = {}) {
@@ -178,6 +187,8 @@ export async function verifyCommand(featureName, options = {}) {
178
187
 
179
188
  let ci = null;
180
189
  if (options.ci) {
190
+ const fresh = reusableLocalStamp(process.cwd(), feature.verification);
191
+ if (fresh) exitWithReusedStamp(featureName, fresh, 'stamp verde e fresco — o CI não roda de novo', options);
181
192
  try {
182
193
  const outcome = await runCiVerify(process.cwd(), featureName, readMorphConfig(process.cwd()), {
183
194
  log: (line) => console.error(chalk.gray(` ${line}`)),
@@ -189,17 +200,6 @@ export async function verifyCommand(featureName, options = {}) {
189
200
  console.error(chalk.yellow(`\n⏳ ${outcome.reason}. Não é vermelho e não há stamp: não repita em laço — siga e deixe o gate-check decidir.\n`));
190
201
  process.exit(3);
191
202
  } else {
192
- const reused = reusableLocalStamp(process.cwd(), feature.verification);
193
- if (reused) {
194
- console.error(chalk.green(`\n↪ CI indisponível (${outcome.reason}) — stamp fresco reaproveitado (sha ${reused.sha.slice(0, 8)}, ${reused.ts}): nada mudou fora de .morph/ desde aquela rodada verde.\n`));
195
- if (options.json) {
196
- console.log(JSON.stringify({
197
- feature: featureName, status: reused.status, nodes: reused.nodes, e2eReason: reused.e2eReason ?? null,
198
- reused: { sha: reused.sha, ts: reused.ts, source: reused.source ?? 'local' }, persisted: false, reason: 'stamp-reused',
199
- }, null, 2));
200
- }
201
- process.exit(0);
202
- }
203
203
  console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
204
204
  }
205
205
  } catch (err) {
@@ -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
+ );