@polymorphism-tech/morph-spec 7.9.4 → 8.1.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 (212) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +31 -2
  3. package/bin/morph-spec.js +50 -2
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +38 -1
  7. package/docs/QUICKSTART.md +60 -1
  8. package/framework/CLAUDE.md +14 -3
  9. package/framework/MORPH.md +41 -5
  10. package/framework/agents.json +22 -3
  11. package/framework/commands/commit.md +166 -166
  12. package/framework/commands/morph-hotfix.md +39 -0
  13. package/framework/commands/morph-infra.md +215 -215
  14. package/framework/commands/morph-preflight.md +205 -205
  15. package/framework/commands/morph-troubleshoot.md +124 -124
  16. package/framework/hooks/README.md +25 -11
  17. package/framework/hooks/claude-code/core/gate-guard.js +4 -2
  18. package/framework/hooks/claude-code/core/loop-detect.js +2 -1
  19. package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
  20. package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
  21. package/framework/hooks/claude-code/core/validate-completion.js +1 -1
  22. package/framework/hooks/claude-code/core/worktree-context.js +160 -0
  23. package/framework/hooks/claude-code/statusline.py +679 -679
  24. package/framework/hooks/claude-code/statusline.sh +11 -11
  25. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  26. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  27. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  28. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  29. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  30. package/framework/hooks/shared/activity-logger.js +160 -160
  31. package/framework/hooks/shared/build-check.js +2 -2
  32. package/framework/hooks/shared/hook-response.js +17 -5
  33. package/framework/hooks/shared/payload-utils.js +42 -0
  34. package/framework/hooks/shared/state-reader.js +22 -2
  35. package/framework/hooks/shared/stdin-reader.js +26 -26
  36. package/framework/rubrics/backend.json +41 -41
  37. package/framework/rules/ai-agents-standards.md +45 -45
  38. package/framework/rules/dotnet-templates.md +86 -86
  39. package/framework/rules/frontend-standards.md +13 -13
  40. package/framework/rules/infrastructure-standards.md +27 -27
  41. package/framework/rules/neon-standards.md +25 -25
  42. package/framework/rules/nextjs-standards.md +20 -20
  43. package/framework/rules/nextjs-templates.md +40 -40
  44. package/framework/schemas/feature.schema.json +95 -1
  45. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  46. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  47. package/framework/skills/core/morph-apply/SKILL.md +49 -27
  48. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
  49. package/framework/skills/core/morph-eval/SKILL.md +6 -1
  50. package/framework/skills/core/morph-hotfix/SKILL.md +134 -0
  51. package/framework/skills/core/morph-implement/SKILL.md +17 -22
  52. package/framework/skills/core/morph-plan/SKILL.md +5 -1
  53. package/framework/skills/core/morph-proposal/SKILL.md +24 -1
  54. package/framework/skills/core/morph-review/SKILL.md +13 -16
  55. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  56. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  57. package/framework/standards/ai-agents/agent-session.md +160 -160
  58. package/framework/standards/ai-agents/batching.md +193 -193
  59. package/framework/standards/ai-agents/context-providers.md +188 -188
  60. package/framework/standards/ai-agents/mcp-server.md +250 -250
  61. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  62. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  63. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  64. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  65. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  66. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  67. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  68. package/framework/standards/ai-agents/production.md +275 -275
  69. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  70. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  71. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  72. package/framework/standards/ai-agents/structured-output.md +249 -249
  73. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  74. package/framework/templates/.idea/morph-templates.xml +92 -0
  75. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  76. package/framework/templates/IDE-SNIPPETS.md +262 -262
  77. package/framework/templates/REGISTRY.json +0 -62
  78. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  79. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  80. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  81. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  82. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  83. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  84. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  85. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  86. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  87. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  88. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  89. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  90. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  91. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  92. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  93. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  94. package/framework/templates/code/typescript/contracts.ts +167 -167
  95. package/framework/templates/docs/clarifications.md +253 -253
  96. package/framework/templates/docs/schema-analysis.md +119 -119
  97. package/framework/templates/docs/ui-components.md +52 -52
  98. package/framework/templates/docs/ui-design-system.md +76 -76
  99. package/framework/templates/docs/ui-flows.md +167 -167
  100. package/framework/templates/docs/ui-mockups.md +98 -98
  101. package/framework/templates/feature/decisions.md +122 -122
  102. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  103. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  104. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  105. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  106. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  107. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  108. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  109. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  110. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  111. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  112. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  113. package/framework/templates/infrastructure/github/README.md +56 -56
  114. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  115. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  116. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  117. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  118. package/framework/templates/integrations/asaas-client.cs +387 -387
  119. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  120. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  121. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  122. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  123. package/framework/templates/project/validate.js +120 -120
  124. package/framework/templates/state.template.json +17 -17
  125. package/framework/templates/ui/design-system.css +226 -226
  126. package/package.json +1 -1
  127. package/src/commands/dashboard/dashboard.js +53 -53
  128. package/src/commands/index.js +13 -13
  129. package/src/commands/mcp/mcp-setup.js +519 -519
  130. package/src/commands/project/checkpoint.js +209 -209
  131. package/src/commands/project/doctor.js +4 -2
  132. package/src/commands/project/init.js +46 -2
  133. package/src/commands/project/update.js +5 -3
  134. package/src/commands/project/worktree.js +1005 -46
  135. package/src/commands/state/advance.js +17 -2
  136. package/src/commands/state/approve.js +18 -1
  137. package/src/commands/state/create.js +158 -14
  138. package/src/commands/state/retype.js +126 -0
  139. package/src/commands/state/score.js +4 -0
  140. package/src/commands/tasks/dag.js +32 -0
  141. package/src/commands/validation/index.js +1 -0
  142. package/src/commands/validation/validate-feature.js +33 -5
  143. package/src/commands/validation/verify.js +190 -0
  144. package/src/core/state/phase-state-machine.js +197 -197
  145. package/src/core/state/state-manager.js +35 -10
  146. package/src/core/templates/template-renderer.js +477 -477
  147. package/src/core/work-types.js +79 -0
  148. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  149. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  150. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  151. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  152. package/src/dashboard/app/main.jsx +13 -13
  153. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  154. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  155. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  156. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  157. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  158. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  159. package/src/dashboard/app/pages/Standards.jsx +332 -332
  160. package/src/dashboard/app/styles/index.css +47 -47
  161. package/src/dashboard/index.html +12 -12
  162. package/src/dashboard/package.json +24 -24
  163. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  164. package/src/dashboard/scanner/phases-scanner.js +15 -15
  165. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  166. package/src/dashboard/scanner/rules-scanner.js +37 -37
  167. package/src/dashboard/scanner/skills-scanner.js +43 -43
  168. package/src/dashboard/scanner/squads-scanner.js +13 -13
  169. package/src/dashboard/scanner/standards-scanner.js +26 -26
  170. package/src/dashboard/scanner/state-scanner.js +42 -0
  171. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  172. package/src/dashboard/vite.config.js +24 -24
  173. package/src/lib/agents/metrics-aggregator.js +56 -56
  174. package/src/lib/agents/metrics-calculator.js +125 -125
  175. package/src/lib/agents/output-collector.js +73 -73
  176. package/src/lib/detectors/claude-config-detector.js +152 -152
  177. package/src/lib/detectors/design-system-detector.js +231 -231
  178. package/src/lib/detectors/index.js +217 -217
  179. package/src/lib/generators/architecture-scanner.js +639 -639
  180. package/src/lib/generators/design-system-generator.js +508 -508
  181. package/src/lib/generators/index.js +13 -13
  182. package/src/lib/generators/settings-generator.js +209 -209
  183. package/src/lib/hooks/hook-executor.js +167 -167
  184. package/src/lib/installers/mcp-installer.js +719 -719
  185. package/src/lib/installers/memory-installer.js +1 -1
  186. package/src/lib/risk-detector.js +16 -0
  187. package/src/lib/tasks/dispatch-planner.js +122 -4
  188. package/src/lib/tasks/mermaid-renderer.js +132 -0
  189. package/src/lib/tasks/task-classifier.js +30 -30
  190. package/src/lib/tasks/task-parser.js +209 -209
  191. package/src/lib/tasks/test-runner.js +27 -6
  192. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  193. package/src/lib/validators/checkpoint-persistence.js +39 -39
  194. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  195. package/src/lib/validators/css/css-validator.js +340 -340
  196. package/src/lib/validators/packages/package-validator.js +347 -347
  197. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  198. package/src/lib/validators/shared/issue-counter.js +14 -14
  199. package/src/lib/validators/validation-runner.js +349 -349
  200. package/src/lib/validators/verify-runner.js +322 -0
  201. package/src/lib/work-type-detector.js +143 -0
  202. package/src/scripts/setup-infra.js +32 -5
  203. package/src/utils/agent-id-mapper.js +11 -11
  204. package/src/utils/agents-installer.js +10 -6
  205. package/src/utils/claude-settings-manager.js +300 -300
  206. package/src/utils/file-copier.js +43 -0
  207. package/src/utils/hooks-installer.js +43 -34
  208. package/src/utils/skills-installer.js +174 -174
  209. package/framework/hooks/claude-code/core/subagent-report-guard.js +0 -95
  210. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  211. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  212. package/src/lib/tasks/wave-resolver.js +0 -353
package/bin/validate.js CHANGED
@@ -1,379 +1,379 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Validation CLI
5
- *
6
- * Runs validators for the current project stack (detected from .morph/config/config.json).
7
- *
8
- * .NET/Blazor stacks: packages, architecture, contrast, spec-tasks
9
- * Next.js stack: next-components, spec-tasks
10
- * Other stacks: spec-tasks
11
- *
12
- * Usage:
13
- * morph-spec validate [options]
14
- * morph-spec validate all
15
- * morph-spec validate next-components (Next.js projects)
16
- * morph-spec validate packages (.NET projects)
17
- *
18
- * MORPH-SPEC 3.0
19
- */
20
-
21
- import chalk from 'chalk';
22
- import { getStackProfile } from '../src/lib/stack/stack-profile.js';
23
-
24
- /**
25
- * Build the VALIDATORS registry for the current stack.
26
- * .NET stacks → packages, architecture, contrast, spec-tasks
27
- * nextjs stack → next-components, spec-tasks
28
- * other stacks → spec-tasks only
29
- */
30
- async function buildValidators(stack) {
31
- const validators = {};
32
-
33
- const isDotnet = !stack || ['dotnet', 'blazor', 'aspnet', 'dotnet-blazor'].includes(stack.toLowerCase());
34
- const isNextjs = stack && ['nextjs', 'next', 'next.js'].includes(stack.toLowerCase());
35
-
36
- if (isDotnet) {
37
- const { validatePackages } = await import('../src/lib/validators/packages/package-validator.js');
38
- const { validateArchitecture } = await import('../src/lib/validators/architecture/architecture-validator.js');
39
- const { validateContrast } = await import('../src/lib/validators/ui/ui-contrast-validator.js');
40
-
41
- validators.packages = {
42
- name: 'Package Compatibility',
43
- description: 'Validates NuGet package versions against .NET compatibility matrix',
44
- run: validatePackages
45
- };
46
- validators.architecture = {
47
- name: 'Architecture Patterns',
48
- description: 'Detects DI anti-patterns, middleware issues, and configuration problems in .NET',
49
- run: validateArchitecture
50
- };
51
- validators.contrast = {
52
- name: 'UI Contrast (WCAG)',
53
- description: 'Validates color contrast ratios for accessibility',
54
- run: validateContrast
55
- };
56
- }
57
-
58
- if (isNextjs) {
59
- const { validateNextComponentFiles } = await import('../src/lib/validators/nextjs/next-component-validator.js');
60
- validators['next-components'] = {
61
- name: 'Next.js Components',
62
- description: "Validates 'use client' directives, hook usage, and file naming conventions",
63
- run: (projectPath, opts) => validateNextComponentFiles(projectPath, opts)
64
- };
65
- }
66
-
67
- validators['spec-tasks'] = {
68
- name: 'Spec vs Tasks Coverage',
69
- description: 'Validates that tasks.json covers all requirements from spec.md',
70
- run: validateSpecVsTasks
71
- };
72
-
73
- return validators;
74
- }
75
-
76
- /**
77
- * Validate spec.md requirements are covered by tasks
78
- */
79
- async function validateSpecVsTasks(projectPath, options = {}) {
80
- const path = await import('path');
81
- const fs = await import('fs');
82
- const { loadState } = await import('../src/core/state/state-manager.js');
83
-
84
- const results = [];
85
- let errors = 0;
86
- let warnings = 0;
87
-
88
- try {
89
- const state = loadState(false);
90
- if (!state || !state.features) {
91
- return { status: 'ok', errors: 0, warnings: 0, message: 'No features to validate' };
92
- }
93
-
94
- for (const [featureName, feature] of Object.entries(state.features)) {
95
- const outputsPath = path.join(projectPath, '.morph/features', featureName);
96
- const specPath = path.join(outputsPath, 'spec.md');
97
-
98
- if (!fs.existsSync(specPath)) {
99
- continue; // No spec to validate
100
- }
101
-
102
- // Read spec.md and extract requirements
103
- const specContent = fs.readFileSync(specPath, 'utf-8');
104
-
105
- // Extract requirement patterns (## Requisitos, - [ ], numbered lists, etc.)
106
- const requirementPatterns = [
107
- /^#+\s*Requisitos?\s*$/gim,
108
- /^-\s*\[.\]\s*(.+)$/gm,
109
- /^\d+\.\s+(.+)$/gm
110
- ];
111
-
112
- const requirements = [];
113
- for (const pattern of requirementPatterns) {
114
- const matches = specContent.matchAll(pattern);
115
- for (const match of matches) {
116
- if (match[1]) {
117
- requirements.push(match[1].trim());
118
- }
119
- }
120
- }
121
-
122
- // Check if feature has tasks
123
- if (!feature.tasks || (Array.isArray(feature.tasks) && feature.tasks.length === 0)) {
124
- if (requirements.length > 0) {
125
- warnings++;
126
- results.push({
127
- feature: featureName,
128
- level: 'warning',
129
- message: `Spec has ${requirements.length} requirements but no tasks defined`
130
- });
131
- }
132
- continue;
133
- }
134
-
135
- // Get task titles
136
- const taskTitles = Array.isArray(feature.tasks)
137
- ? feature.tasks.map(t => t.title?.toLowerCase() || '')
138
- : [];
139
-
140
- // Check each requirement has a matching task (fuzzy match)
141
- for (const req of requirements.slice(0, 20)) { // Limit to first 20
142
- const reqLower = req.toLowerCase();
143
- const hasMatch = taskTitles.some(title =>
144
- title.includes(reqLower.slice(0, 20)) ||
145
- reqLower.includes(title.slice(0, 20))
146
- );
147
-
148
- if (!hasMatch && options.verbose) {
149
- console.log(chalk.gray(` ? Requirement may not be covered: "${req.slice(0, 50)}..."`));
150
- }
151
- }
152
-
153
- if (options.verbose) {
154
- console.log(chalk.green(` ✓ ${featureName}: ${requirements.length} requirements, ${taskTitles.length} tasks`));
155
- }
156
- }
157
-
158
- } catch (error) {
159
- if (options.verbose) {
160
- console.log(chalk.yellow(` ⚠ Could not validate: ${error.message}`));
161
- }
162
- }
163
-
164
- return {
165
- status: errors > 0 ? 'error' : warnings > 0 ? 'warning' : 'ok',
166
- errors,
167
- warnings,
168
- results
169
- };
170
- }
171
-
172
- /**
173
- * Main validation command
174
- */
175
- export async function validateCommand(args = []) {
176
- const validatorName = args[0] || 'all';
177
- const options = parseOptions(args);
178
-
179
- const { stack } = getStackProfile();
180
- const VALIDATORS = await buildValidators(stack);
181
-
182
- console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Validator\n'));
183
- if (stack) {
184
- console.log(chalk.gray(`Stack: ${stack}\n`));
185
- }
186
-
187
- if (validatorName === 'all') {
188
- await runAllValidators(VALIDATORS, options);
189
- } else if (VALIDATORS[validatorName]) {
190
- await runValidator(VALIDATORS, validatorName, options);
191
- } else {
192
- console.error(chalk.red(`❌ Unknown validator: ${validatorName}`));
193
- console.log(chalk.gray('\nAvailable validators:'));
194
- for (const [name, validator] of Object.entries(VALIDATORS)) {
195
- console.log(chalk.gray(` - ${name}: ${validator.description}`));
196
- }
197
- console.log(chalk.gray(' - all: Run all validators'));
198
- process.exit(1);
199
- }
200
- }
201
-
202
- /**
203
- * Run all validators
204
- */
205
- async function runAllValidators(VALIDATORS, options) {
206
- const results = {};
207
- let totalErrors = 0;
208
- let totalWarnings = 0;
209
-
210
- console.log(chalk.gray('Running all validators...\n'));
211
-
212
- for (const [name, validator] of Object.entries(VALIDATORS)) {
213
- console.log(chalk.cyan(`\n▸ ${validator.name}`));
214
- console.log(chalk.gray(` ${validator.description}\n`));
215
-
216
- try {
217
- const result = await validator.run('.', {
218
- verbose: options.verbose,
219
- autoFix: options.autoFix
220
- });
221
-
222
- results[name] = result;
223
-
224
- // Count errors/warnings
225
- if (result.errors !== undefined) {
226
- totalErrors += result.errors;
227
- totalWarnings += result.warnings || 0;
228
- } else if (result.results) {
229
- // Count issues in results array
230
- const issues = result.results.flatMap(r => r.issues || []);
231
- totalErrors += issues.filter(i => i.level === 'error').length;
232
- totalWarnings += issues.filter(i => i.level === 'warning').length;
233
- }
234
-
235
- } catch (error) {
236
- console.error(chalk.red(` ❌ Failed: ${error.message}`));
237
- results[name] = { status: 'error', error: error.message };
238
- }
239
- }
240
-
241
- // Summary
242
- console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
243
- console.log(chalk.bold('📊 Validation Summary\n'));
244
-
245
- if (totalErrors === 0 && totalWarnings === 0) {
246
- console.log(chalk.green('✅ All validations passed!'));
247
- } else {
248
- if (totalErrors > 0) {
249
- console.log(chalk.red(`❌ ${totalErrors} error(s) found`));
250
- }
251
- if (totalWarnings > 0) {
252
- console.log(chalk.yellow(`⚠️ ${totalWarnings} warning(s) found`));
253
- }
254
- }
255
-
256
- console.log('');
257
-
258
- // Exit with error code if errors found
259
- if (totalErrors > 0 && !options.noFail) {
260
- process.exit(1);
261
- }
262
- }
263
-
264
- /**
265
- * Run single validator
266
- */
267
- async function runValidator(VALIDATORS, name, options) {
268
- const validator = VALIDATORS[name];
269
-
270
- console.log(chalk.cyan(`▸ ${validator.name}`));
271
- console.log(chalk.gray(` ${validator.description}\n`));
272
-
273
- try {
274
- const result = await validator.run('.', {
275
- verbose: true, // Always verbose for single validator
276
- autoFix: options.autoFix,
277
- wcagLevel: options.wcagLevel
278
- });
279
-
280
- if (result.status === 'ok') {
281
- console.log(chalk.green('\n✅ Validation passed!\n'));
282
- } else {
283
- console.log(chalk.yellow('\n⚠️ Validation completed with issues\n'));
284
-
285
- if (result.errors > 0 && !options.noFail) {
286
- process.exit(1);
287
- }
288
- }
289
-
290
- } catch (error) {
291
- console.error(chalk.red(`\n❌ Validation failed: ${error.message}\n`));
292
- if (!options.noFail) {
293
- process.exit(1);
294
- }
295
- }
296
- }
297
-
298
- /**
299
- * Parse command-line options
300
- */
301
- function parseOptions(args) {
302
- const options = {
303
- verbose: false,
304
- autoFix: false,
305
- noFail: false,
306
- wcagLevel: 'AA'
307
- };
308
-
309
- for (const arg of args) {
310
- if (arg === '--verbose' || arg === '-v') {
311
- options.verbose = true;
312
- } else if (arg === '--auto-fix' || arg === '--fix') {
313
- options.autoFix = true;
314
- } else if (arg === '--no-fail') {
315
- options.noFail = true;
316
- } else if (arg === '--wcag-aaa') {
317
- options.wcagLevel = 'AAA';
318
- }
319
- }
320
-
321
- return options;
322
- }
323
-
324
- /**
325
- * Show help
326
- */
327
- export function showValidateHelp() {
328
- console.log(`
329
- ${chalk.bold.cyan('morph-spec validate')} - Run project validations
330
-
331
- ${chalk.bold('USAGE')}
332
- morph-spec validate [validator] [options]
333
-
334
- ${chalk.bold('VALIDATORS')}
335
- all Run all validators (default)
336
- packages Validate NuGet package compatibility (.NET 10)
337
- architecture Validate architecture patterns (DI, lifecycle)
338
- contrast Validate UI contrast ratios (WCAG 2.1)
339
-
340
- ${chalk.bold('OPTIONS')}
341
- --verbose, -v Show detailed output
342
- --auto-fix, --fix Auto-fix issues where possible
343
- --no-fail Don't exit with error code
344
- --wcag-aaa Use WCAG AAA standard (stricter)
345
-
346
- ${chalk.bold('EXAMPLES')}
347
- morph-spec validate # Run all validators
348
- morph-spec validate packages --fix # Validate packages and auto-fix
349
- morph-spec validate architecture -v # Verbose architecture validation
350
- morph-spec validate contrast --wcag-aaa # Stricter contrast validation
351
- morph-spec validate all # Run all validators
352
-
353
- ${chalk.bold('EXIT CODES')}
354
- 0 All validations passed
355
- 1 Validation errors found (unless --no-fail)
356
-
357
- ${chalk.bold('INTEGRATION')}
358
- Add to pre-commit hook:
359
- morph-spec validate --no-fail
360
-
361
- CI/CD pipeline:
362
- morph-spec validate || exit 1
363
- `);
364
- }
365
-
366
- // Run if called directly
367
- if (import.meta.url === `file://${process.argv[1]}`) {
368
- const args = process.argv.slice(2);
369
-
370
- if (args.includes('--help') || args.includes('-h')) {
371
- showValidateHelp();
372
- process.exit(0);
373
- }
374
-
375
- validateCommand(args).catch(error => {
376
- console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
377
- process.exit(1);
378
- });
379
- }
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Validation CLI
5
+ *
6
+ * Runs validators for the current project stack (detected from .morph/config/config.json).
7
+ *
8
+ * .NET/Blazor stacks: packages, architecture, contrast, spec-tasks
9
+ * Next.js stack: next-components, spec-tasks
10
+ * Other stacks: spec-tasks
11
+ *
12
+ * Usage:
13
+ * morph-spec validate [options]
14
+ * morph-spec validate all
15
+ * morph-spec validate next-components (Next.js projects)
16
+ * morph-spec validate packages (.NET projects)
17
+ *
18
+ * MORPH-SPEC 3.0
19
+ */
20
+
21
+ import chalk from 'chalk';
22
+ import { getStackProfile } from '../src/lib/stack/stack-profile.js';
23
+
24
+ /**
25
+ * Build the VALIDATORS registry for the current stack.
26
+ * .NET stacks → packages, architecture, contrast, spec-tasks
27
+ * nextjs stack → next-components, spec-tasks
28
+ * other stacks → spec-tasks only
29
+ */
30
+ async function buildValidators(stack) {
31
+ const validators = {};
32
+
33
+ const isDotnet = !stack || ['dotnet', 'blazor', 'aspnet', 'dotnet-blazor'].includes(stack.toLowerCase());
34
+ const isNextjs = stack && ['nextjs', 'next', 'next.js'].includes(stack.toLowerCase());
35
+
36
+ if (isDotnet) {
37
+ const { validatePackages } = await import('../src/lib/validators/packages/package-validator.js');
38
+ const { validateArchitecture } = await import('../src/lib/validators/architecture/architecture-validator.js');
39
+ const { validateContrast } = await import('../src/lib/validators/ui/ui-contrast-validator.js');
40
+
41
+ validators.packages = {
42
+ name: 'Package Compatibility',
43
+ description: 'Validates NuGet package versions against .NET compatibility matrix',
44
+ run: validatePackages
45
+ };
46
+ validators.architecture = {
47
+ name: 'Architecture Patterns',
48
+ description: 'Detects DI anti-patterns, middleware issues, and configuration problems in .NET',
49
+ run: validateArchitecture
50
+ };
51
+ validators.contrast = {
52
+ name: 'UI Contrast (WCAG)',
53
+ description: 'Validates color contrast ratios for accessibility',
54
+ run: validateContrast
55
+ };
56
+ }
57
+
58
+ if (isNextjs) {
59
+ const { validateNextComponentFiles } = await import('../src/lib/validators/nextjs/next-component-validator.js');
60
+ validators['next-components'] = {
61
+ name: 'Next.js Components',
62
+ description: "Validates 'use client' directives, hook usage, and file naming conventions",
63
+ run: (projectPath, opts) => validateNextComponentFiles(projectPath, opts)
64
+ };
65
+ }
66
+
67
+ validators['spec-tasks'] = {
68
+ name: 'Spec vs Tasks Coverage',
69
+ description: 'Validates that tasks.json covers all requirements from spec.md',
70
+ run: validateSpecVsTasks
71
+ };
72
+
73
+ return validators;
74
+ }
75
+
76
+ /**
77
+ * Validate spec.md requirements are covered by tasks
78
+ */
79
+ async function validateSpecVsTasks(projectPath, options = {}) {
80
+ const path = await import('path');
81
+ const fs = await import('fs');
82
+ const { loadState } = await import('../src/core/state/state-manager.js');
83
+
84
+ const results = [];
85
+ let errors = 0;
86
+ let warnings = 0;
87
+
88
+ try {
89
+ const state = loadState(false);
90
+ if (!state || !state.features) {
91
+ return { status: 'ok', errors: 0, warnings: 0, message: 'No features to validate' };
92
+ }
93
+
94
+ for (const [featureName, feature] of Object.entries(state.features)) {
95
+ const outputsPath = path.join(projectPath, '.morph/features', featureName);
96
+ const specPath = path.join(outputsPath, 'spec.md');
97
+
98
+ if (!fs.existsSync(specPath)) {
99
+ continue; // No spec to validate
100
+ }
101
+
102
+ // Read spec.md and extract requirements
103
+ const specContent = fs.readFileSync(specPath, 'utf-8');
104
+
105
+ // Extract requirement patterns (## Requisitos, - [ ], numbered lists, etc.)
106
+ const requirementPatterns = [
107
+ /^#+\s*Requisitos?\s*$/gim,
108
+ /^-\s*\[.\]\s*(.+)$/gm,
109
+ /^\d+\.\s+(.+)$/gm
110
+ ];
111
+
112
+ const requirements = [];
113
+ for (const pattern of requirementPatterns) {
114
+ const matches = specContent.matchAll(pattern);
115
+ for (const match of matches) {
116
+ if (match[1]) {
117
+ requirements.push(match[1].trim());
118
+ }
119
+ }
120
+ }
121
+
122
+ // Check if feature has tasks
123
+ if (!feature.tasks || (Array.isArray(feature.tasks) && feature.tasks.length === 0)) {
124
+ if (requirements.length > 0) {
125
+ warnings++;
126
+ results.push({
127
+ feature: featureName,
128
+ level: 'warning',
129
+ message: `Spec has ${requirements.length} requirements but no tasks defined`
130
+ });
131
+ }
132
+ continue;
133
+ }
134
+
135
+ // Get task titles
136
+ const taskTitles = Array.isArray(feature.tasks)
137
+ ? feature.tasks.map(t => t.title?.toLowerCase() || '')
138
+ : [];
139
+
140
+ // Check each requirement has a matching task (fuzzy match)
141
+ for (const req of requirements.slice(0, 20)) { // Limit to first 20
142
+ const reqLower = req.toLowerCase();
143
+ const hasMatch = taskTitles.some(title =>
144
+ title.includes(reqLower.slice(0, 20)) ||
145
+ reqLower.includes(title.slice(0, 20))
146
+ );
147
+
148
+ if (!hasMatch && options.verbose) {
149
+ console.log(chalk.gray(` ? Requirement may not be covered: "${req.slice(0, 50)}..."`));
150
+ }
151
+ }
152
+
153
+ if (options.verbose) {
154
+ console.log(chalk.green(` ✓ ${featureName}: ${requirements.length} requirements, ${taskTitles.length} tasks`));
155
+ }
156
+ }
157
+
158
+ } catch (error) {
159
+ if (options.verbose) {
160
+ console.log(chalk.yellow(` ⚠ Could not validate: ${error.message}`));
161
+ }
162
+ }
163
+
164
+ return {
165
+ status: errors > 0 ? 'error' : warnings > 0 ? 'warning' : 'ok',
166
+ errors,
167
+ warnings,
168
+ results
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Main validation command
174
+ */
175
+ export async function validateCommand(args = []) {
176
+ const validatorName = args[0] || 'all';
177
+ const options = parseOptions(args);
178
+
179
+ const { stack } = getStackProfile();
180
+ const VALIDATORS = await buildValidators(stack);
181
+
182
+ console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Validator\n'));
183
+ if (stack) {
184
+ console.log(chalk.gray(`Stack: ${stack}\n`));
185
+ }
186
+
187
+ if (validatorName === 'all') {
188
+ await runAllValidators(VALIDATORS, options);
189
+ } else if (VALIDATORS[validatorName]) {
190
+ await runValidator(VALIDATORS, validatorName, options);
191
+ } else {
192
+ console.error(chalk.red(`❌ Unknown validator: ${validatorName}`));
193
+ console.log(chalk.gray('\nAvailable validators:'));
194
+ for (const [name, validator] of Object.entries(VALIDATORS)) {
195
+ console.log(chalk.gray(` - ${name}: ${validator.description}`));
196
+ }
197
+ console.log(chalk.gray(' - all: Run all validators'));
198
+ process.exit(1);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Run all validators
204
+ */
205
+ async function runAllValidators(VALIDATORS, options) {
206
+ const results = {};
207
+ let totalErrors = 0;
208
+ let totalWarnings = 0;
209
+
210
+ console.log(chalk.gray('Running all validators...\n'));
211
+
212
+ for (const [name, validator] of Object.entries(VALIDATORS)) {
213
+ console.log(chalk.cyan(`\n▸ ${validator.name}`));
214
+ console.log(chalk.gray(` ${validator.description}\n`));
215
+
216
+ try {
217
+ const result = await validator.run('.', {
218
+ verbose: options.verbose,
219
+ autoFix: options.autoFix
220
+ });
221
+
222
+ results[name] = result;
223
+
224
+ // Count errors/warnings
225
+ if (result.errors !== undefined) {
226
+ totalErrors += result.errors;
227
+ totalWarnings += result.warnings || 0;
228
+ } else if (result.results) {
229
+ // Count issues in results array
230
+ const issues = result.results.flatMap(r => r.issues || []);
231
+ totalErrors += issues.filter(i => i.level === 'error').length;
232
+ totalWarnings += issues.filter(i => i.level === 'warning').length;
233
+ }
234
+
235
+ } catch (error) {
236
+ console.error(chalk.red(` ❌ Failed: ${error.message}`));
237
+ results[name] = { status: 'error', error: error.message };
238
+ }
239
+ }
240
+
241
+ // Summary
242
+ console.log(chalk.bold.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
243
+ console.log(chalk.bold('📊 Validation Summary\n'));
244
+
245
+ if (totalErrors === 0 && totalWarnings === 0) {
246
+ console.log(chalk.green('✅ All validations passed!'));
247
+ } else {
248
+ if (totalErrors > 0) {
249
+ console.log(chalk.red(`❌ ${totalErrors} error(s) found`));
250
+ }
251
+ if (totalWarnings > 0) {
252
+ console.log(chalk.yellow(`⚠️ ${totalWarnings} warning(s) found`));
253
+ }
254
+ }
255
+
256
+ console.log('');
257
+
258
+ // Exit with error code if errors found
259
+ if (totalErrors > 0 && !options.noFail) {
260
+ process.exit(1);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Run single validator
266
+ */
267
+ async function runValidator(VALIDATORS, name, options) {
268
+ const validator = VALIDATORS[name];
269
+
270
+ console.log(chalk.cyan(`▸ ${validator.name}`));
271
+ console.log(chalk.gray(` ${validator.description}\n`));
272
+
273
+ try {
274
+ const result = await validator.run('.', {
275
+ verbose: true, // Always verbose for single validator
276
+ autoFix: options.autoFix,
277
+ wcagLevel: options.wcagLevel
278
+ });
279
+
280
+ if (result.status === 'ok') {
281
+ console.log(chalk.green('\n✅ Validation passed!\n'));
282
+ } else {
283
+ console.log(chalk.yellow('\n⚠️ Validation completed with issues\n'));
284
+
285
+ if (result.errors > 0 && !options.noFail) {
286
+ process.exit(1);
287
+ }
288
+ }
289
+
290
+ } catch (error) {
291
+ console.error(chalk.red(`\n❌ Validation failed: ${error.message}\n`));
292
+ if (!options.noFail) {
293
+ process.exit(1);
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Parse command-line options
300
+ */
301
+ function parseOptions(args) {
302
+ const options = {
303
+ verbose: false,
304
+ autoFix: false,
305
+ noFail: false,
306
+ wcagLevel: 'AA'
307
+ };
308
+
309
+ for (const arg of args) {
310
+ if (arg === '--verbose' || arg === '-v') {
311
+ options.verbose = true;
312
+ } else if (arg === '--auto-fix' || arg === '--fix') {
313
+ options.autoFix = true;
314
+ } else if (arg === '--no-fail') {
315
+ options.noFail = true;
316
+ } else if (arg === '--wcag-aaa') {
317
+ options.wcagLevel = 'AAA';
318
+ }
319
+ }
320
+
321
+ return options;
322
+ }
323
+
324
+ /**
325
+ * Show help
326
+ */
327
+ export function showValidateHelp() {
328
+ console.log(`
329
+ ${chalk.bold.cyan('morph-spec validate')} - Run project validations
330
+
331
+ ${chalk.bold('USAGE')}
332
+ morph-spec validate [validator] [options]
333
+
334
+ ${chalk.bold('VALIDATORS')}
335
+ all Run all validators (default)
336
+ packages Validate NuGet package compatibility (.NET 10)
337
+ architecture Validate architecture patterns (DI, lifecycle)
338
+ contrast Validate UI contrast ratios (WCAG 2.1)
339
+
340
+ ${chalk.bold('OPTIONS')}
341
+ --verbose, -v Show detailed output
342
+ --auto-fix, --fix Auto-fix issues where possible
343
+ --no-fail Don't exit with error code
344
+ --wcag-aaa Use WCAG AAA standard (stricter)
345
+
346
+ ${chalk.bold('EXAMPLES')}
347
+ morph-spec validate # Run all validators
348
+ morph-spec validate packages --fix # Validate packages and auto-fix
349
+ morph-spec validate architecture -v # Verbose architecture validation
350
+ morph-spec validate contrast --wcag-aaa # Stricter contrast validation
351
+ morph-spec validate all # Run all validators
352
+
353
+ ${chalk.bold('EXIT CODES')}
354
+ 0 All validations passed
355
+ 1 Validation errors found (unless --no-fail)
356
+
357
+ ${chalk.bold('INTEGRATION')}
358
+ Add to pre-commit hook:
359
+ morph-spec validate --no-fail
360
+
361
+ CI/CD pipeline:
362
+ morph-spec validate || exit 1
363
+ `);
364
+ }
365
+
366
+ // Run if called directly
367
+ if (import.meta.url === `file://${process.argv[1]}`) {
368
+ const args = process.argv.slice(2);
369
+
370
+ if (args.includes('--help') || args.includes('-h')) {
371
+ showValidateHelp();
372
+ process.exit(0);
373
+ }
374
+
375
+ validateCommand(args).catch(error => {
376
+ console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
377
+ process.exit(1);
378
+ });
379
+ }