@atlashub/smartstack-cli 3.43.0 → 3.45.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.
Files changed (47) hide show
  1. package/dist/mcp-entry.mjs +201 -22
  2. package/dist/mcp-entry.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/agents/efcore/conflicts.md +22 -2
  5. package/templates/agents/efcore/migration.md +11 -0
  6. package/templates/agents/efcore/rebase-snapshot.md +7 -0
  7. package/templates/agents/efcore/scan.md +24 -2
  8. package/templates/agents/efcore/squash.md +7 -0
  9. package/templates/agents/gitflow/init.md +195 -12
  10. package/templates/skills/apex/SKILL.md +14 -9
  11. package/templates/skills/apex/_shared.md +3 -0
  12. package/templates/skills/apex/references/analysis-methods.md +1 -1
  13. package/templates/skills/apex/references/challenge-questions.md +21 -0
  14. package/templates/skills/apex/references/core-seed-data.md +59 -104
  15. package/templates/skills/apex/references/post-checks.md +289 -225
  16. package/templates/skills/apex/references/smartstack-api.md +33 -35
  17. package/templates/skills/apex/references/smartstack-frontend.md +99 -3
  18. package/templates/skills/apex/references/smartstack-layers.md +145 -23
  19. package/templates/skills/apex/steps/step-00-init.md +2 -2
  20. package/templates/skills/apex/steps/step-01-analyze.md +1 -0
  21. package/templates/skills/apex/steps/step-02-plan.md +4 -3
  22. package/templates/skills/apex/steps/step-03-execute.md +24 -24
  23. package/templates/skills/apex/steps/step-04-examine.md +64 -24
  24. package/templates/skills/apex/steps/step-05-deep-review.md +1 -1
  25. package/templates/skills/apex/steps/step-08-run-tests.md +21 -13
  26. package/templates/skills/application/references/application-roles-template.md +10 -15
  27. package/templates/skills/application/references/backend-entity-seeding.md +6 -5
  28. package/templates/skills/application/references/backend-seeding-and-dto-output.md +1 -1
  29. package/templates/skills/application/references/nav-fallback-procedure.md +14 -17
  30. package/templates/skills/application/references/provider-template.md +5 -5
  31. package/templates/skills/application/references/roles-client-project-handling.md +1 -1
  32. package/templates/skills/application/references/roles-fallback-procedure.md +10 -15
  33. package/templates/skills/application/steps/step-01-navigation.md +1 -1
  34. package/templates/skills/application/steps/step-02-permissions.md +3 -3
  35. package/templates/skills/application/steps/step-03b-provider.md +1 -0
  36. package/templates/skills/application/templates-seed.md +41 -47
  37. package/templates/skills/business-analyse/references/team-orchestration.md +2 -2
  38. package/templates/skills/controller/steps/step-04-perms.md +1 -1
  39. package/templates/skills/efcore/references/troubleshooting.md +2 -2
  40. package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +2 -2
  41. package/templates/skills/efcore/steps/squash/step-00-init.md +2 -2
  42. package/templates/skills/apex/references/examine-build-validation.md +0 -82
  43. package/templates/skills/apex/references/execution-frontend-gates.md +0 -177
  44. package/templates/skills/apex/references/execution-frontend-patterns.md +0 -105
  45. package/templates/skills/apex/references/execution-layer1-rules.md +0 -96
  46. package/templates/skills/apex/references/initialization-challenge-flow.md +0 -110
  47. package/templates/skills/apex/references/planning-layer-mapping.md +0 -151
@@ -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 < 3) {
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 3 levels: "application.module.section" (e.g., "administration.users.management")'
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: _name } of DIRECT_DB_PATTERNS) {
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 hasComplexQuery = /\.Include\(/.test(file.content) && /\.ThenInclude\(/.test(file.content);
62650
- if (hasComplexQuery) {
62651
- const lineNumber = getLineNumber4(file.content, match3.index);
62652
- findings.push({
62653
- id: generateFindingId("architecture"),
62654
- category: "architecture",
62655
- severity: "warning",
62656
- title: "Complex Query in Controller",
62657
- description: "Controllers with complex queries (multiple Includes) should consider moving logic to a service.",
62658
- file: file.relativePath,
62659
- line: lineNumber,
62660
- suggestion: "Consider extracting complex query logic to an Application layer service.",
62661
- autoFixable: false
62662
- });
62663
- break;
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
  }