@atlashub/smartstack-cli 3.43.0 → 3.44.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/dist/mcp-entry.mjs +201 -22
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +11 -0
- package/templates/agents/efcore/rebase-snapshot.md +7 -0
- package/templates/agents/efcore/squash.md +7 -0
- package/templates/skills/apex/SKILL.md +14 -9
- package/templates/skills/apex/_shared.md +3 -0
- package/templates/skills/apex/references/analysis-methods.md +1 -1
- package/templates/skills/apex/references/challenge-questions.md +21 -0
- package/templates/skills/apex/references/core-seed-data.md +59 -104
- package/templates/skills/apex/references/post-checks.md +289 -225
- package/templates/skills/apex/references/smartstack-api.md +33 -35
- package/templates/skills/apex/references/smartstack-frontend.md +99 -3
- package/templates/skills/apex/references/smartstack-layers.md +145 -23
- package/templates/skills/apex/steps/step-00-init.md +2 -2
- package/templates/skills/apex/steps/step-01-analyze.md +1 -0
- package/templates/skills/apex/steps/step-02-plan.md +4 -3
- package/templates/skills/apex/steps/step-03-execute.md +24 -24
- package/templates/skills/apex/steps/step-04-examine.md +64 -24
- package/templates/skills/apex/steps/step-05-deep-review.md +1 -1
- package/templates/skills/apex/steps/step-08-run-tests.md +21 -13
- package/templates/skills/application/references/application-roles-template.md +10 -15
- package/templates/skills/application/references/backend-entity-seeding.md +6 -5
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +1 -1
- package/templates/skills/application/references/nav-fallback-procedure.md +14 -17
- package/templates/skills/application/references/provider-template.md +5 -5
- package/templates/skills/application/references/roles-client-project-handling.md +1 -1
- package/templates/skills/application/references/roles-fallback-procedure.md +10 -15
- package/templates/skills/application/steps/step-01-navigation.md +1 -1
- package/templates/skills/application/steps/step-02-permissions.md +3 -3
- package/templates/skills/application/steps/step-03b-provider.md +1 -0
- package/templates/skills/application/templates-seed.md +41 -47
- package/templates/skills/business-analyse/references/team-orchestration.md +2 -2
- package/templates/skills/controller/steps/step-04-perms.md +1 -1
- package/templates/skills/efcore/references/troubleshooting.md +2 -2
- package/templates/skills/apex/references/examine-build-validation.md +0 -82
- package/templates/skills/apex/references/execution-frontend-gates.md +0 -177
- package/templates/skills/apex/references/execution-frontend-patterns.md +0 -105
- package/templates/skills/apex/references/execution-layer1-rules.md +0 -96
- package/templates/skills/apex/references/initialization-challenge-flow.md +0 -110
- package/templates/skills/apex/references/planning-layer-mapping.md +0 -151
package/dist/mcp-entry.mjs
CHANGED
|
@@ -25881,7 +25881,7 @@ var init_types3 = __esm({
|
|
|
25881
25881
|
TenantModeSchema = external_exports.enum(["strict", "optional", "scoped", "none"]).default("strict");
|
|
25882
25882
|
ValidateConventionsInputSchema = external_exports.object({
|
|
25883
25883
|
path: external_exports.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
|
|
25884
|
-
checks: external_exports.array(external_exports.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
25884
|
+
checks: external_exports.array(external_exports.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns", "architecture", "all"])).default(["all"]).describe("Types of checks to perform")
|
|
25885
25885
|
});
|
|
25886
25886
|
CheckMigrationsInputSchema = external_exports.object({
|
|
25887
25887
|
projectPath: external_exports.string().optional().describe("EF Core project path"),
|
|
@@ -26696,7 +26696,7 @@ import path8 from "path";
|
|
|
26696
26696
|
async function handleValidateConventions(args, config2) {
|
|
26697
26697
|
const input = ValidateConventionsInputSchema.parse(args);
|
|
26698
26698
|
const projectPath = input.path || config2.smartstack.projectPath;
|
|
26699
|
-
const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns"] : input.checks;
|
|
26699
|
+
const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns", "architecture"] : input.checks;
|
|
26700
26700
|
logger.info("Validating conventions", { projectPath, checks });
|
|
26701
26701
|
const result = {
|
|
26702
26702
|
valid: true,
|
|
@@ -26750,6 +26750,9 @@ async function handleValidateConventions(args, config2) {
|
|
|
26750
26750
|
if (checks.includes("code-patterns")) {
|
|
26751
26751
|
await validateCodePatterns(structure, config2, result);
|
|
26752
26752
|
}
|
|
26753
|
+
if (checks.includes("architecture")) {
|
|
26754
|
+
await validateArchitecture(structure, config2, result);
|
|
26755
|
+
}
|
|
26753
26756
|
result.valid = result.errors.length === 0;
|
|
26754
26757
|
result.summary = generateSummary(result, checks);
|
|
26755
26758
|
return formatResult(result);
|
|
@@ -27185,13 +27188,13 @@ async function validateControllerRoutes(structure, _config, result) {
|
|
|
27185
27188
|
if (navRouteMatch) {
|
|
27186
27189
|
const routePath = navRouteMatch[1];
|
|
27187
27190
|
const parts = routePath.split(".");
|
|
27188
|
-
if (parts.length <
|
|
27191
|
+
if (parts.length < 2) {
|
|
27189
27192
|
result.errors.push({
|
|
27190
27193
|
type: "error",
|
|
27191
27194
|
category: "controllers",
|
|
27192
27195
|
message: `Controller "${fileName}" has NavRoute with insufficient depth: "${routePath}"`,
|
|
27193
27196
|
file: path8.relative(structure.root, file),
|
|
27194
|
-
suggestion: 'NavRoute must have at least
|
|
27197
|
+
suggestion: 'NavRoute must have at least 2 levels: "application.module" (e.g., "administration.users")'
|
|
27195
27198
|
});
|
|
27196
27199
|
}
|
|
27197
27200
|
const hasUppercase = parts.some((part) => part !== part.toLowerCase());
|
|
@@ -28102,6 +28105,132 @@ async function validateCodePatterns(structure, _config, result) {
|
|
|
28102
28105
|
message: `Code patterns check completed: ${validatorFiles.length} validators, ${serviceFiles.length} services scanned`
|
|
28103
28106
|
});
|
|
28104
28107
|
}
|
|
28108
|
+
async function validateArchitecture(structure, _config, result) {
|
|
28109
|
+
const layers = [
|
|
28110
|
+
{ path: structure.domain, name: "Domain", forbidden: [".Application", ".Infrastructure", ".Api"] },
|
|
28111
|
+
{ path: structure.application, name: "Application", forbidden: [".Infrastructure", ".Api"] }
|
|
28112
|
+
];
|
|
28113
|
+
for (const layer of layers) {
|
|
28114
|
+
if (!layer.path) continue;
|
|
28115
|
+
const csFiles = await findFiles("**/*.cs", { cwd: layer.path });
|
|
28116
|
+
for (const file of csFiles) {
|
|
28117
|
+
const content = await readText(file);
|
|
28118
|
+
const lines = content.split("\n");
|
|
28119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28120
|
+
const line = lines[i];
|
|
28121
|
+
if (!line.trimStart().startsWith("using ")) continue;
|
|
28122
|
+
for (const forbidden of layer.forbidden) {
|
|
28123
|
+
if (line.includes(forbidden)) {
|
|
28124
|
+
result.errors.push({
|
|
28125
|
+
type: "error",
|
|
28126
|
+
category: "architecture",
|
|
28127
|
+
message: `${layer.name} layer imports forbidden namespace: ${line.trim()}`,
|
|
28128
|
+
file: path8.relative(structure.root, file),
|
|
28129
|
+
line: i + 1,
|
|
28130
|
+
suggestion: `${layer.name} must not depend on ${forbidden.replace(".", "")}. Move shared types to Domain or define interfaces in Application.`
|
|
28131
|
+
});
|
|
28132
|
+
}
|
|
28133
|
+
}
|
|
28134
|
+
}
|
|
28135
|
+
}
|
|
28136
|
+
}
|
|
28137
|
+
if (structure.api) {
|
|
28138
|
+
const controllerFiles = await findFiles("**/Controllers/**/*Controller.cs", { cwd: structure.api });
|
|
28139
|
+
for (const file of controllerFiles) {
|
|
28140
|
+
const content = await readText(file);
|
|
28141
|
+
const dbContextFieldPattern = /private\s+readonly\s+\w*DbContext\b/;
|
|
28142
|
+
const dbContextCtorPattern = /\w*DbContext\s+\w+[,)]/;
|
|
28143
|
+
if (dbContextFieldPattern.test(content) || dbContextCtorPattern.test(content)) {
|
|
28144
|
+
result.errors.push({
|
|
28145
|
+
type: "error",
|
|
28146
|
+
category: "architecture",
|
|
28147
|
+
message: `Controller injects DbContext directly \u2014 violates Clean Architecture`,
|
|
28148
|
+
file: path8.relative(structure.root, file),
|
|
28149
|
+
suggestion: "Controllers must use Application services, not inject DbContext. Create an Application service with the required business logic."
|
|
28150
|
+
});
|
|
28151
|
+
}
|
|
28152
|
+
}
|
|
28153
|
+
}
|
|
28154
|
+
if (structure.api && structure.domain) {
|
|
28155
|
+
const domainFiles = await findFiles("**/Entities/**/*.cs", { cwd: structure.domain });
|
|
28156
|
+
const entityNames = /* @__PURE__ */ new Set();
|
|
28157
|
+
for (const file of domainFiles) {
|
|
28158
|
+
const content = await readText(file);
|
|
28159
|
+
const classMatch = content.match(/public\s+class\s+(\w+)\s*:/);
|
|
28160
|
+
if (classMatch) {
|
|
28161
|
+
const name = classMatch[1];
|
|
28162
|
+
if (!name.endsWith("Dto") && !name.endsWith("Response") && !name.endsWith("ViewModel") && !name.endsWith("Command") && !name.endsWith("Query") && !name.startsWith("I")) {
|
|
28163
|
+
entityNames.add(name);
|
|
28164
|
+
}
|
|
28165
|
+
}
|
|
28166
|
+
}
|
|
28167
|
+
if (entityNames.size > 0) {
|
|
28168
|
+
const controllerFiles = await findFiles("**/Controllers/**/*Controller.cs", { cwd: structure.api });
|
|
28169
|
+
for (const file of controllerFiles) {
|
|
28170
|
+
const content = await readText(file);
|
|
28171
|
+
for (const entityName of entityNames) {
|
|
28172
|
+
const returnPattern = new RegExp(`ActionResult<${entityName}>|ActionResult<IEnumerable<${entityName}>>|ActionResult<List<${entityName}>>`, "g");
|
|
28173
|
+
if (returnPattern.test(content)) {
|
|
28174
|
+
result.warnings.push({
|
|
28175
|
+
type: "warning",
|
|
28176
|
+
category: "architecture",
|
|
28177
|
+
message: `Controller returns Domain entity "${entityName}" instead of a DTO`,
|
|
28178
|
+
file: path8.relative(structure.root, file),
|
|
28179
|
+
suggestion: `Return ${entityName}ResponseDto instead of ${entityName}. API responses should never expose Domain entities directly.`
|
|
28180
|
+
});
|
|
28181
|
+
}
|
|
28182
|
+
}
|
|
28183
|
+
}
|
|
28184
|
+
}
|
|
28185
|
+
}
|
|
28186
|
+
if (structure.application) {
|
|
28187
|
+
const appServiceFiles = await findFiles("**/*Service.cs", { cwd: structure.application });
|
|
28188
|
+
for (const file of appServiceFiles) {
|
|
28189
|
+
const fileName = path8.basename(file, ".cs");
|
|
28190
|
+
if (fileName.startsWith("I")) continue;
|
|
28191
|
+
const content = await readText(file);
|
|
28192
|
+
if (/public\s+class\s+\w+Service/.test(content)) {
|
|
28193
|
+
result.warnings.push({
|
|
28194
|
+
type: "warning",
|
|
28195
|
+
category: "architecture",
|
|
28196
|
+
message: `Service implementation "${fileName}" found in Application layer`,
|
|
28197
|
+
file: path8.relative(structure.root, file),
|
|
28198
|
+
suggestion: "Service implementations belong in Infrastructure. Application layer should only contain interfaces (I*Service)."
|
|
28199
|
+
});
|
|
28200
|
+
}
|
|
28201
|
+
}
|
|
28202
|
+
}
|
|
28203
|
+
if (structure.domain) {
|
|
28204
|
+
const domainServiceFiles = await findFiles("**/I*Service.cs", { cwd: structure.domain });
|
|
28205
|
+
for (const file of domainServiceFiles) {
|
|
28206
|
+
const content = await readText(file);
|
|
28207
|
+
if (/public\s+interface\s+I\w+Service/.test(content)) {
|
|
28208
|
+
result.warnings.push({
|
|
28209
|
+
type: "warning",
|
|
28210
|
+
category: "architecture",
|
|
28211
|
+
message: `Service interface found in Domain layer: ${path8.basename(file)}`,
|
|
28212
|
+
file: path8.relative(structure.root, file),
|
|
28213
|
+
suggestion: "Service interfaces belong in Application layer, not Domain. Move to Application/Interfaces/."
|
|
28214
|
+
});
|
|
28215
|
+
}
|
|
28216
|
+
}
|
|
28217
|
+
}
|
|
28218
|
+
if (structure.api) {
|
|
28219
|
+
const apiServiceFiles = await findFiles("**/I*Service.cs", { cwd: structure.api });
|
|
28220
|
+
for (const file of apiServiceFiles) {
|
|
28221
|
+
const content = await readText(file);
|
|
28222
|
+
if (/public\s+interface\s+I\w+Service/.test(content)) {
|
|
28223
|
+
result.warnings.push({
|
|
28224
|
+
type: "warning",
|
|
28225
|
+
category: "architecture",
|
|
28226
|
+
message: `Service interface found in Api layer: ${path8.basename(file)}`,
|
|
28227
|
+
file: path8.relative(structure.root, file),
|
|
28228
|
+
suggestion: "Service interfaces belong in Application layer, not Api. Move to Application/Interfaces/."
|
|
28229
|
+
});
|
|
28230
|
+
}
|
|
28231
|
+
}
|
|
28232
|
+
}
|
|
28233
|
+
}
|
|
28105
28234
|
function generateSummary(result, checks) {
|
|
28106
28235
|
const parts = [];
|
|
28107
28236
|
parts.push(`Checks performed: ${checks.join(", ")}`);
|
|
@@ -28170,7 +28299,7 @@ var init_validate_conventions = __esm({
|
|
|
28170
28299
|
type: "array",
|
|
28171
28300
|
items: {
|
|
28172
28301
|
type: "string",
|
|
28173
|
-
enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns", "all"]
|
|
28302
|
+
enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "hierarchies", "protected-actions", "permissions", "frontend-routes", "feature-json", "code-patterns", "architecture", "all"]
|
|
28174
28303
|
},
|
|
28175
28304
|
description: "Types of checks to perform",
|
|
28176
28305
|
default: ["all"]
|
|
@@ -62642,26 +62771,76 @@ async function checkArchitecture(context) {
|
|
|
62642
62771
|
}
|
|
62643
62772
|
const isController = /Controller\.cs$/i.test(file.relativePath);
|
|
62644
62773
|
if (isController) {
|
|
62645
|
-
for (const { pattern, name:
|
|
62774
|
+
for (const { pattern, name: patternName } of DIRECT_DB_PATTERNS) {
|
|
62646
62775
|
let match3;
|
|
62647
62776
|
pattern.lastIndex = 0;
|
|
62648
62777
|
while ((match3 = pattern.exec(file.content)) !== null) {
|
|
62649
|
-
const
|
|
62650
|
-
|
|
62651
|
-
|
|
62652
|
-
|
|
62653
|
-
|
|
62654
|
-
|
|
62655
|
-
|
|
62656
|
-
|
|
62657
|
-
|
|
62658
|
-
|
|
62659
|
-
|
|
62660
|
-
|
|
62661
|
-
|
|
62662
|
-
|
|
62663
|
-
|
|
62664
|
-
|
|
62778
|
+
const lineNumber = getLineNumber4(file.content, match3.index);
|
|
62779
|
+
const lineContent = lines[lineNumber - 1]?.trim() || "";
|
|
62780
|
+
findings.push({
|
|
62781
|
+
id: generateFindingId("architecture"),
|
|
62782
|
+
category: "architecture",
|
|
62783
|
+
severity: "critical",
|
|
62784
|
+
title: "Controller Injects DbContext Directly",
|
|
62785
|
+
description: `${patternName} detected in controller. Controllers should use Application services, not inject DbContext directly. This violates Clean Architecture.`,
|
|
62786
|
+
file: file.relativePath,
|
|
62787
|
+
line: lineNumber,
|
|
62788
|
+
code: truncateCode3(lineContent),
|
|
62789
|
+
suggestion: "Create an Application service with the required business logic and inject it instead.",
|
|
62790
|
+
autoFixable: false
|
|
62791
|
+
});
|
|
62792
|
+
break;
|
|
62793
|
+
}
|
|
62794
|
+
}
|
|
62795
|
+
}
|
|
62796
|
+
if (isController && fileLayer === "api") {
|
|
62797
|
+
const returnTypePattern = /Task<ActionResult<(?!.*(?:Dto|Response|ViewModel|PaginatedResult))(\w+)>>/g;
|
|
62798
|
+
let returnMatch;
|
|
62799
|
+
while ((returnMatch = returnTypePattern.exec(file.content)) !== null) {
|
|
62800
|
+
const entityName = returnMatch[1];
|
|
62801
|
+
if (["string", "int", "bool", "Guid", "object", "IActionResult"].includes(entityName)) continue;
|
|
62802
|
+
const lineNumber = getLineNumber4(file.content, returnMatch.index);
|
|
62803
|
+
const lineContent = lines[lineNumber - 1]?.trim() || "";
|
|
62804
|
+
findings.push({
|
|
62805
|
+
id: generateFindingId("architecture"),
|
|
62806
|
+
category: "architecture",
|
|
62807
|
+
severity: "warning",
|
|
62808
|
+
title: "API Returns Domain Entity",
|
|
62809
|
+
description: `Controller returns "${entityName}" which may be a Domain entity. API endpoints should return DTOs, not Domain entities.`,
|
|
62810
|
+
file: file.relativePath,
|
|
62811
|
+
line: lineNumber,
|
|
62812
|
+
code: truncateCode3(lineContent),
|
|
62813
|
+
suggestion: `Return ${entityName}ResponseDto instead of ${entityName}. Create a DTO in the Application layer.`,
|
|
62814
|
+
autoFixable: false
|
|
62815
|
+
});
|
|
62816
|
+
}
|
|
62817
|
+
}
|
|
62818
|
+
if (fileLayer === "domain") {
|
|
62819
|
+
const efCorePatterns = [
|
|
62820
|
+
{ pattern: /\[Table\s*\(/g, name: "[Table] attribute" },
|
|
62821
|
+
{ pattern: /\[Column\s*\(/g, name: "[Column] attribute" },
|
|
62822
|
+
{ pattern: /\[Index\s*\(/g, name: "[Index] attribute" },
|
|
62823
|
+
{ pattern: /using\s+Microsoft\.EntityFrameworkCore/g, name: "EF Core using directive" }
|
|
62824
|
+
];
|
|
62825
|
+
for (const { pattern: efPattern, name: efName } of efCorePatterns) {
|
|
62826
|
+
let efMatch;
|
|
62827
|
+
efPattern.lastIndex = 0;
|
|
62828
|
+
while ((efMatch = efPattern.exec(file.content)) !== null) {
|
|
62829
|
+
const lineNumber = getLineNumber4(file.content, efMatch.index);
|
|
62830
|
+
const lineContent = lines[lineNumber - 1]?.trim() || "";
|
|
62831
|
+
findings.push({
|
|
62832
|
+
id: generateFindingId("architecture"),
|
|
62833
|
+
category: "architecture",
|
|
62834
|
+
severity: "critical",
|
|
62835
|
+
title: "EF Core Concern in Domain Layer",
|
|
62836
|
+
description: `${efName} found in Domain layer. Domain entities must be persistence-ignorant \u2014 EF Core configuration belongs in Infrastructure.`,
|
|
62837
|
+
file: file.relativePath,
|
|
62838
|
+
line: lineNumber,
|
|
62839
|
+
code: truncateCode3(lineContent),
|
|
62840
|
+
suggestion: "Move EF Core configuration to Infrastructure/Persistence/Configurations/ using IEntityTypeConfiguration<T>.",
|
|
62841
|
+
autoFixable: false
|
|
62842
|
+
});
|
|
62843
|
+
break;
|
|
62665
62844
|
}
|
|
62666
62845
|
}
|
|
62667
62846
|
}
|