@polymorphism-tech/morph-spec 7.9.0 ā 7.9.2
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/README.md +4 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +6 -2
- package/docs/QUICKSTART.md +4 -4
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +2 -2
- package/framework/commands/commit.md +166 -166
- 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/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/hook-response.js +45 -45
- package/framework/hooks/shared/payload-utils.js +78 -78
- 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/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 +13 -8
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +13 -8
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +10 -7
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- 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/templates/IDE-SNIPPETS.md +262 -262
- 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/parallel-workers/parallel-coordinator.md +113 -113
- 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 +2 -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 +1 -1
- package/src/commands/project/update.js +4 -1
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- 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 +239 -239
- 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/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/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +81 -81
- 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/scripts/setup-infra.js +385 -385
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/skills-installer.js +174 -174
- package/src/utils/version-checker.js +2 -2
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
|
@@ -1,259 +1,259 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Architecture Pattern Validator
|
|
3
|
-
*
|
|
4
|
-
* Validates architectural patterns and detects anti-patterns in .NET apps.
|
|
5
|
-
* Checks Program.cs configuration, middleware order, and common DI issues.
|
|
6
|
-
*
|
|
7
|
-
* MORPH-SPEC 3.0 - Sprint 4
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync } from 'fs';
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Architecture Validation Rules
|
|
15
|
-
*/
|
|
16
|
-
const VALIDATION_RULES = {
|
|
17
|
-
// Program.cs Issues
|
|
18
|
-
missingHttpClient: {
|
|
19
|
-
pattern: /AddHttpClient\(\)/,
|
|
20
|
-
inverse: true, // Rule triggers if pattern NOT found
|
|
21
|
-
level: 'error',
|
|
22
|
-
message: 'AddHttpClient() missing in Program.cs',
|
|
23
|
-
reference: 'framework/standards/program-cs-checklist.md',
|
|
24
|
-
solution: 'Add builder.Services.AddHttpClient() before builder.Build()'
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
missingHttpContextAccessor: {
|
|
28
|
-
pattern: /AddHttpContextAccessor\(\)/,
|
|
29
|
-
inverse: true,
|
|
30
|
-
level: 'warning',
|
|
31
|
-
message: 'AddHttpContextAccessor() missing (needed for auth contexts)',
|
|
32
|
-
reference: 'framework/standards/program-cs-checklist.md',
|
|
33
|
-
solution: 'Add builder.Services.AddHttpContextAccessor()'
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
missingStaticFiles: {
|
|
37
|
-
pattern: /app\.UseStaticFiles\(\)/,
|
|
38
|
-
inverse: true,
|
|
39
|
-
level: 'error',
|
|
40
|
-
message: 'app.UseStaticFiles() missing (CSS/JS won\'t load)',
|
|
41
|
-
reference: 'framework/standards/program-cs-checklist.md',
|
|
42
|
-
solution: 'Add app.UseStaticFiles() before app.UseAntiforgery()'
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
wrongMiddlewareOrder: {
|
|
46
|
-
// Custom validator (checked in validateProgramCs)
|
|
47
|
-
level: 'error',
|
|
48
|
-
message: 'app.UseStaticFiles() must come BEFORE app.UseAntiforgery()',
|
|
49
|
-
reference: 'framework/standards/program-cs-checklist.md',
|
|
50
|
-
solution: 'Reorder: UseStaticFiles() ā UseAntiforgery() ā UseAuthorization()'
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Architecture Validator Class
|
|
56
|
-
*/
|
|
57
|
-
export class ArchitectureValidator {
|
|
58
|
-
constructor(projectPath = '.') {
|
|
59
|
-
this.projectPath = projectPath;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Validate all architecture rules
|
|
64
|
-
*/
|
|
65
|
-
async validateAll() {
|
|
66
|
-
const results = {
|
|
67
|
-
programCs: await this.validateProgramCs()
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const allIssues = [
|
|
71
|
-
...results.programCs.issues
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
status: allIssues.length === 0 ? 'ok' : 'error',
|
|
76
|
-
totalIssues: allIssues.length,
|
|
77
|
-
errors: allIssues.filter(i => i.level === 'error').length,
|
|
78
|
-
warnings: allIssues.filter(i => i.level === 'warning').length,
|
|
79
|
-
results
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Validate Program.cs
|
|
85
|
-
*/
|
|
86
|
-
async validateProgramCs() {
|
|
87
|
-
const programCsPath = 'Program.cs';
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const content = readFileSync(programCsPath, 'utf-8');
|
|
91
|
-
return this.validateProgramCsContent(content);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
// No Program.cs found (not a .NET project)
|
|
94
|
-
return {
|
|
95
|
-
exists: false,
|
|
96
|
-
issues: []
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Validate Program.cs content
|
|
103
|
-
*/
|
|
104
|
-
validateProgramCsContent(content) {
|
|
105
|
-
const issues = [];
|
|
106
|
-
|
|
107
|
-
// Check missing patterns (inverse rules)
|
|
108
|
-
const inverseRules = ['missingHttpClient', 'missingHttpContextAccessor', 'missingStaticFiles'];
|
|
109
|
-
|
|
110
|
-
for (const ruleId of inverseRules) {
|
|
111
|
-
const rule = VALIDATION_RULES[ruleId];
|
|
112
|
-
const found = rule.pattern.test(content);
|
|
113
|
-
|
|
114
|
-
if (!found) {
|
|
115
|
-
issues.push({
|
|
116
|
-
file: 'Program.cs',
|
|
117
|
-
rule: ruleId,
|
|
118
|
-
level: rule.level,
|
|
119
|
-
message: rule.message,
|
|
120
|
-
reference: rule.reference,
|
|
121
|
-
solution: rule.solution
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check middleware order
|
|
127
|
-
const staticFilesIndex = content.indexOf('UseStaticFiles');
|
|
128
|
-
const antiforgeryIndex = content.indexOf('UseAntiforgery');
|
|
129
|
-
|
|
130
|
-
if (staticFilesIndex > 0 && antiforgeryIndex > 0 && staticFilesIndex > antiforgeryIndex) {
|
|
131
|
-
const rule = VALIDATION_RULES.wrongMiddlewareOrder;
|
|
132
|
-
issues.push({
|
|
133
|
-
file: 'Program.cs',
|
|
134
|
-
rule: 'wrongMiddlewareOrder',
|
|
135
|
-
level: rule.level,
|
|
136
|
-
message: rule.message,
|
|
137
|
-
reference: rule.reference,
|
|
138
|
-
solution: rule.solution,
|
|
139
|
-
lineNumber: this.findLineNumber(content, /UseStaticFiles/)
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
exists: true,
|
|
145
|
-
issues
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Find line number of pattern match
|
|
151
|
-
*/
|
|
152
|
-
findLineNumber(content, pattern) {
|
|
153
|
-
const lines = content.split('\n');
|
|
154
|
-
for (let i = 0; i < lines.length; i++) {
|
|
155
|
-
if (pattern.test(lines[i])) {
|
|
156
|
-
return i + 1;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Format validation results for console output
|
|
164
|
-
*/
|
|
165
|
-
formatResults(results) {
|
|
166
|
-
if (results.status === 'ok') {
|
|
167
|
-
console.log(chalk.green('ā
No architecture issues found'));
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
console.log(chalk.yellow(`\nā ļø Found ${results.totalIssues} architecture issue(s):\n`));
|
|
172
|
-
|
|
173
|
-
// Group by file
|
|
174
|
-
const issuesByFile = new Map();
|
|
175
|
-
const allIssues = [
|
|
176
|
-
...results.results.programCs.issues
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
for (const issue of allIssues) {
|
|
180
|
-
if (!issuesByFile.has(issue.file)) {
|
|
181
|
-
issuesByFile.set(issue.file, []);
|
|
182
|
-
}
|
|
183
|
-
issuesByFile.get(issue.file).push(issue);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Display by file
|
|
187
|
-
for (const [file, issues] of issuesByFile) {
|
|
188
|
-
console.log(chalk.cyan(`\nš ${file}`));
|
|
189
|
-
|
|
190
|
-
const errors = issues.filter(i => i.level === 'error');
|
|
191
|
-
const warnings = issues.filter(i => i.level === 'warning');
|
|
192
|
-
|
|
193
|
-
if (errors.length > 0) {
|
|
194
|
-
console.log(chalk.red(` ā ${errors.length} error(s):`));
|
|
195
|
-
errors.forEach(e => {
|
|
196
|
-
const location = e.lineNumber ? `:${e.lineNumber}` : '';
|
|
197
|
-
console.log(chalk.red(` ${file}${location}`));
|
|
198
|
-
console.log(chalk.red(` - ${e.message}`));
|
|
199
|
-
console.log(chalk.gray(` Solution: ${e.solution}`));
|
|
200
|
-
console.log(chalk.blue(` Reference: ${e.reference}`));
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (warnings.length > 0) {
|
|
205
|
-
console.log(chalk.yellow(` ā ļø ${warnings.length} warning(s):`));
|
|
206
|
-
warnings.forEach(w => {
|
|
207
|
-
const location = w.lineNumber ? `:${w.lineNumber}` : '';
|
|
208
|
-
console.log(chalk.yellow(` ${file}${location}`));
|
|
209
|
-
console.log(chalk.yellow(` - ${w.message}`));
|
|
210
|
-
console.log(chalk.gray(` Solution: ${w.solution}`));
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
console.log('');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Get suggested fixes
|
|
220
|
-
*/
|
|
221
|
-
getSuggestedFixes(results) {
|
|
222
|
-
const fixes = [];
|
|
223
|
-
const allIssues = [
|
|
224
|
-
...results.results.programCs.issues
|
|
225
|
-
];
|
|
226
|
-
|
|
227
|
-
for (const issue of allIssues) {
|
|
228
|
-
fixes.push({
|
|
229
|
-
file: issue.file,
|
|
230
|
-
line: issue.lineNumber,
|
|
231
|
-
issue: issue.message,
|
|
232
|
-
solution: issue.solution,
|
|
233
|
-
reference: issue.reference,
|
|
234
|
-
severity: issue.level
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return fixes;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Quick validation function (for imports)
|
|
244
|
-
*/
|
|
245
|
-
export async function validateArchitecture(projectPath = '.', options = {}) {
|
|
246
|
-
const validator = new ArchitectureValidator(projectPath);
|
|
247
|
-
const results = await validator.validateAll();
|
|
248
|
-
|
|
249
|
-
if (options.verbose) {
|
|
250
|
-
validator.formatResults(results);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (options.fixes) {
|
|
254
|
-
const fixes = validator.getSuggestedFixes(results);
|
|
255
|
-
return { ...results, fixes };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return results;
|
|
259
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Architecture Pattern Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates architectural patterns and detects anti-patterns in .NET apps.
|
|
5
|
+
* Checks Program.cs configuration, middleware order, and common DI issues.
|
|
6
|
+
*
|
|
7
|
+
* MORPH-SPEC 3.0 - Sprint 4
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Architecture Validation Rules
|
|
15
|
+
*/
|
|
16
|
+
const VALIDATION_RULES = {
|
|
17
|
+
// Program.cs Issues
|
|
18
|
+
missingHttpClient: {
|
|
19
|
+
pattern: /AddHttpClient\(\)/,
|
|
20
|
+
inverse: true, // Rule triggers if pattern NOT found
|
|
21
|
+
level: 'error',
|
|
22
|
+
message: 'AddHttpClient() missing in Program.cs',
|
|
23
|
+
reference: 'framework/standards/program-cs-checklist.md',
|
|
24
|
+
solution: 'Add builder.Services.AddHttpClient() before builder.Build()'
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
missingHttpContextAccessor: {
|
|
28
|
+
pattern: /AddHttpContextAccessor\(\)/,
|
|
29
|
+
inverse: true,
|
|
30
|
+
level: 'warning',
|
|
31
|
+
message: 'AddHttpContextAccessor() missing (needed for auth contexts)',
|
|
32
|
+
reference: 'framework/standards/program-cs-checklist.md',
|
|
33
|
+
solution: 'Add builder.Services.AddHttpContextAccessor()'
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
missingStaticFiles: {
|
|
37
|
+
pattern: /app\.UseStaticFiles\(\)/,
|
|
38
|
+
inverse: true,
|
|
39
|
+
level: 'error',
|
|
40
|
+
message: 'app.UseStaticFiles() missing (CSS/JS won\'t load)',
|
|
41
|
+
reference: 'framework/standards/program-cs-checklist.md',
|
|
42
|
+
solution: 'Add app.UseStaticFiles() before app.UseAntiforgery()'
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
wrongMiddlewareOrder: {
|
|
46
|
+
// Custom validator (checked in validateProgramCs)
|
|
47
|
+
level: 'error',
|
|
48
|
+
message: 'app.UseStaticFiles() must come BEFORE app.UseAntiforgery()',
|
|
49
|
+
reference: 'framework/standards/program-cs-checklist.md',
|
|
50
|
+
solution: 'Reorder: UseStaticFiles() ā UseAntiforgery() ā UseAuthorization()'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Architecture Validator Class
|
|
56
|
+
*/
|
|
57
|
+
export class ArchitectureValidator {
|
|
58
|
+
constructor(projectPath = '.') {
|
|
59
|
+
this.projectPath = projectPath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate all architecture rules
|
|
64
|
+
*/
|
|
65
|
+
async validateAll() {
|
|
66
|
+
const results = {
|
|
67
|
+
programCs: await this.validateProgramCs()
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const allIssues = [
|
|
71
|
+
...results.programCs.issues
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
status: allIssues.length === 0 ? 'ok' : 'error',
|
|
76
|
+
totalIssues: allIssues.length,
|
|
77
|
+
errors: allIssues.filter(i => i.level === 'error').length,
|
|
78
|
+
warnings: allIssues.filter(i => i.level === 'warning').length,
|
|
79
|
+
results
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate Program.cs
|
|
85
|
+
*/
|
|
86
|
+
async validateProgramCs() {
|
|
87
|
+
const programCsPath = 'Program.cs';
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const content = readFileSync(programCsPath, 'utf-8');
|
|
91
|
+
return this.validateProgramCsContent(content);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// No Program.cs found (not a .NET project)
|
|
94
|
+
return {
|
|
95
|
+
exists: false,
|
|
96
|
+
issues: []
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validate Program.cs content
|
|
103
|
+
*/
|
|
104
|
+
validateProgramCsContent(content) {
|
|
105
|
+
const issues = [];
|
|
106
|
+
|
|
107
|
+
// Check missing patterns (inverse rules)
|
|
108
|
+
const inverseRules = ['missingHttpClient', 'missingHttpContextAccessor', 'missingStaticFiles'];
|
|
109
|
+
|
|
110
|
+
for (const ruleId of inverseRules) {
|
|
111
|
+
const rule = VALIDATION_RULES[ruleId];
|
|
112
|
+
const found = rule.pattern.test(content);
|
|
113
|
+
|
|
114
|
+
if (!found) {
|
|
115
|
+
issues.push({
|
|
116
|
+
file: 'Program.cs',
|
|
117
|
+
rule: ruleId,
|
|
118
|
+
level: rule.level,
|
|
119
|
+
message: rule.message,
|
|
120
|
+
reference: rule.reference,
|
|
121
|
+
solution: rule.solution
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check middleware order
|
|
127
|
+
const staticFilesIndex = content.indexOf('UseStaticFiles');
|
|
128
|
+
const antiforgeryIndex = content.indexOf('UseAntiforgery');
|
|
129
|
+
|
|
130
|
+
if (staticFilesIndex > 0 && antiforgeryIndex > 0 && staticFilesIndex > antiforgeryIndex) {
|
|
131
|
+
const rule = VALIDATION_RULES.wrongMiddlewareOrder;
|
|
132
|
+
issues.push({
|
|
133
|
+
file: 'Program.cs',
|
|
134
|
+
rule: 'wrongMiddlewareOrder',
|
|
135
|
+
level: rule.level,
|
|
136
|
+
message: rule.message,
|
|
137
|
+
reference: rule.reference,
|
|
138
|
+
solution: rule.solution,
|
|
139
|
+
lineNumber: this.findLineNumber(content, /UseStaticFiles/)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
exists: true,
|
|
145
|
+
issues
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Find line number of pattern match
|
|
151
|
+
*/
|
|
152
|
+
findLineNumber(content, pattern) {
|
|
153
|
+
const lines = content.split('\n');
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
if (pattern.test(lines[i])) {
|
|
156
|
+
return i + 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format validation results for console output
|
|
164
|
+
*/
|
|
165
|
+
formatResults(results) {
|
|
166
|
+
if (results.status === 'ok') {
|
|
167
|
+
console.log(chalk.green('ā
No architecture issues found'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(chalk.yellow(`\nā ļø Found ${results.totalIssues} architecture issue(s):\n`));
|
|
172
|
+
|
|
173
|
+
// Group by file
|
|
174
|
+
const issuesByFile = new Map();
|
|
175
|
+
const allIssues = [
|
|
176
|
+
...results.results.programCs.issues
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
for (const issue of allIssues) {
|
|
180
|
+
if (!issuesByFile.has(issue.file)) {
|
|
181
|
+
issuesByFile.set(issue.file, []);
|
|
182
|
+
}
|
|
183
|
+
issuesByFile.get(issue.file).push(issue);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Display by file
|
|
187
|
+
for (const [file, issues] of issuesByFile) {
|
|
188
|
+
console.log(chalk.cyan(`\nš ${file}`));
|
|
189
|
+
|
|
190
|
+
const errors = issues.filter(i => i.level === 'error');
|
|
191
|
+
const warnings = issues.filter(i => i.level === 'warning');
|
|
192
|
+
|
|
193
|
+
if (errors.length > 0) {
|
|
194
|
+
console.log(chalk.red(` ā ${errors.length} error(s):`));
|
|
195
|
+
errors.forEach(e => {
|
|
196
|
+
const location = e.lineNumber ? `:${e.lineNumber}` : '';
|
|
197
|
+
console.log(chalk.red(` ${file}${location}`));
|
|
198
|
+
console.log(chalk.red(` - ${e.message}`));
|
|
199
|
+
console.log(chalk.gray(` Solution: ${e.solution}`));
|
|
200
|
+
console.log(chalk.blue(` Reference: ${e.reference}`));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (warnings.length > 0) {
|
|
205
|
+
console.log(chalk.yellow(` ā ļø ${warnings.length} warning(s):`));
|
|
206
|
+
warnings.forEach(w => {
|
|
207
|
+
const location = w.lineNumber ? `:${w.lineNumber}` : '';
|
|
208
|
+
console.log(chalk.yellow(` ${file}${location}`));
|
|
209
|
+
console.log(chalk.yellow(` - ${w.message}`));
|
|
210
|
+
console.log(chalk.gray(` Solution: ${w.solution}`));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log('');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get suggested fixes
|
|
220
|
+
*/
|
|
221
|
+
getSuggestedFixes(results) {
|
|
222
|
+
const fixes = [];
|
|
223
|
+
const allIssues = [
|
|
224
|
+
...results.results.programCs.issues
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
for (const issue of allIssues) {
|
|
228
|
+
fixes.push({
|
|
229
|
+
file: issue.file,
|
|
230
|
+
line: issue.lineNumber,
|
|
231
|
+
issue: issue.message,
|
|
232
|
+
solution: issue.solution,
|
|
233
|
+
reference: issue.reference,
|
|
234
|
+
severity: issue.level
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return fixes;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Quick validation function (for imports)
|
|
244
|
+
*/
|
|
245
|
+
export async function validateArchitecture(projectPath = '.', options = {}) {
|
|
246
|
+
const validator = new ArchitectureValidator(projectPath);
|
|
247
|
+
const results = await validator.validateAll();
|
|
248
|
+
|
|
249
|
+
if (options.verbose) {
|
|
250
|
+
validator.formatResults(results);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (options.fixes) {
|
|
254
|
+
const fixes = validator.getSuggestedFixes(results);
|
|
255
|
+
return { ...results, fixes };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return results;
|
|
259
|
+
}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Checkpoint Validation Persistence
|
|
3
|
-
*
|
|
4
|
-
* Persists checkpoint validation results to disk for traceability.
|
|
5
|
-
* Each checkpoint writes a JSON file to:
|
|
6
|
-
* .morph/features/{feature}/3-implement/validations/checkpoint-{N}.json
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Persist a checkpoint validation result to disk.
|
|
14
|
-
*
|
|
15
|
-
* @param {string} projectPath - Project root
|
|
16
|
-
* @param {string} featureName - Feature name
|
|
17
|
-
* @param {{ checkpointNum: number, timestamp: string, passed: boolean, validators: Object, summary: string, feature?: string, phase?: string }} result - Validation result
|
|
18
|
-
* @returns {string} Path to the saved JSON file
|
|
19
|
-
*/
|
|
20
|
-
export function persistCheckpointValidation(projectPath, featureName, result) {
|
|
21
|
-
const dir = join(projectPath, `.morph/features/${featureName}/3-implement/validations`);
|
|
22
|
-
mkdirSync(dir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
const filename = `checkpoint-${result.checkpointNum}.json`;
|
|
25
|
-
const filePath = join(dir, filename);
|
|
26
|
-
|
|
27
|
-
const output = {
|
|
28
|
-
checkpointNum: result.checkpointNum,
|
|
29
|
-
timestamp: result.timestamp || new Date().toISOString(),
|
|
30
|
-
passed: result.passed,
|
|
31
|
-
validators: result.validators || {},
|
|
32
|
-
summary: result.summary || '',
|
|
33
|
-
feature: result.feature || featureName,
|
|
34
|
-
phase: result.phase || 'implement',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
writeFileSync(filePath, JSON.stringify(output, null, 2), 'utf8');
|
|
38
|
-
return filePath;
|
|
39
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint Validation Persistence
|
|
3
|
+
*
|
|
4
|
+
* Persists checkpoint validation results to disk for traceability.
|
|
5
|
+
* Each checkpoint writes a JSON file to:
|
|
6
|
+
* .morph/features/{feature}/3-implement/validations/checkpoint-{N}.json
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Persist a checkpoint validation result to disk.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} projectPath - Project root
|
|
16
|
+
* @param {string} featureName - Feature name
|
|
17
|
+
* @param {{ checkpointNum: number, timestamp: string, passed: boolean, validators: Object, summary: string, feature?: string, phase?: string }} result - Validation result
|
|
18
|
+
* @returns {string} Path to the saved JSON file
|
|
19
|
+
*/
|
|
20
|
+
export function persistCheckpointValidation(projectPath, featureName, result) {
|
|
21
|
+
const dir = join(projectPath, `.morph/features/${featureName}/3-implement/validations`);
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
const filename = `checkpoint-${result.checkpointNum}.json`;
|
|
25
|
+
const filePath = join(dir, filename);
|
|
26
|
+
|
|
27
|
+
const output = {
|
|
28
|
+
checkpointNum: result.checkpointNum,
|
|
29
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
30
|
+
passed: result.passed,
|
|
31
|
+
validators: result.validators || {},
|
|
32
|
+
summary: result.summary || '',
|
|
33
|
+
feature: result.feature || featureName,
|
|
34
|
+
phase: result.phase || 'implement',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
writeFileSync(filePath, JSON.stringify(output, null, 2), 'utf8');
|
|
38
|
+
return filePath;
|
|
39
|
+
}
|