@polymorphism-tech/morph-spec 2.4.0 → 3.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 +158 -26
- package/LICENSE +72 -72
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +8 -0
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- 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-archive.md +79 -79
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -227
- package/content/.claude/commands/morph-troubleshoot.md +122 -122
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +742 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -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 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/examples/state-v3.json +188 -188
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +158 -0
- 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/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/coding.md +377 -377
- 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/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/contracts.cs +217 -217
- 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/azure-pipelines-deploy.yml +480 -0
- 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 -426
- 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/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/sprint-status.yaml +68 -68
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/test.cs +239 -239
- 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/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/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/templates.md +418 -418
- package/package.json +1 -1
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -193
- package/src/commands/create-story.js +351 -351
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +34 -6
- package/src/commands/detect.js +104 -104
- package/src/commands/generate-context.js +40 -0
- package/src/commands/generate.js +149 -149
- package/src/commands/lint-fluent.js +352 -352
- package/src/commands/rollback-phase.js +185 -185
- package/src/commands/session-summary.js +291 -291
- 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/troubleshoot.js +222 -222
- package/src/commands/validate-blazor-state.js +210 -210
- package/src/commands/validate-blazor.js +156 -156
- package/src/commands/validate-css.js +84 -84
- package/src/commands/validate-phase.js +221 -221
- package/src/lib/blazor-concurrency-analyzer.js +288 -288
- package/src/lib/blazor-state-validator.js +291 -291
- package/src/lib/blazor-validator.js +374 -374
- package/src/lib/context-generator.js +513 -0
- package/src/lib/css-validator.js +352 -352
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/learning-system.js +520 -520
- package/src/lib/mockup-generator.js +366 -366
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -194
- package/src/lib/troubleshoot-index.js +144 -144
- package/src/lib/ui-detector.js +350 -350
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/architecture-validator.js +387 -387
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/lib/validators/package-validator.js +360 -360
- package/src/lib/validators/ui-contrast-validator.js +422 -422
- package/src/utils/file-copier.js +9 -1
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates spec.md and contracts.cs at design time (Phase 2) to catch
|
|
5
|
+
* architectural and coding standard violations early.
|
|
6
|
+
*
|
|
7
|
+
* MORPH-SPEC 3.0 - Phase 4: Spec-Time Validation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate spec.md and contracts.cs at design time
|
|
15
|
+
*
|
|
16
|
+
* @param {string} projectPath - Project root path
|
|
17
|
+
* @param {string} featureName - Feature name
|
|
18
|
+
* @param {Object} options - Options
|
|
19
|
+
* @returns {Object} { status, errors, warnings, issues }
|
|
20
|
+
*/
|
|
21
|
+
export async function validateSpec(projectPath, featureName, options = {}) {
|
|
22
|
+
const outputsPath = join(projectPath, '.morph/project/outputs', featureName);
|
|
23
|
+
const specPath = join(outputsPath, 'spec.md');
|
|
24
|
+
const contractsPath = join(outputsPath, 'contracts.cs');
|
|
25
|
+
|
|
26
|
+
const issues = [];
|
|
27
|
+
|
|
28
|
+
// Validate spec.md
|
|
29
|
+
if (existsSync(specPath)) {
|
|
30
|
+
const specContent = readFileSync(specPath, 'utf8');
|
|
31
|
+
const specIssues = validateSpecMd(specContent);
|
|
32
|
+
issues.push(...specIssues);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validate contracts.cs
|
|
36
|
+
if (existsSync(contractsPath)) {
|
|
37
|
+
const contractsContent = readFileSync(contractsPath, 'utf8');
|
|
38
|
+
const contractsIssues = validateContractsCs(contractsContent);
|
|
39
|
+
issues.push(...contractsIssues);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const errors = issues.filter(i => i.level === 'error').length;
|
|
43
|
+
const warnings = issues.filter(i => i.level === 'warning').length;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
status: errors > 0 ? 'error' : (warnings > 0 ? 'warning' : 'ok'),
|
|
47
|
+
errors,
|
|
48
|
+
warnings,
|
|
49
|
+
issues,
|
|
50
|
+
passed: errors === 0
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate spec.md structure and content
|
|
56
|
+
*/
|
|
57
|
+
function validateSpecMd(content) {
|
|
58
|
+
const issues = [];
|
|
59
|
+
|
|
60
|
+
// Required sections
|
|
61
|
+
const requiredSections = [
|
|
62
|
+
{ name: 'Overview', pattern: /^##\s+(?:1\.\s+)?Overview/mi },
|
|
63
|
+
{ name: 'Functional Requirements', pattern: /^##\s+(?:\d+\.\s+)?Functional Requirements/mi },
|
|
64
|
+
{ name: 'Non-Functional Requirements', pattern: /^##\s+(?:\d+\.\s+)?Non-Functional Requirements/mi },
|
|
65
|
+
{ name: 'Technical Architecture', pattern: /^##\s+(?:\d+\.\s+)?Technical Architecture/mi },
|
|
66
|
+
{ name: 'Data Model', pattern: /^##\s+(?:\d+\.\s+)?Data Model/mi }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const section of requiredSections) {
|
|
70
|
+
if (!section.pattern.test(content)) {
|
|
71
|
+
issues.push({
|
|
72
|
+
level: 'error',
|
|
73
|
+
type: 'spec',
|
|
74
|
+
message: `Missing required section: "${section.name}"`,
|
|
75
|
+
solution: `Add ## ${section.name} section to spec.md`
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for manual infrastructure creation (anti-pattern)
|
|
81
|
+
const manualInfraPatterns = [
|
|
82
|
+
/create.*manually/i,
|
|
83
|
+
/portal.*azure/i,
|
|
84
|
+
/click.*portal/i,
|
|
85
|
+
/via.*portal/i,
|
|
86
|
+
/manual.*setup/i
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const pattern of manualInfraPatterns) {
|
|
90
|
+
if (pattern.test(content)) {
|
|
91
|
+
issues.push({
|
|
92
|
+
level: 'error',
|
|
93
|
+
type: 'spec',
|
|
94
|
+
message: 'Spec references manual Azure portal creation (anti-pattern)',
|
|
95
|
+
solution: 'Use Infrastructure as Code (Bicep) instead of manual portal operations'
|
|
96
|
+
});
|
|
97
|
+
break; // One error is enough
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for Bicep reference (best practice)
|
|
102
|
+
const hasBicep = /bicep/i.test(content) || /infrastructure\s+as\s+code/i.test(content) || /\.bicep/i.test(content);
|
|
103
|
+
if (!hasBicep && /azure|sql|storage|container/i.test(content)) {
|
|
104
|
+
issues.push({
|
|
105
|
+
level: 'warning',
|
|
106
|
+
type: 'spec',
|
|
107
|
+
message: 'Spec mentions Azure resources but no Bicep/IaC reference found',
|
|
108
|
+
solution: 'Add Infrastructure section with Bicep module references'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return issues;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate contracts.cs coding standards
|
|
117
|
+
*/
|
|
118
|
+
function validateContractsCs(content) {
|
|
119
|
+
const issues = [];
|
|
120
|
+
|
|
121
|
+
// 1. Interface naming: Must start with 'I' and be PascalCase
|
|
122
|
+
const interfaceRegex = /public\s+interface\s+(\w+)/g;
|
|
123
|
+
let match;
|
|
124
|
+
|
|
125
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
126
|
+
const name = match[1];
|
|
127
|
+
|
|
128
|
+
if (!name.startsWith('I')) {
|
|
129
|
+
issues.push({
|
|
130
|
+
level: 'error',
|
|
131
|
+
type: 'contracts',
|
|
132
|
+
message: `Interface '${name}' must start with 'I' (e.g., I${name})`,
|
|
133
|
+
solution: `Rename to I${name}`
|
|
134
|
+
});
|
|
135
|
+
} else if (!isPascalCase(name)) {
|
|
136
|
+
issues.push({
|
|
137
|
+
level: 'error',
|
|
138
|
+
type: 'contracts',
|
|
139
|
+
message: `Interface '${name}' must use PascalCase`,
|
|
140
|
+
solution: `Rename to ${toPascalCase(name)}`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 2. Async methods must have CancellationToken parameter
|
|
146
|
+
const asyncMethodRegex = /Task(?:<[^>]+>)?\s+(\w+Async)\s*\(([^)]*)\)/g;
|
|
147
|
+
while ((match = asyncMethodRegex.exec(content)) !== null) {
|
|
148
|
+
const methodName = match[1];
|
|
149
|
+
const params = match[2];
|
|
150
|
+
|
|
151
|
+
if (!params.includes('CancellationToken')) {
|
|
152
|
+
issues.push({
|
|
153
|
+
level: 'error',
|
|
154
|
+
type: 'contracts',
|
|
155
|
+
message: `Async method '${methodName}' missing CancellationToken parameter`,
|
|
156
|
+
solution: `Add 'CancellationToken ct = default' parameter to ${methodName}`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 3. DTOs should use records (not classes)
|
|
162
|
+
const classRegex = /public\s+class\s+(\w+(?:Dto|Response|Request|Command|Query))/g;
|
|
163
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
164
|
+
const className = match[1];
|
|
165
|
+
issues.push({
|
|
166
|
+
level: 'warning',
|
|
167
|
+
type: 'contracts',
|
|
168
|
+
message: `DTO '${className}' defined as class instead of record`,
|
|
169
|
+
solution: `Use 'public record ${className}' for immutable DTOs`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 4. Classes should be sealed (unless abstract/base)
|
|
174
|
+
const unsealedClassRegex = /public\s+class\s+(\w+)(?!\s*:\s*\w+Base)/g;
|
|
175
|
+
while ((match = unsealedClassRegex.exec(content)) !== null) {
|
|
176
|
+
const className = match[1];
|
|
177
|
+
// Skip if already sealed
|
|
178
|
+
const lineStart = content.lastIndexOf('\n', match.index) + 1;
|
|
179
|
+
const lineEnd = content.indexOf('\n', match.index);
|
|
180
|
+
const line = content.substring(lineStart, lineEnd);
|
|
181
|
+
|
|
182
|
+
if (!line.includes('sealed') && !line.includes('abstract')) {
|
|
183
|
+
issues.push({
|
|
184
|
+
level: 'warning',
|
|
185
|
+
type: 'contracts',
|
|
186
|
+
message: `Class '${className}' should be sealed (unless designed for inheritance)`,
|
|
187
|
+
solution: `Change to 'public sealed class ${className}'`
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 5. PascalCase for constants (NOT UPPER_SNAKE_CASE)
|
|
193
|
+
const upperSnakeRegex = /const\s+\w+\s+([A-Z_][A-Z0-9_]+)\s*=/g;
|
|
194
|
+
while ((match = upperSnakeRegex.exec(content)) !== null) {
|
|
195
|
+
const constName = match[1];
|
|
196
|
+
if (constName.includes('_') && constName === constName.toUpperCase()) {
|
|
197
|
+
issues.push({
|
|
198
|
+
level: 'error',
|
|
199
|
+
type: 'contracts',
|
|
200
|
+
message: `Constant '${constName}' uses UPPER_SNAKE_CASE (anti-pattern in C#)`,
|
|
201
|
+
solution: `Use PascalCase: '${toPascalCase(constName)}'`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 6. Methods returning Task must end with Async
|
|
207
|
+
const taskMethodRegex = /Task(?:<[^>]+>)?\s+(\w+)\s*\(/g;
|
|
208
|
+
while ((match = taskMethodRegex.exec(content)) !== null) {
|
|
209
|
+
const methodName = match[1];
|
|
210
|
+
if (!methodName.endsWith('Async') && !['get', 'set', 'init'].includes(methodName)) {
|
|
211
|
+
issues.push({
|
|
212
|
+
level: 'error',
|
|
213
|
+
type: 'contracts',
|
|
214
|
+
message: `Method '${methodName}' returns Task but doesn't end with 'Async'`,
|
|
215
|
+
solution: `Rename to '${methodName}Async'`
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 7. Enum members should be PascalCase (NOT UPPER_SNAKE_CASE)
|
|
221
|
+
const enumRegex = /enum\s+\w+\s*\{([^}]+)\}/gs;
|
|
222
|
+
while ((match = enumRegex.exec(content)) !== null) {
|
|
223
|
+
const enumBody = match[1];
|
|
224
|
+
const memberRegex = /\n\s*([A-Z_][A-Z0-9_]+)/g;
|
|
225
|
+
let memberMatch;
|
|
226
|
+
|
|
227
|
+
while ((memberMatch = memberRegex.exec(enumBody)) !== null) {
|
|
228
|
+
const memberName = memberMatch[1];
|
|
229
|
+
if (memberName.includes('_') && memberName === memberName.toUpperCase()) {
|
|
230
|
+
issues.push({
|
|
231
|
+
level: 'error',
|
|
232
|
+
type: 'contracts',
|
|
233
|
+
message: `Enum member '${memberName}' uses UPPER_SNAKE_CASE (must be PascalCase)`,
|
|
234
|
+
solution: `Use PascalCase: '${toPascalCase(memberName)}'`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return issues;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if a string is PascalCase
|
|
245
|
+
*/
|
|
246
|
+
function isPascalCase(str) {
|
|
247
|
+
return /^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)*$/.test(str);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Convert string to PascalCase
|
|
252
|
+
*/
|
|
253
|
+
function toPascalCase(str) {
|
|
254
|
+
return str
|
|
255
|
+
.split('_')
|
|
256
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
257
|
+
.join('');
|
|
258
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Standards Context Injector
|
|
3
|
+
*
|
|
4
|
+
* Automatically loads and surfaces relevant standards content for agents.
|
|
5
|
+
* Resolution order: project overrides → framework standards → content standards
|
|
6
|
+
*
|
|
7
|
+
* @module standards-context-injector
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Agent → Standards mapping
|
|
15
|
+
* Maps each agent ID to relevant standard files (without .md extension)
|
|
16
|
+
*/
|
|
17
|
+
const AGENT_STANDARDS_MAP = {
|
|
18
|
+
// Tier 1: Orchestrators
|
|
19
|
+
'standards-architect': [
|
|
20
|
+
'architecture',
|
|
21
|
+
'coding',
|
|
22
|
+
'azure',
|
|
23
|
+
'dotnet10-compatibility',
|
|
24
|
+
'program-cs-checklist'
|
|
25
|
+
],
|
|
26
|
+
'ai-system-architect': [
|
|
27
|
+
'agent-framework-setup',
|
|
28
|
+
'agent-framework-workflows',
|
|
29
|
+
'agent-framework-production',
|
|
30
|
+
'agent-framework-blazor-ui',
|
|
31
|
+
'architecture',
|
|
32
|
+
'coding'
|
|
33
|
+
],
|
|
34
|
+
'popm-advisor': [], // No technical standards
|
|
35
|
+
|
|
36
|
+
// Tier 2: Domain Leaders
|
|
37
|
+
'dotnet-senior': [
|
|
38
|
+
'coding',
|
|
39
|
+
'architecture',
|
|
40
|
+
'dotnet10-compatibility',
|
|
41
|
+
'program-cs-checklist'
|
|
42
|
+
],
|
|
43
|
+
'azure-architect': [
|
|
44
|
+
'azure',
|
|
45
|
+
'architecture',
|
|
46
|
+
'dotnet10-compatibility'
|
|
47
|
+
],
|
|
48
|
+
'ui-designer': [
|
|
49
|
+
'css-naming',
|
|
50
|
+
'css-animations',
|
|
51
|
+
'fluent-ui-setup',
|
|
52
|
+
'fluent-ui-blazor'
|
|
53
|
+
],
|
|
54
|
+
|
|
55
|
+
// Tier 3: Specialists - Backend Squad
|
|
56
|
+
'ef-modeler': [
|
|
57
|
+
'blazor-efcore',
|
|
58
|
+
'architecture',
|
|
59
|
+
'coding',
|
|
60
|
+
'dotnet10-compatibility'
|
|
61
|
+
],
|
|
62
|
+
'event-architect': [
|
|
63
|
+
'architecture',
|
|
64
|
+
'coding',
|
|
65
|
+
'dotnet10-compatibility'
|
|
66
|
+
],
|
|
67
|
+
'api-designer': [
|
|
68
|
+
'architecture',
|
|
69
|
+
'coding',
|
|
70
|
+
'dotnet10-compatibility'
|
|
71
|
+
],
|
|
72
|
+
'nosql-cache-expert': [
|
|
73
|
+
'architecture',
|
|
74
|
+
'coding'
|
|
75
|
+
],
|
|
76
|
+
'ddd-expert': [
|
|
77
|
+
'architecture',
|
|
78
|
+
'coding'
|
|
79
|
+
],
|
|
80
|
+
'hangfire-orchestrator': [
|
|
81
|
+
'architecture',
|
|
82
|
+
'coding',
|
|
83
|
+
'blazor-lifecycle', // Background jobs + Blazor lifecycle
|
|
84
|
+
'dotnet10-compatibility'
|
|
85
|
+
],
|
|
86
|
+
'ms-agent-expert': [
|
|
87
|
+
'agent-framework-setup',
|
|
88
|
+
'agent-framework-workflows',
|
|
89
|
+
'agent-framework-production',
|
|
90
|
+
'agent-framework-blazor-ui',
|
|
91
|
+
'coding',
|
|
92
|
+
'architecture'
|
|
93
|
+
],
|
|
94
|
+
'asaas-financial': [
|
|
95
|
+
'coding',
|
|
96
|
+
'architecture'
|
|
97
|
+
],
|
|
98
|
+
'clerk-auth': [
|
|
99
|
+
'coding',
|
|
100
|
+
'architecture',
|
|
101
|
+
'passkeys-auth'
|
|
102
|
+
],
|
|
103
|
+
'resend-email': [
|
|
104
|
+
'coding',
|
|
105
|
+
'architecture'
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
// Tier 3: Specialists - Frontend Squad
|
|
109
|
+
'blazor-builder': [
|
|
110
|
+
'blazor-lifecycle',
|
|
111
|
+
'blazor-state',
|
|
112
|
+
'blazor-efcore',
|
|
113
|
+
'blazor-pitfalls',
|
|
114
|
+
'html-to-blazor',
|
|
115
|
+
'fluent-ui-blazor',
|
|
116
|
+
'css-naming',
|
|
117
|
+
'css-animations',
|
|
118
|
+
'coding',
|
|
119
|
+
'architecture'
|
|
120
|
+
],
|
|
121
|
+
'nextjs-expert': [
|
|
122
|
+
'coding'
|
|
123
|
+
],
|
|
124
|
+
'css-specialist': [
|
|
125
|
+
'css-naming',
|
|
126
|
+
'css-animations',
|
|
127
|
+
'fluent-ui-blazor'
|
|
128
|
+
],
|
|
129
|
+
|
|
130
|
+
// Tier 3: Specialists - Infrastructure Squad
|
|
131
|
+
'bicep-architect': [
|
|
132
|
+
'azure',
|
|
133
|
+
'architecture'
|
|
134
|
+
],
|
|
135
|
+
'devops-engineer': [
|
|
136
|
+
'azure',
|
|
137
|
+
'dotnet10-compatibility'
|
|
138
|
+
],
|
|
139
|
+
'container-specialist': [
|
|
140
|
+
'azure',
|
|
141
|
+
'dotnet10-compatibility'
|
|
142
|
+
],
|
|
143
|
+
'observability-expert': [
|
|
144
|
+
'azure',
|
|
145
|
+
'architecture'
|
|
146
|
+
],
|
|
147
|
+
'azure-identity': [
|
|
148
|
+
'azure',
|
|
149
|
+
'architecture',
|
|
150
|
+
'passkeys-auth'
|
|
151
|
+
],
|
|
152
|
+
|
|
153
|
+
// Tier 3: Specialists - Quality Squad
|
|
154
|
+
'testing-specialist': [
|
|
155
|
+
'coding',
|
|
156
|
+
'architecture',
|
|
157
|
+
'dotnet10-compatibility'
|
|
158
|
+
],
|
|
159
|
+
'code-analyzer': [
|
|
160
|
+
'coding',
|
|
161
|
+
'architecture',
|
|
162
|
+
'dotnet10-compatibility',
|
|
163
|
+
'program-cs-checklist'
|
|
164
|
+
],
|
|
165
|
+
'troubleshooting-expert': [
|
|
166
|
+
'architecture',
|
|
167
|
+
'blazor-pitfalls',
|
|
168
|
+
'dotnet10-compatibility'
|
|
169
|
+
],
|
|
170
|
+
'load-testing-expert': [
|
|
171
|
+
'architecture',
|
|
172
|
+
'azure'
|
|
173
|
+
],
|
|
174
|
+
'documentation-specialist': [], // No technical standards
|
|
175
|
+
|
|
176
|
+
// Tier 4: Validators
|
|
177
|
+
'security-expert': [
|
|
178
|
+
'coding',
|
|
179
|
+
'architecture',
|
|
180
|
+
'passkeys-auth'
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Load a single standard file with project override support
|
|
186
|
+
* @param {string} standardName - Standard filename without .md extension
|
|
187
|
+
* @param {string} projectPath - Root path of the project
|
|
188
|
+
* @returns {string|null} Standard content or null if not found
|
|
189
|
+
*/
|
|
190
|
+
function loadStandard(standardName, projectPath) {
|
|
191
|
+
const locations = [
|
|
192
|
+
// 1. Project override
|
|
193
|
+
join(projectPath, '.morph/project/standards', `${standardName}.md`),
|
|
194
|
+
// 2. Content standards (AI/Agent Framework, Azure, etc.)
|
|
195
|
+
join(projectPath, 'content/.morph/standards', `${standardName}.md`),
|
|
196
|
+
// 3. Framework standards (Blazor, CSS, .NET)
|
|
197
|
+
join(projectPath, 'framework/standards', `${standardName}.md`)
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const path of locations) {
|
|
201
|
+
if (existsSync(path)) {
|
|
202
|
+
try {
|
|
203
|
+
return readFileSync(path, 'utf8');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
// Continue to next location
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Load all relevant standards for a given agent
|
|
215
|
+
* @param {string} agentId - Agent ID (e.g., 'blazor-builder')
|
|
216
|
+
* @param {string} projectPath - Root path of the project
|
|
217
|
+
* @returns {Object} { sections: [{standard, content}], fullContent: string }
|
|
218
|
+
*/
|
|
219
|
+
export function loadStandardsForAgent(agentId, projectPath = '.') {
|
|
220
|
+
const standardNames = AGENT_STANDARDS_MAP[agentId] || [];
|
|
221
|
+
const sections = [];
|
|
222
|
+
const contentParts = [];
|
|
223
|
+
|
|
224
|
+
// Load inferred standards if exists (project-specific learnings)
|
|
225
|
+
const inferredPath = join(projectPath, '.morph/project/standards/inferred.md');
|
|
226
|
+
if (existsSync(inferredPath)) {
|
|
227
|
+
try {
|
|
228
|
+
const inferredContent = readFileSync(inferredPath, 'utf8');
|
|
229
|
+
sections.push({
|
|
230
|
+
standard: 'inferred',
|
|
231
|
+
content: inferredContent
|
|
232
|
+
});
|
|
233
|
+
contentParts.push(`# Project-Specific Standards (Inferred)\n\n${inferredContent}`);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
// Ignore if can't read
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Load each standard
|
|
240
|
+
for (const standardName of standardNames) {
|
|
241
|
+
const content = loadStandard(standardName, projectPath);
|
|
242
|
+
if (content) {
|
|
243
|
+
sections.push({
|
|
244
|
+
standard: standardName,
|
|
245
|
+
content
|
|
246
|
+
});
|
|
247
|
+
contentParts.push(`# ${standardName}\n\n${content}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
sections,
|
|
253
|
+
fullContent: contentParts.join('\n\n---\n\n'),
|
|
254
|
+
standardNames // For debugging/introspection
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get list of standards for an agent (without loading content)
|
|
260
|
+
* @param {string} agentId - Agent ID
|
|
261
|
+
* @returns {string[]} Array of standard names
|
|
262
|
+
*/
|
|
263
|
+
export function getStandardsListForAgent(agentId) {
|
|
264
|
+
return AGENT_STANDARDS_MAP[agentId] || [];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if a standard exists in any location
|
|
269
|
+
* @param {string} standardName - Standard filename without .md extension
|
|
270
|
+
* @param {string} projectPath - Root path of the project
|
|
271
|
+
* @returns {Object} { exists: boolean, location: string|null }
|
|
272
|
+
*/
|
|
273
|
+
export function checkStandardExists(standardName, projectPath = '.') {
|
|
274
|
+
const locations = [
|
|
275
|
+
{ type: 'project', path: join(projectPath, '.morph/project/standards', `${standardName}.md`) },
|
|
276
|
+
{ type: 'content', path: join(projectPath, 'content/.morph/standards', `${standardName}.md`) },
|
|
277
|
+
{ type: 'framework', path: join(projectPath, 'framework/standards', `${standardName}.md`) }
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
for (const loc of locations) {
|
|
281
|
+
if (existsSync(loc.path)) {
|
|
282
|
+
return { exists: true, location: loc.type, path: loc.path };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { exists: false, location: null, path: null };
|
|
287
|
+
}
|