@polymorphism-tech/morph-spec 2.2.0 → 2.4.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 +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuous Validator
|
|
3
|
+
*
|
|
4
|
+
* Background watcher that validates project in real-time.
|
|
5
|
+
* Detects issues before they become problems.
|
|
6
|
+
*
|
|
7
|
+
* MORPH-SPEC 3.0 - Sprint 4
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync, watch } from 'fs';
|
|
11
|
+
import { glob } from 'glob';
|
|
12
|
+
import { join, dirname } from 'path';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
|
|
15
|
+
export class ContinuousValidator {
|
|
16
|
+
constructor(projectPath = '.') {
|
|
17
|
+
this.projectPath = projectPath;
|
|
18
|
+
this.watchers = [];
|
|
19
|
+
this.validationInterval = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Start continuous validation
|
|
24
|
+
*/
|
|
25
|
+
async start(options = {}) {
|
|
26
|
+
const { watchMode = true, interval = 30000 } = options;
|
|
27
|
+
|
|
28
|
+
console.log(chalk.cyan('🔍 Starting continuous validation...'));
|
|
29
|
+
|
|
30
|
+
// Initial validation
|
|
31
|
+
await this.runAllValidations();
|
|
32
|
+
|
|
33
|
+
if (watchMode) {
|
|
34
|
+
// Watch for file changes
|
|
35
|
+
this.setupFileWatchers();
|
|
36
|
+
|
|
37
|
+
// Periodic validation
|
|
38
|
+
this.validationInterval = setInterval(() => {
|
|
39
|
+
this.runAllValidations();
|
|
40
|
+
}, interval);
|
|
41
|
+
|
|
42
|
+
console.log(chalk.gray(`Watching for changes (interval: ${interval/1000}s)...`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Stop continuous validation
|
|
48
|
+
*/
|
|
49
|
+
stop() {
|
|
50
|
+
// Stop file watchers
|
|
51
|
+
this.watchers.forEach(watcher => watcher.close());
|
|
52
|
+
this.watchers = [];
|
|
53
|
+
|
|
54
|
+
// Stop periodic validation
|
|
55
|
+
if (this.validationInterval) {
|
|
56
|
+
clearInterval(this.validationInterval);
|
|
57
|
+
this.validationInterval = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.gray('Stopped continuous validation'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Setup file watchers
|
|
65
|
+
*/
|
|
66
|
+
setupFileWatchers() {
|
|
67
|
+
const watchPaths = [
|
|
68
|
+
'**/*.csproj',
|
|
69
|
+
'**/Program.cs',
|
|
70
|
+
'wwwroot/css/**/*.css',
|
|
71
|
+
'infra/**/*.bicep'
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const pattern of watchPaths) {
|
|
75
|
+
const watcher = watch(pattern, { persistent: false }, async (eventType, filename) => {
|
|
76
|
+
if (filename) {
|
|
77
|
+
console.log(chalk.gray(`File changed: ${filename}`));
|
|
78
|
+
await this.runAllValidations();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.watchers.push(watcher);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Run all validations
|
|
88
|
+
*/
|
|
89
|
+
async runAllValidations() {
|
|
90
|
+
const results = await Promise.all([
|
|
91
|
+
this.validatePackageCompatibility(),
|
|
92
|
+
this.validateArchitecturePatterns(),
|
|
93
|
+
this.validateProgramCs(),
|
|
94
|
+
this.validateUIContrast()
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const errors = results.filter(r => r.level === 'error');
|
|
98
|
+
const warnings = results.filter(r => r.level === 'warning');
|
|
99
|
+
const infos = results.filter(r => r.level === 'info');
|
|
100
|
+
|
|
101
|
+
// Display summary
|
|
102
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
103
|
+
console.log(chalk.green('✅ All validations passed'));
|
|
104
|
+
} else {
|
|
105
|
+
if (errors.length > 0) {
|
|
106
|
+
console.log(chalk.red(`\n❌ ${errors.length} error(s) found:`));
|
|
107
|
+
errors.forEach(e => console.log(chalk.red(` - ${e.message}`)));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (warnings.length > 0) {
|
|
111
|
+
console.log(chalk.yellow(`\n⚠️ ${warnings.length} warning(s):`));
|
|
112
|
+
warnings.forEach(w => console.log(chalk.yellow(` - ${w.message}`)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Auto-fix errors if possible
|
|
117
|
+
const fixableErrors = errors.filter(e => e.autoFix);
|
|
118
|
+
if (fixableErrors.length > 0) {
|
|
119
|
+
console.log(chalk.cyan(`\n🔧 Auto-fixing ${fixableErrors.length} issue(s)...`));
|
|
120
|
+
for (const error of fixableErrors) {
|
|
121
|
+
try {
|
|
122
|
+
await error.autoFix();
|
|
123
|
+
console.log(chalk.green(` ✅ Fixed: ${error.message}`));
|
|
124
|
+
} catch (ex) {
|
|
125
|
+
console.log(chalk.red(` ❌ Failed to fix: ${error.message}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { errors, warnings, infos };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Validate package compatibility (.NET 10)
|
|
135
|
+
*/
|
|
136
|
+
async validatePackageCompatibility() {
|
|
137
|
+
try {
|
|
138
|
+
const csprojFiles = await glob('**/*.csproj', { ignore: '**/obj/**' });
|
|
139
|
+
|
|
140
|
+
for (const file of csprojFiles) {
|
|
141
|
+
const content = readFileSync(file, 'utf-8');
|
|
142
|
+
|
|
143
|
+
// Extract .NET version
|
|
144
|
+
const tfmMatch = content.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
|
|
145
|
+
if (!tfmMatch) continue;
|
|
146
|
+
|
|
147
|
+
const targetFramework = tfmMatch[1];
|
|
148
|
+
const dotnetVersion = parseInt(targetFramework.replace('net', ''));
|
|
149
|
+
|
|
150
|
+
if (dotnetVersion < 10) {
|
|
151
|
+
return {
|
|
152
|
+
level: 'info',
|
|
153
|
+
message: `${file}: Using .NET ${dotnetVersion} (consider upgrading to .NET 10)`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Package compatibility matrix
|
|
158
|
+
const incompatiblePackages = this.checkPackageCompatibility(content, dotnetVersion);
|
|
159
|
+
|
|
160
|
+
if (incompatiblePackages.length > 0) {
|
|
161
|
+
return {
|
|
162
|
+
level: 'error',
|
|
163
|
+
message: `${file}: Incompatible packages with .NET ${dotnetVersion}:\n` +
|
|
164
|
+
incompatiblePackages.map(p => ` - ${p.name} ${p.version} → upgrade to ${p.requiredVersion}`).join('\n'),
|
|
165
|
+
packages: incompatiblePackages
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { level: 'ok' };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return { level: 'error', message: `Package validation failed: ${error.message}` };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check package compatibility
|
|
178
|
+
*/
|
|
179
|
+
checkPackageCompatibility(csprojContent, dotnetVersion) {
|
|
180
|
+
const incompatible = [];
|
|
181
|
+
|
|
182
|
+
// Compatibility matrix (from dotnet10-compatibility.md)
|
|
183
|
+
const matrix = {
|
|
184
|
+
'MudBlazor': {
|
|
185
|
+
10: '8.15.0',
|
|
186
|
+
pattern: /<PackageReference Include="MudBlazor" Version="(.*?)"/
|
|
187
|
+
},
|
|
188
|
+
'Microsoft.FluentUI.AspNetCore.Components': {
|
|
189
|
+
10: '5.0.0',
|
|
190
|
+
pattern: /<PackageReference Include="Microsoft\.FluentUI\.AspNetCore\.Components" Version="(.*?)"/
|
|
191
|
+
},
|
|
192
|
+
'Hangfire.AspNetCore': {
|
|
193
|
+
10: '1.8.22',
|
|
194
|
+
pattern: /<PackageReference Include="Hangfire\.AspNetCore" Version="(.*?)"/
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const [pkg, config] of Object.entries(matrix)) {
|
|
199
|
+
const match = csprojContent.match(config.pattern);
|
|
200
|
+
if (match) {
|
|
201
|
+
const installedVersion = match[1];
|
|
202
|
+
const requiredVersion = config[dotnetVersion];
|
|
203
|
+
|
|
204
|
+
if (requiredVersion && this.compareVersions(installedVersion, requiredVersion) < 0) {
|
|
205
|
+
incompatible.push({
|
|
206
|
+
name: pkg,
|
|
207
|
+
version: installedVersion,
|
|
208
|
+
requiredVersion
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return incompatible;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Compare semantic versions
|
|
219
|
+
*/
|
|
220
|
+
compareVersions(v1, v2) {
|
|
221
|
+
const parts1 = v1.split('.').map(Number);
|
|
222
|
+
const parts2 = v2.split('.').map(Number);
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
225
|
+
const p1 = parts1[i] || 0;
|
|
226
|
+
const p2 = parts2[i] || 0;
|
|
227
|
+
if (p1 < p2) return -1;
|
|
228
|
+
if (p1 > p2) return 1;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Validate architecture patterns
|
|
236
|
+
*/
|
|
237
|
+
async validateArchitecturePatterns() {
|
|
238
|
+
try {
|
|
239
|
+
const blazorFiles = await glob('**/*.razor.cs', { ignore: '**/obj/**' });
|
|
240
|
+
|
|
241
|
+
for (const file of blazorFiles) {
|
|
242
|
+
const content = readFileSync(file, 'utf-8');
|
|
243
|
+
|
|
244
|
+
// Check for Application layer injection (anti-pattern)
|
|
245
|
+
if (/\[Inject\].*I\w+Command/.test(content)) {
|
|
246
|
+
return {
|
|
247
|
+
level: 'error',
|
|
248
|
+
message: `${file}: Blazor component injecting Application layer directly (use HttpClient pattern)`,
|
|
249
|
+
reference: 'framework/standards/blazor-pitfalls.md'
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check for DbContext injection (anti-pattern)
|
|
254
|
+
if (/\[Inject\].*DbContext/.test(content)) {
|
|
255
|
+
return {
|
|
256
|
+
level: 'error',
|
|
257
|
+
message: `${file}: Blazor component injecting DbContext directly (use HttpClient → API → Handler)`,
|
|
258
|
+
reference: 'framework/standards/blazor-pitfalls.md'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check for JSRuntime in OnInitialized (pitfall)
|
|
263
|
+
if (/OnInitialized.*JSRuntime/.test(content.replace(/\s+/g, ''))) {
|
|
264
|
+
return {
|
|
265
|
+
level: 'warning',
|
|
266
|
+
message: `${file}: Possible JSRuntime call in OnInitialized (use OnAfterRenderAsync)`,
|
|
267
|
+
reference: 'framework/standards/blazor-lifecycle.md'
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { level: 'ok' };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return { level: 'error', message: `Architecture validation failed: ${error.message}` };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validate Program.cs setup
|
|
280
|
+
*/
|
|
281
|
+
async validateProgramCs() {
|
|
282
|
+
try {
|
|
283
|
+
const programCs = 'Program.cs';
|
|
284
|
+
if (!existsSync(programCs)) {
|
|
285
|
+
return { level: 'ok' }; // Not a Blazor project
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const content = readFileSync(programCs, 'utf-8');
|
|
289
|
+
|
|
290
|
+
const checks = [
|
|
291
|
+
{ pattern: /app\.UseStaticFiles\(\)/, message: 'app.UseStaticFiles() is missing', critical: true },
|
|
292
|
+
{ pattern: /AddHttpClient\(\)/, message: 'AddHttpClient() is missing', critical: true },
|
|
293
|
+
{ pattern: /AddHttpContextAccessor\(\)/, message: 'AddHttpContextAccessor() is missing', critical: true }
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
for (const check of checks) {
|
|
297
|
+
if (!check.pattern.test(content)) {
|
|
298
|
+
return {
|
|
299
|
+
level: check.critical ? 'error' : 'warning',
|
|
300
|
+
message: `Program.cs: ${check.message}`,
|
|
301
|
+
reference: 'framework/standards/program-cs-checklist.md'
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Check middleware order (UseStaticFiles before UseAntiforgery)
|
|
307
|
+
const staticFilesIndex = content.indexOf('UseStaticFiles');
|
|
308
|
+
const antiforgeryIndex = content.indexOf('UseAntiforgery');
|
|
309
|
+
|
|
310
|
+
if (staticFilesIndex > 0 && antiforgeryIndex > 0 && staticFilesIndex > antiforgeryIndex) {
|
|
311
|
+
return {
|
|
312
|
+
level: 'error',
|
|
313
|
+
message: 'Program.cs: app.UseStaticFiles() must come BEFORE app.UseAntiforgery()',
|
|
314
|
+
reference: 'framework/standards/program-cs-checklist.md'
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { level: 'ok' };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return { level: 'error', message: `Program.cs validation failed: ${error.message}` };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validate UI contrast (WCAG 2.1 AA)
|
|
326
|
+
*/
|
|
327
|
+
async validateUIContrast() {
|
|
328
|
+
try {
|
|
329
|
+
const cssFiles = await glob('wwwroot/css/**/*.css', { ignore: '**/node_modules/**' });
|
|
330
|
+
|
|
331
|
+
for (const file of cssFiles) {
|
|
332
|
+
const content = readFileSync(file, 'utf-8');
|
|
333
|
+
|
|
334
|
+
// Extract color variables
|
|
335
|
+
const colors = this.extractColors(content);
|
|
336
|
+
|
|
337
|
+
// Check common color pairs
|
|
338
|
+
const issues = [];
|
|
339
|
+
const bgColors = colors.filter(c => c.name.includes('background') || c.name.includes('bg'));
|
|
340
|
+
const textColors = colors.filter(c => c.name.includes('text') || c.name.includes('color'));
|
|
341
|
+
|
|
342
|
+
for (const bg of bgColors) {
|
|
343
|
+
for (const text of textColors) {
|
|
344
|
+
const ratio = this.calculateContrastRatio(bg.value, text.value);
|
|
345
|
+
if (ratio < 4.5) { // WCAG AA minimum for text
|
|
346
|
+
issues.push({ bg: bg.name, text: text.name, ratio: ratio.toFixed(2) });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (issues.length > 0) {
|
|
352
|
+
return {
|
|
353
|
+
level: 'warning',
|
|
354
|
+
message: `${file}: Low contrast detected (WCAG AA requires 4.5:1):\n` +
|
|
355
|
+
issues.map(i => ` - ${i.text} on ${i.bg}: ${i.ratio}:1`).join('\n')
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { level: 'ok' };
|
|
361
|
+
} catch (error) {
|
|
362
|
+
return { level: 'ok' }; // CSS parsing is optional
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Extract colors from CSS
|
|
368
|
+
*/
|
|
369
|
+
extractColors(css) {
|
|
370
|
+
const colors = [];
|
|
371
|
+
const hexPattern = /--([\w-]+):\s*(#[0-9a-fA-F]{3,6})/g;
|
|
372
|
+
let match;
|
|
373
|
+
|
|
374
|
+
while ((match = hexPattern.exec(css)) !== null) {
|
|
375
|
+
colors.push({ name: match[1], value: match[2] });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return colors;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Calculate contrast ratio (WCAG formula)
|
|
383
|
+
*/
|
|
384
|
+
calculateContrastRatio(color1, color2) {
|
|
385
|
+
const l1 = this.getLuminance(color1);
|
|
386
|
+
const l2 = this.getLuminance(color2);
|
|
387
|
+
|
|
388
|
+
const lighter = Math.max(l1, l2);
|
|
389
|
+
const darker = Math.min(l1, l2);
|
|
390
|
+
|
|
391
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get relative luminance
|
|
396
|
+
*/
|
|
397
|
+
getLuminance(hex) {
|
|
398
|
+
const rgb = this.hexToRgb(hex);
|
|
399
|
+
if (!rgb) return 0;
|
|
400
|
+
|
|
401
|
+
const [r, g, b] = rgb.map(val => {
|
|
402
|
+
val = val / 255;
|
|
403
|
+
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Convert hex to RGB
|
|
411
|
+
*/
|
|
412
|
+
hexToRgb(hex) {
|
|
413
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
414
|
+
return result ? [
|
|
415
|
+
parseInt(result[1], 16),
|
|
416
|
+
parseInt(result[2], 16),
|
|
417
|
+
parseInt(result[3], 16)
|
|
418
|
+
] : null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
}
|