@polymorphism-tech/morph-spec 7.9.3 → 8.0.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.
- package/CLAUDE.md +2 -2
- package/README.md +31 -2
- package/bin/morph-spec.js +50 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +38 -1
- package/docs/QUICKSTART.md +60 -1
- package/framework/CLAUDE.md +13 -2
- package/framework/MORPH.md +38 -2
- package/framework/agents.json +22 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-hotfix.md +39 -0
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/README.md +25 -10
- package/framework/hooks/claude-code/core/gate-guard.js +4 -2
- package/framework/hooks/claude-code/core/loop-detect.js +2 -1
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
- package/framework/hooks/claude-code/core/validate-completion.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +160 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/build-check.js +2 -2
- package/framework/hooks/shared/hook-response.js +17 -5
- package/framework/hooks/shared/payload-utils.js +42 -0
- package/framework/hooks/shared/state-reader.js +22 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +95 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +39 -8
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
- package/framework/skills/core/morph-eval/SKILL.md +6 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
- package/framework/skills/core/morph-implement/SKILL.md +8 -4
- package/framework/skills/core/morph-plan/SKILL.md +5 -1
- package/framework/skills/core/morph-proposal/SKILL.md +24 -1
- package/framework/skills/core/morph-review/SKILL.md +3 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +7 -2
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +0 -62
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +4 -2
- package/src/commands/project/init.js +46 -2
- package/src/commands/project/update.js +5 -3
- package/src/commands/project/worktree.js +1005 -46
- package/src/commands/state/advance.js +17 -2
- package/src/commands/state/approve.js +18 -1
- package/src/commands/state/create.js +158 -14
- package/src/commands/state/retype.js +126 -0
- package/src/commands/state/score.js +4 -0
- package/src/commands/tasks/dag.js +32 -0
- package/src/commands/validation/index.js +1 -0
- package/src/commands/validation/validate-feature.js +33 -5
- package/src/commands/validation/verify.js +190 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +35 -10
- package/src/core/templates/template-renderer.js +477 -477
- package/src/core/work-types.js +79 -0
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +11 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/scanner/state-scanner.js +42 -0
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/risk-detector.js +16 -0
- package/src/lib/tasks/dispatch-planner.js +122 -4
- package/src/lib/tasks/mermaid-renderer.js +132 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +27 -6
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/lib/validators/verify-runner.js +322 -0
- package/src/lib/work-type-detector.js +143 -0
- package/src/scripts/setup-infra.js +32 -5
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +10 -6
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +43 -0
- package/src/utils/hooks-installer.js +41 -11
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- 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
|
+
}
|