@atlashub/smartstack-mcp 1.14.0 → 1.15.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/index.js CHANGED
@@ -82,10 +82,10 @@ import { stat, mkdir, readFile, writeFile, cp, rm } from "fs/promises";
82
82
  import path from "path";
83
83
  import { glob } from "glob";
84
84
  var FileSystemError = class extends Error {
85
- constructor(message, operation, path24, cause) {
85
+ constructor(message, operation, path26, cause) {
86
86
  super(message);
87
87
  this.operation = operation;
88
- this.path = path24;
88
+ this.path = path26;
89
89
  this.cause = cause;
90
90
  this.name = "FileSystemError";
91
91
  }
@@ -648,6 +648,39 @@ var SuggestTestScenariosInputSchema = z.object({
648
648
  name: z.string().min(1).describe("Component name or file path"),
649
649
  depth: z.enum(["basic", "comprehensive", "security-focused"]).default("comprehensive").describe("Depth of analysis")
650
650
  });
651
+ var SecurityCheckSchema = z.enum([
652
+ "hardcoded-secrets",
653
+ "sql-injection",
654
+ "tenant-isolation",
655
+ "authorization",
656
+ "dangerous-functions",
657
+ "input-validation",
658
+ "xss",
659
+ "csrf",
660
+ "logging-sensitive",
661
+ "all"
662
+ ]);
663
+ var ValidateSecurityInputSchema = z.object({
664
+ path: z.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
665
+ checks: z.array(SecurityCheckSchema).default(["all"]).describe("Security checks to run"),
666
+ severity: z.enum(["blocking", "all"]).optional().describe("Filter results by severity")
667
+ });
668
+ var QualityMetricSchema = z.enum([
669
+ "cognitive-complexity",
670
+ "cyclomatic-complexity",
671
+ "function-size",
672
+ "nesting-depth",
673
+ "parameter-count",
674
+ "code-duplication",
675
+ "file-size",
676
+ "all"
677
+ ]);
678
+ var AnalyzeCodeQualityInputSchema = z.object({
679
+ path: z.string().optional().describe("Project path to analyze (default: SmartStack.app path)"),
680
+ metrics: z.array(QualityMetricSchema).default(["all"]).describe("Metrics to analyze"),
681
+ threshold: z.enum(["strict", "normal", "lenient"]).default("normal").describe("Threshold level for violations"),
682
+ scope: z.enum(["changed", "all"]).default("all").describe("Analyze only changed files or all")
683
+ });
651
684
  var ScaffoldApiClientInputSchema = z.object({
652
685
  path: z.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
653
686
  navRoute: z.string().min(1).describe('NavRoute path (e.g., "platform.administration.users")'),
@@ -9735,6 +9768,919 @@ function generateRecommendations3(analyzed) {
9735
9768
  return recommendations;
9736
9769
  }
9737
9770
 
9771
+ // src/tools/validate-security.ts
9772
+ import path20 from "path";
9773
+ var validateSecurityTool = {
9774
+ name: "validate_security",
9775
+ description: "Validate SmartStack security patterns: multi-tenant isolation, authorization, input validation, secrets exposure, injection vulnerabilities, and OWASP Top 10 compliance.",
9776
+ inputSchema: {
9777
+ type: "object",
9778
+ properties: {
9779
+ path: {
9780
+ type: "string",
9781
+ description: "Project path to validate (default: SmartStack.app path from config)"
9782
+ },
9783
+ checks: {
9784
+ type: "array",
9785
+ items: {
9786
+ type: "string",
9787
+ enum: [
9788
+ "hardcoded-secrets",
9789
+ "sql-injection",
9790
+ "tenant-isolation",
9791
+ "authorization",
9792
+ "dangerous-functions",
9793
+ "input-validation",
9794
+ "xss",
9795
+ "csrf",
9796
+ "logging-sensitive",
9797
+ "all"
9798
+ ]
9799
+ },
9800
+ description: "Security checks to run",
9801
+ default: ["all"]
9802
+ },
9803
+ severity: {
9804
+ type: "string",
9805
+ enum: ["blocking", "all"],
9806
+ description: "Filter results by severity"
9807
+ }
9808
+ }
9809
+ }
9810
+ };
9811
+ async function handleValidateSecurity(args, config) {
9812
+ const input = ValidateSecurityInputSchema.parse(args);
9813
+ const projectPath = input.path || config.smartstack.projectPath;
9814
+ const checksToRun = input.checks.includes("all") ? [
9815
+ "hardcoded-secrets",
9816
+ "sql-injection",
9817
+ "tenant-isolation",
9818
+ "authorization",
9819
+ "dangerous-functions",
9820
+ "input-validation"
9821
+ ] : input.checks;
9822
+ logger.info("Validating security", { projectPath, checks: checksToRun });
9823
+ const result = {
9824
+ valid: true,
9825
+ summary: "",
9826
+ findings: [],
9827
+ stats: {
9828
+ blocking: 0,
9829
+ critical: 0,
9830
+ warning: 0,
9831
+ filesScanned: 0
9832
+ }
9833
+ };
9834
+ const structure = await findSmartStackStructure(projectPath);
9835
+ if (checksToRun.includes("hardcoded-secrets")) {
9836
+ await checkHardcodedSecrets(structure, result);
9837
+ }
9838
+ if (checksToRun.includes("sql-injection")) {
9839
+ await checkSqlInjection(structure, result);
9840
+ }
9841
+ if (checksToRun.includes("tenant-isolation")) {
9842
+ await checkTenantIsolation(structure, result);
9843
+ }
9844
+ if (checksToRun.includes("authorization")) {
9845
+ await checkAuthorization(structure, result);
9846
+ }
9847
+ if (checksToRun.includes("dangerous-functions")) {
9848
+ await checkDangerousFunctions(structure, result);
9849
+ }
9850
+ if (checksToRun.includes("input-validation")) {
9851
+ await checkInputValidation(structure, result);
9852
+ }
9853
+ result.stats.blocking = result.findings.filter((f) => f.severity === "blocking").length;
9854
+ result.stats.critical = result.findings.filter((f) => f.severity === "critical").length;
9855
+ result.stats.warning = result.findings.filter((f) => f.severity === "warning").length;
9856
+ if (input.severity === "blocking") {
9857
+ result.findings = result.findings.filter((f) => f.severity === "blocking");
9858
+ }
9859
+ result.valid = result.stats.blocking === 0;
9860
+ result.summary = result.valid ? `Security validation passed. ${result.stats.warning} warning(s) found.` : `Security validation FAILED. ${result.stats.blocking} blocking issue(s) found.`;
9861
+ return formatSecurityReport(result);
9862
+ }
9863
+ async function checkHardcodedSecrets(structure, result) {
9864
+ const secretPatterns = [
9865
+ // Credentials
9866
+ { pattern: /(?:password|passwd|pwd)\s*[=:]\s*["'][^"']{4,}["']/gi, name: "password" },
9867
+ { pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*["'][^"']{8,}["']/gi, name: "API key" },
9868
+ { pattern: /(?:secret|token)\s*[=:]\s*["'][^"']{8,}["']/gi, name: "secret/token" },
9869
+ // Connection strings with embedded passwords
9870
+ { pattern: /Server=.+;.*Password=[^;]+/gi, name: "connection string with password" },
9871
+ { pattern: /Data Source=.+;.*Password=[^;]+/gi, name: "connection string with password" },
9872
+ // Private keys
9873
+ { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g, name: "private key" }
9874
+ ];
9875
+ const filesToScan = await getFilesToScan(structure, ["**/*.cs", "**/*.ts", "**/*.tsx", "**/*.json"]);
9876
+ result.stats.filesScanned += filesToScan.length;
9877
+ for (const file of filesToScan) {
9878
+ if (isExcludedFile(file)) continue;
9879
+ const content = await readText(file);
9880
+ const lines = content.split("\n");
9881
+ for (const { pattern, name } of secretPatterns) {
9882
+ pattern.lastIndex = 0;
9883
+ let match;
9884
+ while ((match = pattern.exec(content)) !== null) {
9885
+ const lineNumber = getLineNumber(content, match.index);
9886
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
9887
+ if (isPlaceholderValue(lineContent)) continue;
9888
+ result.findings.push({
9889
+ severity: "blocking",
9890
+ category: "hardcoded-secrets",
9891
+ message: `Hardcoded ${name} detected`,
9892
+ file: path20.relative(structure.root, file),
9893
+ line: lineNumber,
9894
+ code: truncateCode(lineContent),
9895
+ suggestion: `Move ${name} to configuration or environment variables`,
9896
+ cweId: "CWE-798"
9897
+ });
9898
+ }
9899
+ }
9900
+ }
9901
+ }
9902
+ async function checkSqlInjection(structure, result) {
9903
+ const sqlInjectionPatterns = [
9904
+ // Raw SQL with string concatenation (the most dangerous pattern)
9905
+ { pattern: /\.(?:FromSqlRaw|ExecuteSqlRaw)\s*\([^)]*\s*\+\s*/g, name: "concatenated SQL in FromSqlRaw/ExecuteSqlRaw" },
9906
+ // ADO.NET without parameters
9907
+ { pattern: /new SqlCommand\s*\([^)]*\+/g, name: "concatenated SQL in SqlCommand" },
9908
+ { pattern: /CommandText\s*=\s*[^;]*\+/g, name: "concatenated SQL in CommandText" }
9909
+ // Note: FromSqlInterpolated with $"" is safe and not flagged
9910
+ ];
9911
+ const csFiles = await getFilesToScan(structure, ["**/*.cs"]);
9912
+ result.stats.filesScanned += csFiles.length;
9913
+ for (const file of csFiles) {
9914
+ if (isExcludedFile(file)) continue;
9915
+ const content = await readText(file);
9916
+ const lines = content.split("\n");
9917
+ for (const { pattern, name } of sqlInjectionPatterns) {
9918
+ pattern.lastIndex = 0;
9919
+ let match;
9920
+ while ((match = pattern.exec(content)) !== null) {
9921
+ const lineNumber = getLineNumber(content, match.index);
9922
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
9923
+ result.findings.push({
9924
+ severity: "blocking",
9925
+ category: "sql-injection",
9926
+ message: `Potential SQL injection: ${name}`,
9927
+ file: path20.relative(structure.root, file),
9928
+ line: lineNumber,
9929
+ code: truncateCode(lineContent),
9930
+ suggestion: 'Use parameterized queries: FromSqlRaw("SELECT * FROM x WHERE id = {0}", id) or FromSqlInterpolated',
9931
+ cweId: "CWE-89"
9932
+ });
9933
+ }
9934
+ }
9935
+ }
9936
+ }
9937
+ async function checkTenantIsolation(structure, result) {
9938
+ if (!structure.domain) {
9939
+ result.findings.push({
9940
+ severity: "warning",
9941
+ category: "tenant-isolation",
9942
+ message: "Domain project not found, skipping tenant isolation validation",
9943
+ file: "",
9944
+ suggestion: "Ensure project structure follows SmartStack conventions"
9945
+ });
9946
+ return;
9947
+ }
9948
+ const entityFiles = await findFiles("**/Entities/**/*.cs", { cwd: structure.domain });
9949
+ const tenantEntities = [];
9950
+ for (const file of entityFiles) {
9951
+ const content = await readText(file);
9952
+ if (content.includes("ITenantEntity") || content.includes(": TenantEntity")) {
9953
+ const entityMatch = content.match(/class\s+(\w+)/);
9954
+ if (entityMatch) {
9955
+ tenantEntities.push(entityMatch[1]);
9956
+ }
9957
+ }
9958
+ const createMethodMatches = content.matchAll(/public\s+static\s+\w+\s+Create\s*\(([^)]*)\)/g);
9959
+ for (const match of createMethodMatches) {
9960
+ const params = match[1];
9961
+ if ((content.includes("ITenantEntity") || content.includes(": TenantEntity")) && !params.includes("tenantId") && !params.includes("TenantId")) {
9962
+ const lineNumber = getLineNumber(content, match.index);
9963
+ result.findings.push({
9964
+ severity: "blocking",
9965
+ category: "tenant-isolation",
9966
+ message: "Create method missing tenantId parameter for tenant entity",
9967
+ file: path20.relative(structure.root, file),
9968
+ line: lineNumber,
9969
+ code: truncateCode(match[0]),
9970
+ suggestion: "Add Guid tenantId as first parameter: Create(Guid tenantId, ...)",
9971
+ cweId: "CWE-639"
9972
+ });
9973
+ }
9974
+ }
9975
+ }
9976
+ if (structure.application) {
9977
+ const serviceFiles = await findFiles("**/*Service.cs", { cwd: structure.application });
9978
+ for (const file of serviceFiles) {
9979
+ const content = await readText(file);
9980
+ const directAccessPattern = /_context\.\w+\.(?:First|Single|Find|ToList)\s*\(/g;
9981
+ let match;
9982
+ while ((match = directAccessPattern.exec(content)) !== null) {
9983
+ const surroundingCode = content.substring(Math.max(0, match.index - 100), match.index + 100);
9984
+ if (!surroundingCode.includes(".Where(") && !surroundingCode.includes("TenantId")) {
9985
+ const lineNumber = getLineNumber(content, match.index);
9986
+ result.findings.push({
9987
+ severity: "critical",
9988
+ category: "tenant-isolation",
9989
+ message: "Direct DbContext access without tenant filtering",
9990
+ file: path20.relative(structure.root, file),
9991
+ line: lineNumber,
9992
+ code: truncateCode(match[0]),
9993
+ suggestion: "Use repository with global tenant filter or add explicit TenantId filter",
9994
+ cweId: "CWE-639"
9995
+ });
9996
+ }
9997
+ }
9998
+ }
9999
+ }
10000
+ }
10001
+ async function checkAuthorization(structure, result) {
10002
+ const apiPath = structure.api || structure.apiCore;
10003
+ if (!apiPath) {
10004
+ result.findings.push({
10005
+ severity: "warning",
10006
+ category: "authorization",
10007
+ message: "API project not found, skipping authorization validation",
10008
+ file: "",
10009
+ suggestion: "Ensure project structure follows SmartStack conventions"
10010
+ });
10011
+ return;
10012
+ }
10013
+ const controllerFiles = await findFiles("**/Controllers/**/*.cs", { cwd: apiPath });
10014
+ result.stats.filesScanned += controllerFiles.length;
10015
+ for (const file of controllerFiles) {
10016
+ const content = await readText(file);
10017
+ if (content.includes("[ApiController]")) {
10018
+ const hasClassLevelAuth = content.includes("[Authorize]") || content.includes("[NavRoute(") || content.includes("[AllowAnonymous]");
10019
+ if (!hasClassLevelAuth) {
10020
+ const controllerMatch = content.match(/class\s+(\w+Controller)/);
10021
+ const controllerName = controllerMatch ? controllerMatch[1] : "Unknown";
10022
+ const lineNumber = controllerMatch ? getLineNumber(content, controllerMatch.index) : 1;
10023
+ result.findings.push({
10024
+ severity: "blocking",
10025
+ category: "authorization",
10026
+ message: `Controller ${controllerName} missing authorization attribute`,
10027
+ file: path20.relative(structure.root, file),
10028
+ line: lineNumber,
10029
+ suggestion: 'Add [NavRoute("context.application.module")] or [Authorize] attribute to the controller class',
10030
+ cweId: "CWE-862"
10031
+ });
10032
+ }
10033
+ }
10034
+ }
10035
+ }
10036
+ async function checkDangerousFunctions(structure, result) {
10037
+ const csharpPatterns = [
10038
+ { pattern: /Process\.Start\s*\([^)]*(?:user|input|param|request)/gi, name: "Process.Start with user input" },
10039
+ { pattern: /Assembly\.Load(?:From|File)?\s*\(/g, name: "Dynamic assembly loading" },
10040
+ { pattern: /Type\.GetType\s*\([^)]*(?:user|input|param|request)/gi, name: "Type.GetType with user input" },
10041
+ { pattern: /Activator\.CreateInstance\s*\([^)]*(?:user|input|param|request)/gi, name: "Activator.CreateInstance with user input" }
10042
+ ];
10043
+ const typescriptPatterns = [
10044
+ { pattern: /\beval\s*\(/g, name: "eval() usage" },
10045
+ { pattern: /new\s+Function\s*\(/g, name: "new Function() usage" },
10046
+ { pattern: /child_process\.exec\s*\(/g, name: "child_process.exec usage" },
10047
+ { pattern: /\.innerHTML\s*=/g, name: "innerHTML assignment" },
10048
+ { pattern: /dangerouslySetInnerHTML/g, name: "dangerouslySetInnerHTML usage" }
10049
+ ];
10050
+ const csFiles = await getFilesToScan(structure, ["**/*.cs"]);
10051
+ for (const file of csFiles) {
10052
+ if (isExcludedFile(file)) continue;
10053
+ const content = await readText(file);
10054
+ await checkPatterns(file, content, csharpPatterns, structure, result);
10055
+ }
10056
+ const tsFiles = await getFilesToScan(structure, ["**/*.ts", "**/*.tsx"]);
10057
+ for (const file of tsFiles) {
10058
+ if (isExcludedFile(file)) continue;
10059
+ const content = await readText(file);
10060
+ await checkPatterns(file, content, typescriptPatterns, structure, result);
10061
+ }
10062
+ }
10063
+ async function checkPatterns(file, content, patterns, structure, result) {
10064
+ const lines = content.split("\n");
10065
+ for (const { pattern, name } of patterns) {
10066
+ pattern.lastIndex = 0;
10067
+ let match;
10068
+ while ((match = pattern.exec(content)) !== null) {
10069
+ const lineNumber = getLineNumber(content, match.index);
10070
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
10071
+ result.findings.push({
10072
+ severity: "critical",
10073
+ category: "dangerous-functions",
10074
+ message: `Dangerous function: ${name}`,
10075
+ file: path20.relative(structure.root, file),
10076
+ line: lineNumber,
10077
+ code: truncateCode(lineContent),
10078
+ suggestion: "Avoid using dangerous functions with user-controlled input. Use safe alternatives.",
10079
+ cweId: "CWE-78"
10080
+ });
10081
+ }
10082
+ }
10083
+ }
10084
+ async function checkInputValidation(structure, result) {
10085
+ if (!structure.application) {
10086
+ return;
10087
+ }
10088
+ const dtoFiles = await findFiles("**/*Dto.cs", { cwd: structure.application });
10089
+ const dtos = [];
10090
+ for (const file of dtoFiles) {
10091
+ const content = await readText(file);
10092
+ const dtoMatch = content.match(/class\s+(\w+Dto)/);
10093
+ if (dtoMatch) {
10094
+ dtos.push(dtoMatch[1]);
10095
+ }
10096
+ }
10097
+ const validatorFiles = await findFiles("**/*Validator.cs", { cwd: structure.application });
10098
+ const validators = [];
10099
+ for (const file of validatorFiles) {
10100
+ const content = await readText(file);
10101
+ const validatorMatch = content.match(/class\s+(\w+Validator)/);
10102
+ if (validatorMatch) {
10103
+ validators.push(validatorMatch[1]);
10104
+ }
10105
+ }
10106
+ for (const dto of dtos) {
10107
+ const expectedValidator = dto.replace(/Dto$/, "DtoValidator");
10108
+ const hasValidator = validators.some((v) => v === expectedValidator || v === `${dto}Validator`);
10109
+ if (!hasValidator) {
10110
+ result.findings.push({
10111
+ severity: "warning",
10112
+ category: "input-validation",
10113
+ message: `DTO ${dto} has no FluentValidation validator`,
10114
+ file: `Application/DTOs/${dto}.cs`,
10115
+ suggestion: `Create ${expectedValidator} class with FluentValidation rules`,
10116
+ cweId: "CWE-20"
10117
+ });
10118
+ }
10119
+ }
10120
+ }
10121
+ async function getFilesToScan(structure, patterns) {
10122
+ const allFiles = [];
10123
+ for (const pattern of patterns) {
10124
+ const files = await findFiles(pattern, { cwd: structure.root });
10125
+ for (const file of files) {
10126
+ try {
10127
+ validatePathSecurity(file, structure.root);
10128
+ allFiles.push(file);
10129
+ } catch {
10130
+ logger.warn("Skipping file outside project root", { file });
10131
+ }
10132
+ }
10133
+ }
10134
+ return [...new Set(allFiles)];
10135
+ }
10136
+ function isExcludedFile(filePath) {
10137
+ const exclusions = [
10138
+ /[/\\]bin[/\\]/,
10139
+ /[/\\]obj[/\\]/,
10140
+ /[/\\]node_modules[/\\]/,
10141
+ /\.example\./,
10142
+ /\.template\./,
10143
+ /\.test\./,
10144
+ /\.spec\./,
10145
+ /Tests[/\\]/,
10146
+ /appsettings\.Development\.json$/,
10147
+ /appsettings\.Template\.json$/
10148
+ ];
10149
+ return exclusions.some((pattern) => pattern.test(filePath));
10150
+ }
10151
+ function isPlaceholderValue(line) {
10152
+ const placeholderPatterns = [
10153
+ /\${[A-Z_][A-Z0-9_]*}/,
10154
+ // ${VAR_NAME} - env var syntax
10155
+ /%[A-Z_][A-Z0-9_]*%/,
10156
+ // %VAR_NAME% - Windows env var
10157
+ /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/,
10158
+ // {{varName}} - template syntax
10159
+ /["']your[_-]?(?:password|secret|key|token)["']/i,
10160
+ // "your_password" placeholders
10161
+ /["']changeme["']/i,
10162
+ /["']<[A-Z_]+>["']/,
10163
+ // "<REPLACE_ME>" syntax
10164
+ /["']xxx+["']/i,
10165
+ // "xxx" or "XXXX" placeholders
10166
+ /=\s*["']["']/
10167
+ // Empty string assignments
10168
+ ];
10169
+ return placeholderPatterns.some((pattern) => pattern.test(line));
10170
+ }
10171
+ function getLineNumber(content, index) {
10172
+ const normalizedContent = content.substring(0, index).replace(/\r\n/g, "\n");
10173
+ return normalizedContent.split("\n").length;
10174
+ }
10175
+ function truncateCode(code, maxLength = 80) {
10176
+ return code.length > maxLength ? code.substring(0, maxLength) + "..." : code;
10177
+ }
10178
+ function formatSecurityReport(result) {
10179
+ const lines = [];
10180
+ lines.push("# Security Validation Report");
10181
+ lines.push("");
10182
+ lines.push("## Summary");
10183
+ lines.push(`- **Status**: ${result.valid ? "\u2705 PASSED" : "\u274C FAILED"}`);
10184
+ lines.push(`- **Blocking**: ${result.stats.blocking}`);
10185
+ lines.push(`- **Critical**: ${result.stats.critical}`);
10186
+ lines.push(`- **Warnings**: ${result.stats.warning}`);
10187
+ lines.push(`- **Files Scanned**: ${result.stats.filesScanned}`);
10188
+ lines.push("");
10189
+ if (result.findings.length === 0) {
10190
+ lines.push("No security issues found.");
10191
+ return lines.join("\n");
10192
+ }
10193
+ const blocking = result.findings.filter((f) => f.severity === "blocking");
10194
+ const critical = result.findings.filter((f) => f.severity === "critical");
10195
+ const warnings = result.findings.filter((f) => f.severity === "warning");
10196
+ if (blocking.length > 0) {
10197
+ lines.push("## Blocking Issues");
10198
+ lines.push("");
10199
+ for (const finding of blocking) {
10200
+ formatFinding(finding, lines);
10201
+ }
10202
+ }
10203
+ if (critical.length > 0) {
10204
+ lines.push("## Critical Issues");
10205
+ lines.push("");
10206
+ for (const finding of critical) {
10207
+ formatFinding(finding, lines);
10208
+ }
10209
+ }
10210
+ if (warnings.length > 0) {
10211
+ lines.push("## Warnings");
10212
+ lines.push("");
10213
+ for (const finding of warnings) {
10214
+ formatFinding(finding, lines);
10215
+ }
10216
+ }
10217
+ return lines.join("\n");
10218
+ }
10219
+ function formatFinding(finding, lines) {
10220
+ lines.push(`### ${finding.category}: ${finding.message}`);
10221
+ if (finding.file) {
10222
+ lines.push(`- **File**: \`${finding.file}${finding.line ? `:${finding.line}` : ""}\``);
10223
+ }
10224
+ if (finding.code) {
10225
+ lines.push(`- **Code**: \`${finding.code}\``);
10226
+ }
10227
+ if (finding.cweId) {
10228
+ lines.push(`- **CWE**: ${finding.cweId}`);
10229
+ }
10230
+ lines.push(`- **Fix**: ${finding.suggestion}`);
10231
+ lines.push("");
10232
+ }
10233
+
10234
+ // src/tools/analyze-code-quality.ts
10235
+ import path21 from "path";
10236
+ var analyzeCodeQualityTool = {
10237
+ name: "analyze_code_quality",
10238
+ description: "Analyze code quality metrics for SmartStack projects: cognitive complexity, cyclomatic complexity, function size, nesting depth, and maintainability indicators.",
10239
+ inputSchema: {
10240
+ type: "object",
10241
+ properties: {
10242
+ path: {
10243
+ type: "string",
10244
+ description: "Project path to analyze (default: SmartStack.app path from config)"
10245
+ },
10246
+ metrics: {
10247
+ type: "array",
10248
+ items: {
10249
+ type: "string",
10250
+ enum: [
10251
+ "cognitive-complexity",
10252
+ "cyclomatic-complexity",
10253
+ "function-size",
10254
+ "nesting-depth",
10255
+ "parameter-count",
10256
+ "code-duplication",
10257
+ "file-size",
10258
+ "all"
10259
+ ]
10260
+ },
10261
+ description: "Metrics to analyze",
10262
+ default: ["all"]
10263
+ },
10264
+ threshold: {
10265
+ type: "string",
10266
+ enum: ["strict", "normal", "lenient"],
10267
+ description: "Threshold level for violations",
10268
+ default: "normal"
10269
+ },
10270
+ scope: {
10271
+ type: "string",
10272
+ enum: ["changed", "all"],
10273
+ description: "Analyze only changed files or all",
10274
+ default: "all"
10275
+ }
10276
+ }
10277
+ }
10278
+ };
10279
+ var THRESHOLDS = {
10280
+ strict: {
10281
+ cognitiveComplexity: 10,
10282
+ cyclomaticComplexity: 8,
10283
+ functionSize: 30,
10284
+ nestingDepth: 2,
10285
+ parameterCount: 3,
10286
+ fileSize: 300
10287
+ },
10288
+ normal: {
10289
+ cognitiveComplexity: 15,
10290
+ cyclomaticComplexity: 10,
10291
+ functionSize: 50,
10292
+ nestingDepth: 3,
10293
+ parameterCount: 4,
10294
+ fileSize: 500
10295
+ },
10296
+ lenient: {
10297
+ cognitiveComplexity: 25,
10298
+ cyclomaticComplexity: 15,
10299
+ functionSize: 80,
10300
+ nestingDepth: 4,
10301
+ parameterCount: 5,
10302
+ fileSize: 800
10303
+ }
10304
+ };
10305
+ async function handleAnalyzeCodeQuality(args, config) {
10306
+ const input = AnalyzeCodeQualityInputSchema.parse(args);
10307
+ const projectPath = input.path || config.smartstack.projectPath;
10308
+ const thresholdLevel = input.threshold;
10309
+ const thresholds = THRESHOLDS[thresholdLevel];
10310
+ logger.info("Analyzing code quality", { projectPath, threshold: thresholdLevel });
10311
+ const structure = await findSmartStackStructure(projectPath);
10312
+ const allFunctionMetrics = [];
10313
+ const fileMetrics = /* @__PURE__ */ new Map();
10314
+ const csFiles = await findFiles("**/*.cs", { cwd: structure.root });
10315
+ const filteredCsFiles = csFiles.filter((f) => !isExcludedPath(f));
10316
+ for (const file of filteredCsFiles) {
10317
+ const content = await readText(file);
10318
+ const relPath = path21.relative(structure.root, file);
10319
+ const lineCount = content.split("\n").length;
10320
+ const functions = extractCSharpFunctions(content, relPath);
10321
+ allFunctionMetrics.push(...functions);
10322
+ fileMetrics.set(relPath, { lineCount, functions: functions.length });
10323
+ }
10324
+ const tsFiles = await findFiles("**/*.{ts,tsx}", { cwd: structure.root });
10325
+ const filteredTsFiles = tsFiles.filter((f) => !isExcludedPath(f));
10326
+ for (const file of filteredTsFiles) {
10327
+ const content = await readText(file);
10328
+ const relPath = path21.relative(structure.root, file);
10329
+ const lineCount = content.split("\n").length;
10330
+ const functions = extractTypeScriptFunctions(content, relPath);
10331
+ allFunctionMetrics.push(...functions);
10332
+ fileMetrics.set(relPath, { lineCount, functions: functions.length });
10333
+ }
10334
+ const metrics = calculateMetrics(allFunctionMetrics, fileMetrics, thresholds);
10335
+ const hotspots = identifyHotspots(allFunctionMetrics, fileMetrics, thresholds);
10336
+ const summary = calculateSummary(allFunctionMetrics, fileMetrics, metrics, hotspots);
10337
+ const result = {
10338
+ summary,
10339
+ metrics,
10340
+ hotspots
10341
+ };
10342
+ return formatQualityReport(result, thresholds);
10343
+ }
10344
+ function extractCSharpFunctions(content, file) {
10345
+ const functions = [];
10346
+ const lines = content.split("\n");
10347
+ const methodPattern = /\b(public|private|protected|internal)\s+(?:async\s+)?(?:static\s+)?(?:virtual\s+)?(?:override\s+)?(?:[\w<>,\s\[\]]+)\s+(\w+)\s*\(([^)]*)\)/gm;
10348
+ let match;
10349
+ while ((match = methodPattern.exec(content)) !== null) {
10350
+ const methodName = match[2];
10351
+ const params = match[3];
10352
+ const startLine = getLineNumber2(content, match.index);
10353
+ const endLine = findMethodEnd(lines, startLine - 1);
10354
+ const lineCount = endLine - startLine + 1;
10355
+ const parameterCount = params.trim() ? params.split(",").length : 0;
10356
+ const methodBody = lines.slice(startLine - 1, endLine).join("\n");
10357
+ const cognitiveComplexity = calculateCognitiveComplexity(methodBody);
10358
+ const cyclomaticComplexity = calculateCyclomaticComplexity(methodBody);
10359
+ const maxNestingDepth = calculateNestingDepth(methodBody);
10360
+ functions.push({
10361
+ name: methodName,
10362
+ file,
10363
+ startLine,
10364
+ endLine,
10365
+ lineCount,
10366
+ parameterCount,
10367
+ maxNestingDepth,
10368
+ cognitiveComplexity,
10369
+ cyclomaticComplexity
10370
+ });
10371
+ }
10372
+ return functions;
10373
+ }
10374
+ function extractTypeScriptFunctions(content, file) {
10375
+ const functions = [];
10376
+ const lines = content.split("\n");
10377
+ const processedFunctions = /* @__PURE__ */ new Set();
10378
+ const patterns = [
10379
+ // Arrow functions assigned to const/let
10380
+ /(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w+)?\s*=>/g,
10381
+ // Function declarations
10382
+ /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g,
10383
+ // Class methods (removed ^ anchor for consistency)
10384
+ /\b(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[\w<>,\s\[\]]+)?\s*\{/gm
10385
+ ];
10386
+ for (const pattern of patterns) {
10387
+ pattern.lastIndex = 0;
10388
+ let match;
10389
+ while ((match = pattern.exec(content)) !== null) {
10390
+ const funcName = match[1];
10391
+ const params = match[2] || "";
10392
+ const startLine = getLineNumber2(content, match.index);
10393
+ if (["if", "for", "while", "switch", "catch", "constructor"].includes(funcName)) continue;
10394
+ const funcKey = `${file}:${startLine}:${funcName}`;
10395
+ if (processedFunctions.has(funcKey)) continue;
10396
+ processedFunctions.add(funcKey);
10397
+ const endLine = findMethodEnd(lines, startLine - 1);
10398
+ const lineCount = endLine - startLine + 1;
10399
+ const parameterCount = params.trim() ? params.split(",").length : 0;
10400
+ const methodBody = lines.slice(startLine - 1, endLine).join("\n");
10401
+ const cognitiveComplexity = calculateCognitiveComplexity(methodBody);
10402
+ const cyclomaticComplexity = calculateCyclomaticComplexity(methodBody);
10403
+ const maxNestingDepth = calculateNestingDepth(methodBody);
10404
+ functions.push({
10405
+ name: funcName,
10406
+ file,
10407
+ startLine,
10408
+ endLine,
10409
+ lineCount,
10410
+ parameterCount,
10411
+ maxNestingDepth,
10412
+ cognitiveComplexity,
10413
+ cyclomaticComplexity
10414
+ });
10415
+ }
10416
+ }
10417
+ return functions;
10418
+ }
10419
+ function findMethodEnd(lines, startIndex) {
10420
+ let braceCount = 0;
10421
+ let foundFirstBrace = false;
10422
+ for (let i = startIndex; i < lines.length; i++) {
10423
+ const line = stripStringsAndComments(lines[i]);
10424
+ for (const char of line) {
10425
+ if (char === "{") {
10426
+ braceCount++;
10427
+ foundFirstBrace = true;
10428
+ } else if (char === "}") {
10429
+ braceCount--;
10430
+ if (foundFirstBrace && braceCount === 0) {
10431
+ return i + 1;
10432
+ }
10433
+ }
10434
+ }
10435
+ }
10436
+ return startIndex + 1;
10437
+ }
10438
+ function stripStringsAndComments(line) {
10439
+ let result = line.replace(/\/\/.*$/, "");
10440
+ result = result.replace(/"(?:[^"\\]|\\.)*"/g, '""');
10441
+ result = result.replace(/'(?:[^'\\]|\\.)*'/g, "''");
10442
+ result = result.replace(/`(?:[^`\\]|\\.)*`/g, "``");
10443
+ return result;
10444
+ }
10445
+ function calculateCognitiveComplexity(code) {
10446
+ let complexity = 0;
10447
+ let nestingLevel = 0;
10448
+ const lines = code.split("\n");
10449
+ for (const line of lines) {
10450
+ const trimmed = line.trim();
10451
+ const controlFlowPatterns = [
10452
+ /\bif\s*\(/g,
10453
+ /\belse\s+if\s*\(/g,
10454
+ /\bfor\s*\(/g,
10455
+ /\bforeach\s*\(/g,
10456
+ /\bwhile\s*\(/g,
10457
+ /\bdo\s*\{/g,
10458
+ /\bswitch\s*\(/g,
10459
+ /\bcatch\s*\(/g
10460
+ ];
10461
+ for (const pattern of controlFlowPatterns) {
10462
+ const matches = trimmed.match(pattern) || [];
10463
+ complexity += matches.length * (1 + nestingLevel);
10464
+ }
10465
+ const logicalPatterns = [/&&/g, /\|\|/g, /\?\?/g, /\?\./g];
10466
+ for (const pattern of logicalPatterns) {
10467
+ const matches = trimmed.match(pattern) || [];
10468
+ complexity += matches.length;
10469
+ }
10470
+ const ternaryMatches = trimmed.match(/\?[^?:]+:/g) || [];
10471
+ complexity += ternaryMatches.length;
10472
+ const openBraces = (trimmed.match(/\{/g) || []).length;
10473
+ const closeBraces = (trimmed.match(/\}/g) || []).length;
10474
+ nestingLevel += openBraces - closeBraces;
10475
+ nestingLevel = Math.max(0, nestingLevel);
10476
+ }
10477
+ return complexity;
10478
+ }
10479
+ function calculateCyclomaticComplexity(code) {
10480
+ let complexity = 1;
10481
+ const patterns = [
10482
+ /\bif\b/g,
10483
+ /\belse\s+if\b/g,
10484
+ /\bwhile\b/g,
10485
+ /\bfor\b/g,
10486
+ /\bforeach\b/g,
10487
+ /\bcase\b/g,
10488
+ /\bcatch\b/g,
10489
+ /\?\?/g,
10490
+ /\?\./g,
10491
+ /&&/g,
10492
+ /\|\|/g
10493
+ ];
10494
+ for (const pattern of patterns) {
10495
+ const matches = code.match(pattern) || [];
10496
+ complexity += matches.length;
10497
+ }
10498
+ return complexity;
10499
+ }
10500
+ function calculateNestingDepth(code) {
10501
+ let maxDepth = 0;
10502
+ let currentDepth = 0;
10503
+ for (const char of code) {
10504
+ if (char === "{") {
10505
+ currentDepth++;
10506
+ maxDepth = Math.max(maxDepth, currentDepth);
10507
+ } else if (char === "}") {
10508
+ currentDepth = Math.max(0, currentDepth - 1);
10509
+ }
10510
+ }
10511
+ return maxDepth;
10512
+ }
10513
+ function calculateMetrics(functions, fileMetrics, thresholds) {
10514
+ const createStat = (values, threshold) => {
10515
+ const filtered = values.filter((v) => v > 0);
10516
+ const avg = filtered.length > 0 ? filtered.reduce((a, b) => a + b, 0) / filtered.length : 0;
10517
+ const max = filtered.length > 0 ? Math.max(...filtered) : 0;
10518
+ const violations = filtered.filter((v) => v > threshold).length;
10519
+ return {
10520
+ average: Math.round(avg * 10) / 10,
10521
+ max,
10522
+ threshold,
10523
+ violations
10524
+ };
10525
+ };
10526
+ const fileSizes = Array.from(fileMetrics.values()).map((f) => f.lineCount);
10527
+ return {
10528
+ cognitiveComplexity: createStat(
10529
+ functions.map((f) => f.cognitiveComplexity),
10530
+ thresholds.cognitiveComplexity
10531
+ ),
10532
+ cyclomaticComplexity: createStat(
10533
+ functions.map((f) => f.cyclomaticComplexity),
10534
+ thresholds.cyclomaticComplexity
10535
+ ),
10536
+ functionSize: createStat(
10537
+ functions.map((f) => f.lineCount),
10538
+ thresholds.functionSize
10539
+ ),
10540
+ nestingDepth: createStat(
10541
+ functions.map((f) => f.maxNestingDepth),
10542
+ thresholds.nestingDepth
10543
+ ),
10544
+ fileSize: createStat(fileSizes, thresholds.fileSize)
10545
+ };
10546
+ }
10547
+ function identifyHotspots(functions, fileMetrics, thresholds) {
10548
+ const hotspots = [];
10549
+ for (const func of functions) {
10550
+ const issues = [];
10551
+ if (func.cognitiveComplexity > thresholds.cognitiveComplexity) {
10552
+ issues.push(`Cognitive complexity: ${func.cognitiveComplexity} (threshold: ${thresholds.cognitiveComplexity})`);
10553
+ }
10554
+ if (func.cyclomaticComplexity > thresholds.cyclomaticComplexity) {
10555
+ issues.push(`Cyclomatic complexity: ${func.cyclomaticComplexity} (threshold: ${thresholds.cyclomaticComplexity})`);
10556
+ }
10557
+ if (func.lineCount > thresholds.functionSize) {
10558
+ issues.push(`Lines: ${func.lineCount} (threshold: ${thresholds.functionSize})`);
10559
+ }
10560
+ if (func.maxNestingDepth > thresholds.nestingDepth) {
10561
+ issues.push(`Nesting depth: ${func.maxNestingDepth} (threshold: ${thresholds.nestingDepth})`);
10562
+ }
10563
+ if (issues.length > 0) {
10564
+ const severity = issues.length >= 3 ? "high" : issues.length === 2 ? "medium" : "low";
10565
+ hotspots.push({
10566
+ file: func.file,
10567
+ function: func.name,
10568
+ issues,
10569
+ severity,
10570
+ metrics: {
10571
+ cognitiveComplexity: func.cognitiveComplexity,
10572
+ cyclomaticComplexity: func.cyclomaticComplexity,
10573
+ lineCount: func.lineCount,
10574
+ nestingDepth: func.maxNestingDepth
10575
+ }
10576
+ });
10577
+ }
10578
+ }
10579
+ for (const [file, metrics] of fileMetrics) {
10580
+ if (metrics.lineCount > thresholds.fileSize) {
10581
+ hotspots.push({
10582
+ file,
10583
+ issues: [`File size: ${metrics.lineCount} lines (threshold: ${thresholds.fileSize})`],
10584
+ severity: "medium",
10585
+ metrics: {
10586
+ lineCount: metrics.lineCount
10587
+ }
10588
+ });
10589
+ }
10590
+ }
10591
+ const severityOrder = { high: 0, medium: 1, low: 2 };
10592
+ hotspots.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
10593
+ return hotspots.slice(0, 20);
10594
+ }
10595
+ function calculateSummary(functions, fileMetrics, metrics, hotspots) {
10596
+ const totalViolations = metrics.cognitiveComplexity.violations + metrics.cyclomaticComplexity.violations + metrics.functionSize.violations + metrics.nestingDepth.violations + metrics.fileSize.violations;
10597
+ const totalFunctions = functions.length;
10598
+ const totalFiles = fileMetrics.size;
10599
+ const violationRate = totalFunctions > 0 ? totalViolations / totalFunctions : 0;
10600
+ const score = Math.max(0, Math.round(100 - violationRate * 100));
10601
+ let grade;
10602
+ if (score >= 90) grade = "A";
10603
+ else if (score >= 80) grade = "B";
10604
+ else if (score >= 70) grade = "C";
10605
+ else if (score >= 60) grade = "D";
10606
+ else grade = "F";
10607
+ return {
10608
+ score,
10609
+ grade,
10610
+ filesAnalyzed: totalFiles,
10611
+ functionsAnalyzed: totalFunctions,
10612
+ issuesFound: hotspots.length
10613
+ };
10614
+ }
10615
+ function isExcludedPath(filePath) {
10616
+ const exclusions = [
10617
+ /[/\\]bin[/\\]/,
10618
+ /[/\\]obj[/\\]/,
10619
+ /[/\\]node_modules[/\\]/,
10620
+ /[/\\]Migrations[/\\]/,
10621
+ /\.test\./,
10622
+ /\.spec\./,
10623
+ /Tests[/\\]/,
10624
+ /\.d\.ts$/,
10625
+ /\.min\./
10626
+ ];
10627
+ return exclusions.some((pattern) => pattern.test(filePath));
10628
+ }
10629
+ function getLineNumber2(content, index) {
10630
+ const normalizedContent = content.substring(0, index).replace(/\r\n/g, "\n");
10631
+ return normalizedContent.split("\n").length;
10632
+ }
10633
+ function formatQualityReport(result, thresholds) {
10634
+ const lines = [];
10635
+ lines.push("# Code Quality Report");
10636
+ lines.push("");
10637
+ lines.push("## Summary");
10638
+ lines.push(`- **Score**: ${result.summary.score}/100 (Grade: ${result.summary.grade})`);
10639
+ lines.push(`- **Files analyzed**: ${result.summary.filesAnalyzed}`);
10640
+ lines.push(`- **Functions analyzed**: ${result.summary.functionsAnalyzed}`);
10641
+ lines.push(`- **Issues found**: ${result.summary.issuesFound}`);
10642
+ lines.push("");
10643
+ lines.push("## Metrics Overview");
10644
+ lines.push("");
10645
+ lines.push("| Metric | Average | Max | Threshold | Status |");
10646
+ lines.push("|--------|---------|-----|-----------|--------|");
10647
+ const formatMetricRow = (name, stat2) => {
10648
+ const status = stat2.violations > 0 ? `${stat2.violations} violations` : "\u2705 OK";
10649
+ return `| ${name} | ${stat2.average} | ${stat2.max} | ${stat2.threshold} | ${status} |`;
10650
+ };
10651
+ lines.push(formatMetricRow("Cognitive Complexity", result.metrics.cognitiveComplexity));
10652
+ lines.push(formatMetricRow("Cyclomatic Complexity", result.metrics.cyclomaticComplexity));
10653
+ lines.push(formatMetricRow("Function Size", result.metrics.functionSize));
10654
+ lines.push(formatMetricRow("Nesting Depth", result.metrics.nestingDepth));
10655
+ lines.push(formatMetricRow("File Size", result.metrics.fileSize));
10656
+ lines.push("");
10657
+ if (result.hotspots.length > 0) {
10658
+ lines.push("## Hotspots (Needs Attention)");
10659
+ lines.push("");
10660
+ for (const hotspot of result.hotspots) {
10661
+ const severityEmoji = hotspot.severity === "high" ? "\u{1F534}" : hotspot.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10662
+ const location = hotspot.function ? `\`${hotspot.function}\` (${hotspot.file})` : `\`${hotspot.file}\``;
10663
+ lines.push(`### ${severityEmoji} ${hotspot.severity.toUpperCase()}: ${location}`);
10664
+ for (const issue of hotspot.issues) {
10665
+ lines.push(`- ${issue}`);
10666
+ }
10667
+ if (hotspot.function) {
10668
+ if (hotspot.metrics.cognitiveComplexity && hotspot.metrics.cognitiveComplexity > thresholds.cognitiveComplexity) {
10669
+ lines.push(`- **Recommendation**: Extract logic into smaller, focused methods`);
10670
+ } else if (hotspot.metrics.lineCount && hotspot.metrics.lineCount > thresholds.functionSize) {
10671
+ lines.push(`- **Recommendation**: Split into multiple functions with single responsibilities`);
10672
+ }
10673
+ } else {
10674
+ lines.push(`- **Recommendation**: Consider splitting this file into smaller modules`);
10675
+ }
10676
+ lines.push("");
10677
+ }
10678
+ } else {
10679
+ lines.push("No hotspots found. Code quality is within acceptable thresholds.");
10680
+ }
10681
+ return lines.join("\n");
10682
+ }
10683
+
9738
10684
  // src/resources/conventions.ts
9739
10685
  var conventionsResourceTemplate = {
9740
10686
  uri: "smartstack://conventions",
@@ -10870,7 +11816,7 @@ Run specific or all checks:
10870
11816
  }
10871
11817
 
10872
11818
  // src/resources/project-info.ts
10873
- import path20 from "path";
11819
+ import path22 from "path";
10874
11820
  var projectInfoResourceTemplate = {
10875
11821
  uri: "smartstack://project",
10876
11822
  name: "SmartStack Project Info",
@@ -10907,16 +11853,16 @@ async function getProjectInfoResource(config) {
10907
11853
  lines.push("```");
10908
11854
  lines.push(`${projectInfo.name}/`);
10909
11855
  if (structure.domain) {
10910
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.domain)}/ # Domain layer (entities)`);
11856
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.domain)}/ # Domain layer (entities)`);
10911
11857
  }
10912
11858
  if (structure.application) {
10913
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.application)}/ # Application layer (services)`);
11859
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.application)}/ # Application layer (services)`);
10914
11860
  }
10915
11861
  if (structure.infrastructure) {
10916
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
11862
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
10917
11863
  }
10918
11864
  if (structure.api) {
10919
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.api)}/ # API layer (controllers)`);
11865
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.api)}/ # API layer (controllers)`);
10920
11866
  }
10921
11867
  if (structure.web) {
10922
11868
  lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
@@ -10929,8 +11875,8 @@ async function getProjectInfoResource(config) {
10929
11875
  lines.push("| Project | Path |");
10930
11876
  lines.push("|---------|------|");
10931
11877
  for (const csproj of projectInfo.csprojFiles) {
10932
- const name = path20.basename(csproj, ".csproj");
10933
- const relativePath = path20.relative(projectPath, csproj);
11878
+ const name = path22.basename(csproj, ".csproj");
11879
+ const relativePath = path22.relative(projectPath, csproj);
10934
11880
  lines.push(`| ${name} | \`${relativePath}\` |`);
10935
11881
  }
10936
11882
  lines.push("");
@@ -10940,10 +11886,10 @@ async function getProjectInfoResource(config) {
10940
11886
  cwd: structure.migrations,
10941
11887
  ignore: ["*.Designer.cs"]
10942
11888
  });
10943
- const migrations = migrationFiles.map((f) => path20.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
11889
+ const migrations = migrationFiles.map((f) => path22.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
10944
11890
  lines.push("## EF Core Migrations");
10945
11891
  lines.push("");
10946
- lines.push(`**Location**: \`${path20.relative(projectPath, structure.migrations)}\``);
11892
+ lines.push(`**Location**: \`${path22.relative(projectPath, structure.migrations)}\``);
10947
11893
  lines.push(`**Total Migrations**: ${migrations.length}`);
10948
11894
  lines.push("");
10949
11895
  if (migrations.length > 0) {
@@ -10978,11 +11924,11 @@ async function getProjectInfoResource(config) {
10978
11924
  lines.push("dotnet build");
10979
11925
  lines.push("");
10980
11926
  lines.push("# Run API");
10981
- lines.push(`cd ${structure.api ? path20.relative(projectPath, structure.api) : "src/Api"}`);
11927
+ lines.push(`cd ${structure.api ? path22.relative(projectPath, structure.api) : "src/Api"}`);
10982
11928
  lines.push("dotnet run");
10983
11929
  lines.push("");
10984
11930
  lines.push("# Run frontend");
10985
- lines.push(`cd ${structure.web ? path20.relative(projectPath, structure.web) : "web"}`);
11931
+ lines.push(`cd ${structure.web ? path22.relative(projectPath, structure.web) : "web"}`);
10986
11932
  lines.push("npm run dev");
10987
11933
  lines.push("");
10988
11934
  lines.push("# Create migration");
@@ -11005,7 +11951,7 @@ async function getProjectInfoResource(config) {
11005
11951
  }
11006
11952
 
11007
11953
  // src/resources/api-endpoints.ts
11008
- import path21 from "path";
11954
+ import path23 from "path";
11009
11955
  var apiEndpointsResourceTemplate = {
11010
11956
  uri: "smartstack://api/",
11011
11957
  name: "SmartStack API Endpoints",
@@ -11030,7 +11976,7 @@ async function getApiEndpointsResource(config, endpointFilter) {
11030
11976
  }
11031
11977
  async function parseController(filePath, _rootPath) {
11032
11978
  const content = await readText(filePath);
11033
- const fileName = path21.basename(filePath, ".cs");
11979
+ const fileName = path23.basename(filePath, ".cs");
11034
11980
  const controllerName = fileName.replace("Controller", "");
11035
11981
  const endpoints = [];
11036
11982
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
@@ -11177,7 +12123,7 @@ function getMethodEmoji(method) {
11177
12123
  }
11178
12124
 
11179
12125
  // src/resources/db-schema.ts
11180
- import path22 from "path";
12126
+ import path24 from "path";
11181
12127
  var dbSchemaResourceTemplate = {
11182
12128
  uri: "smartstack://schema/",
11183
12129
  name: "SmartStack Database Schema",
@@ -11267,7 +12213,7 @@ async function parseEntity(filePath, rootPath, _config) {
11267
12213
  tableName,
11268
12214
  properties,
11269
12215
  relationships,
11270
- file: path22.relative(rootPath, filePath)
12216
+ file: path24.relative(rootPath, filePath)
11271
12217
  };
11272
12218
  }
11273
12219
  async function enrichFromConfigurations(entities, infrastructurePath, _config) {
@@ -11413,7 +12359,7 @@ function formatSchema(entities, filter, _config) {
11413
12359
  }
11414
12360
 
11415
12361
  // src/resources/entities.ts
11416
- import path23 from "path";
12362
+ import path25 from "path";
11417
12363
  var entitiesResourceTemplate = {
11418
12364
  uri: "smartstack://entities/",
11419
12365
  name: "SmartStack Entities",
@@ -11473,7 +12419,7 @@ async function parseEntitySummary(filePath, rootPath, config) {
11473
12419
  hasSoftDelete,
11474
12420
  hasRowVersion,
11475
12421
  file: filePath,
11476
- relativePath: path23.relative(rootPath, filePath)
12422
+ relativePath: path25.relative(rootPath, filePath)
11477
12423
  };
11478
12424
  }
11479
12425
  function inferTableInfo(entityName, config) {
@@ -11673,7 +12619,10 @@ async function createServer() {
11673
12619
  validateFrontendRoutesTool,
11674
12620
  // Frontend Extension Tools
11675
12621
  scaffoldFrontendExtensionTool,
11676
- analyzeExtensionPointsTool
12622
+ analyzeExtensionPointsTool,
12623
+ // Security & Code Quality Tools
12624
+ validateSecurityTool,
12625
+ analyzeCodeQualityTool
11677
12626
  ]
11678
12627
  };
11679
12628
  });
@@ -11732,6 +12681,13 @@ async function createServer() {
11732
12681
  case "analyze_extension_points":
11733
12682
  result = await handleAnalyzeExtensionPoints(args ?? {}, config);
11734
12683
  break;
12684
+ // Security & Code Quality Tools
12685
+ case "validate_security":
12686
+ result = await handleValidateSecurity(args ?? {}, config);
12687
+ break;
12688
+ case "analyze_code_quality":
12689
+ result = await handleAnalyzeCodeQuality(args ?? {}, config);
12690
+ break;
11735
12691
  default:
11736
12692
  throw new Error(`Unknown tool: ${name}`);
11737
12693
  }