@atlashub/smartstack-mcp 1.14.0 → 1.16.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")'),
@@ -7804,6 +7837,47 @@ function generateNavRouteRegistry(routes) {
7804
7837
  lines.push(" return Object.values(ROUTES).filter(r => r.navRoute.startsWith(`${context}.`));");
7805
7838
  lines.push("}");
7806
7839
  lines.push("");
7840
+ lines.push("/**");
7841
+ lines.push(" * Get web route with optional tenant slug prefix");
7842
+ lines.push(" *");
7843
+ lines.push(" * @param navRoute - NavRoute path (e.g., 'platform.administration.users')");
7844
+ lines.push(" * @param options - Configuration for URL building");
7845
+ lines.push(" * @returns Web route path, optionally prefixed with /t/{slug}");
7846
+ lines.push(" *");
7847
+ lines.push(" * @example");
7848
+ lines.push(" * // Single-tenant mode or no tenant");
7849
+ lines.push(" * getWebRoute('platform.administration.users', { isSingleTenant: true })");
7850
+ lines.push(" * // Returns: '/platform/administration/users'");
7851
+ lines.push(" *");
7852
+ lines.push(" * @example");
7853
+ lines.push(" * // Multi-tenant mode with slug");
7854
+ lines.push(" * getWebRoute('platform.administration.users', { slug: 'acme', isSingleTenant: false })");
7855
+ lines.push(" * // Returns: '/t/acme/platform/administration/users'");
7856
+ lines.push(" */");
7857
+ lines.push("export function getWebRoute(");
7858
+ lines.push(" navRoute: string,");
7859
+ lines.push(" options: {");
7860
+ lines.push(" slug?: string | null;");
7861
+ lines.push(" isSingleTenant: boolean;");
7862
+ lines.push(" isAdminMode?: boolean;");
7863
+ lines.push(" } = { isSingleTenant: true }");
7864
+ lines.push("): string {");
7865
+ lines.push(" const route = ROUTES[navRoute];");
7866
+ lines.push(" if (!route) {");
7867
+ lines.push(" throw new Error(`Route not found: ${navRoute}`);");
7868
+ lines.push(" }");
7869
+ lines.push("");
7870
+ lines.push(" const webPath = route.web;");
7871
+ lines.push("");
7872
+ lines.push(" // No prefix needed in single-tenant mode, admin mode, or without slug");
7873
+ lines.push(" if (options.isSingleTenant || options.isAdminMode || !options.slug) {");
7874
+ lines.push(" return webPath;");
7875
+ lines.push(" }");
7876
+ lines.push("");
7877
+ lines.push(" // Add tenant slug prefix");
7878
+ lines.push(" return `/t/${options.slug}${webPath}`;");
7879
+ lines.push("}");
7880
+ lines.push("");
7807
7881
  return lines.join("\n");
7808
7882
  }
7809
7883
  function generateRouterConfig(routes, includeGuards) {
@@ -9735,6 +9809,919 @@ function generateRecommendations3(analyzed) {
9735
9809
  return recommendations;
9736
9810
  }
9737
9811
 
9812
+ // src/tools/validate-security.ts
9813
+ import path20 from "path";
9814
+ var validateSecurityTool = {
9815
+ name: "validate_security",
9816
+ description: "Validate SmartStack security patterns: multi-tenant isolation, authorization, input validation, secrets exposure, injection vulnerabilities, and OWASP Top 10 compliance.",
9817
+ inputSchema: {
9818
+ type: "object",
9819
+ properties: {
9820
+ path: {
9821
+ type: "string",
9822
+ description: "Project path to validate (default: SmartStack.app path from config)"
9823
+ },
9824
+ checks: {
9825
+ type: "array",
9826
+ items: {
9827
+ type: "string",
9828
+ enum: [
9829
+ "hardcoded-secrets",
9830
+ "sql-injection",
9831
+ "tenant-isolation",
9832
+ "authorization",
9833
+ "dangerous-functions",
9834
+ "input-validation",
9835
+ "xss",
9836
+ "csrf",
9837
+ "logging-sensitive",
9838
+ "all"
9839
+ ]
9840
+ },
9841
+ description: "Security checks to run",
9842
+ default: ["all"]
9843
+ },
9844
+ severity: {
9845
+ type: "string",
9846
+ enum: ["blocking", "all"],
9847
+ description: "Filter results by severity"
9848
+ }
9849
+ }
9850
+ }
9851
+ };
9852
+ async function handleValidateSecurity(args, config) {
9853
+ const input = ValidateSecurityInputSchema.parse(args);
9854
+ const projectPath = input.path || config.smartstack.projectPath;
9855
+ const checksToRun = input.checks.includes("all") ? [
9856
+ "hardcoded-secrets",
9857
+ "sql-injection",
9858
+ "tenant-isolation",
9859
+ "authorization",
9860
+ "dangerous-functions",
9861
+ "input-validation"
9862
+ ] : input.checks;
9863
+ logger.info("Validating security", { projectPath, checks: checksToRun });
9864
+ const result = {
9865
+ valid: true,
9866
+ summary: "",
9867
+ findings: [],
9868
+ stats: {
9869
+ blocking: 0,
9870
+ critical: 0,
9871
+ warning: 0,
9872
+ filesScanned: 0
9873
+ }
9874
+ };
9875
+ const structure = await findSmartStackStructure(projectPath);
9876
+ if (checksToRun.includes("hardcoded-secrets")) {
9877
+ await checkHardcodedSecrets(structure, result);
9878
+ }
9879
+ if (checksToRun.includes("sql-injection")) {
9880
+ await checkSqlInjection(structure, result);
9881
+ }
9882
+ if (checksToRun.includes("tenant-isolation")) {
9883
+ await checkTenantIsolation(structure, result);
9884
+ }
9885
+ if (checksToRun.includes("authorization")) {
9886
+ await checkAuthorization(structure, result);
9887
+ }
9888
+ if (checksToRun.includes("dangerous-functions")) {
9889
+ await checkDangerousFunctions(structure, result);
9890
+ }
9891
+ if (checksToRun.includes("input-validation")) {
9892
+ await checkInputValidation(structure, result);
9893
+ }
9894
+ result.stats.blocking = result.findings.filter((f) => f.severity === "blocking").length;
9895
+ result.stats.critical = result.findings.filter((f) => f.severity === "critical").length;
9896
+ result.stats.warning = result.findings.filter((f) => f.severity === "warning").length;
9897
+ if (input.severity === "blocking") {
9898
+ result.findings = result.findings.filter((f) => f.severity === "blocking");
9899
+ }
9900
+ result.valid = result.stats.blocking === 0;
9901
+ result.summary = result.valid ? `Security validation passed. ${result.stats.warning} warning(s) found.` : `Security validation FAILED. ${result.stats.blocking} blocking issue(s) found.`;
9902
+ return formatSecurityReport(result);
9903
+ }
9904
+ async function checkHardcodedSecrets(structure, result) {
9905
+ const secretPatterns = [
9906
+ // Credentials
9907
+ { pattern: /(?:password|passwd|pwd)\s*[=:]\s*["'][^"']{4,}["']/gi, name: "password" },
9908
+ { pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*["'][^"']{8,}["']/gi, name: "API key" },
9909
+ { pattern: /(?:secret|token)\s*[=:]\s*["'][^"']{8,}["']/gi, name: "secret/token" },
9910
+ // Connection strings with embedded passwords
9911
+ { pattern: /Server=.+;.*Password=[^;]+/gi, name: "connection string with password" },
9912
+ { pattern: /Data Source=.+;.*Password=[^;]+/gi, name: "connection string with password" },
9913
+ // Private keys
9914
+ { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g, name: "private key" }
9915
+ ];
9916
+ const filesToScan = await getFilesToScan(structure, ["**/*.cs", "**/*.ts", "**/*.tsx", "**/*.json"]);
9917
+ result.stats.filesScanned += filesToScan.length;
9918
+ for (const file of filesToScan) {
9919
+ if (isExcludedFile(file)) continue;
9920
+ const content = await readText(file);
9921
+ const lines = content.split("\n");
9922
+ for (const { pattern, name } of secretPatterns) {
9923
+ pattern.lastIndex = 0;
9924
+ let match;
9925
+ while ((match = pattern.exec(content)) !== null) {
9926
+ const lineNumber = getLineNumber(content, match.index);
9927
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
9928
+ if (isPlaceholderValue(lineContent)) continue;
9929
+ result.findings.push({
9930
+ severity: "blocking",
9931
+ category: "hardcoded-secrets",
9932
+ message: `Hardcoded ${name} detected`,
9933
+ file: path20.relative(structure.root, file),
9934
+ line: lineNumber,
9935
+ code: truncateCode(lineContent),
9936
+ suggestion: `Move ${name} to configuration or environment variables`,
9937
+ cweId: "CWE-798"
9938
+ });
9939
+ }
9940
+ }
9941
+ }
9942
+ }
9943
+ async function checkSqlInjection(structure, result) {
9944
+ const sqlInjectionPatterns = [
9945
+ // Raw SQL with string concatenation (the most dangerous pattern)
9946
+ { pattern: /\.(?:FromSqlRaw|ExecuteSqlRaw)\s*\([^)]*\s*\+\s*/g, name: "concatenated SQL in FromSqlRaw/ExecuteSqlRaw" },
9947
+ // ADO.NET without parameters
9948
+ { pattern: /new SqlCommand\s*\([^)]*\+/g, name: "concatenated SQL in SqlCommand" },
9949
+ { pattern: /CommandText\s*=\s*[^;]*\+/g, name: "concatenated SQL in CommandText" }
9950
+ // Note: FromSqlInterpolated with $"" is safe and not flagged
9951
+ ];
9952
+ const csFiles = await getFilesToScan(structure, ["**/*.cs"]);
9953
+ result.stats.filesScanned += csFiles.length;
9954
+ for (const file of csFiles) {
9955
+ if (isExcludedFile(file)) continue;
9956
+ const content = await readText(file);
9957
+ const lines = content.split("\n");
9958
+ for (const { pattern, name } of sqlInjectionPatterns) {
9959
+ pattern.lastIndex = 0;
9960
+ let match;
9961
+ while ((match = pattern.exec(content)) !== null) {
9962
+ const lineNumber = getLineNumber(content, match.index);
9963
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
9964
+ result.findings.push({
9965
+ severity: "blocking",
9966
+ category: "sql-injection",
9967
+ message: `Potential SQL injection: ${name}`,
9968
+ file: path20.relative(structure.root, file),
9969
+ line: lineNumber,
9970
+ code: truncateCode(lineContent),
9971
+ suggestion: 'Use parameterized queries: FromSqlRaw("SELECT * FROM x WHERE id = {0}", id) or FromSqlInterpolated',
9972
+ cweId: "CWE-89"
9973
+ });
9974
+ }
9975
+ }
9976
+ }
9977
+ }
9978
+ async function checkTenantIsolation(structure, result) {
9979
+ if (!structure.domain) {
9980
+ result.findings.push({
9981
+ severity: "warning",
9982
+ category: "tenant-isolation",
9983
+ message: "Domain project not found, skipping tenant isolation validation",
9984
+ file: "",
9985
+ suggestion: "Ensure project structure follows SmartStack conventions"
9986
+ });
9987
+ return;
9988
+ }
9989
+ const entityFiles = await findFiles("**/Entities/**/*.cs", { cwd: structure.domain });
9990
+ const tenantEntities = [];
9991
+ for (const file of entityFiles) {
9992
+ const content = await readText(file);
9993
+ if (content.includes("ITenantEntity") || content.includes(": TenantEntity")) {
9994
+ const entityMatch = content.match(/class\s+(\w+)/);
9995
+ if (entityMatch) {
9996
+ tenantEntities.push(entityMatch[1]);
9997
+ }
9998
+ }
9999
+ const createMethodMatches = content.matchAll(/public\s+static\s+\w+\s+Create\s*\(([^)]*)\)/g);
10000
+ for (const match of createMethodMatches) {
10001
+ const params = match[1];
10002
+ if ((content.includes("ITenantEntity") || content.includes(": TenantEntity")) && !params.includes("tenantId") && !params.includes("TenantId")) {
10003
+ const lineNumber = getLineNumber(content, match.index);
10004
+ result.findings.push({
10005
+ severity: "blocking",
10006
+ category: "tenant-isolation",
10007
+ message: "Create method missing tenantId parameter for tenant entity",
10008
+ file: path20.relative(structure.root, file),
10009
+ line: lineNumber,
10010
+ code: truncateCode(match[0]),
10011
+ suggestion: "Add Guid tenantId as first parameter: Create(Guid tenantId, ...)",
10012
+ cweId: "CWE-639"
10013
+ });
10014
+ }
10015
+ }
10016
+ }
10017
+ if (structure.application) {
10018
+ const serviceFiles = await findFiles("**/*Service.cs", { cwd: structure.application });
10019
+ for (const file of serviceFiles) {
10020
+ const content = await readText(file);
10021
+ const directAccessPattern = /_context\.\w+\.(?:First|Single|Find|ToList)\s*\(/g;
10022
+ let match;
10023
+ while ((match = directAccessPattern.exec(content)) !== null) {
10024
+ const surroundingCode = content.substring(Math.max(0, match.index - 100), match.index + 100);
10025
+ if (!surroundingCode.includes(".Where(") && !surroundingCode.includes("TenantId")) {
10026
+ const lineNumber = getLineNumber(content, match.index);
10027
+ result.findings.push({
10028
+ severity: "critical",
10029
+ category: "tenant-isolation",
10030
+ message: "Direct DbContext access without tenant filtering",
10031
+ file: path20.relative(structure.root, file),
10032
+ line: lineNumber,
10033
+ code: truncateCode(match[0]),
10034
+ suggestion: "Use repository with global tenant filter or add explicit TenantId filter",
10035
+ cweId: "CWE-639"
10036
+ });
10037
+ }
10038
+ }
10039
+ }
10040
+ }
10041
+ }
10042
+ async function checkAuthorization(structure, result) {
10043
+ const apiPath = structure.api || structure.apiCore;
10044
+ if (!apiPath) {
10045
+ result.findings.push({
10046
+ severity: "warning",
10047
+ category: "authorization",
10048
+ message: "API project not found, skipping authorization validation",
10049
+ file: "",
10050
+ suggestion: "Ensure project structure follows SmartStack conventions"
10051
+ });
10052
+ return;
10053
+ }
10054
+ const controllerFiles = await findFiles("**/Controllers/**/*.cs", { cwd: apiPath });
10055
+ result.stats.filesScanned += controllerFiles.length;
10056
+ for (const file of controllerFiles) {
10057
+ const content = await readText(file);
10058
+ if (content.includes("[ApiController]")) {
10059
+ const hasClassLevelAuth = content.includes("[Authorize]") || content.includes("[NavRoute(") || content.includes("[AllowAnonymous]");
10060
+ if (!hasClassLevelAuth) {
10061
+ const controllerMatch = content.match(/class\s+(\w+Controller)/);
10062
+ const controllerName = controllerMatch ? controllerMatch[1] : "Unknown";
10063
+ const lineNumber = controllerMatch ? getLineNumber(content, controllerMatch.index) : 1;
10064
+ result.findings.push({
10065
+ severity: "blocking",
10066
+ category: "authorization",
10067
+ message: `Controller ${controllerName} missing authorization attribute`,
10068
+ file: path20.relative(structure.root, file),
10069
+ line: lineNumber,
10070
+ suggestion: 'Add [NavRoute("context.application.module")] or [Authorize] attribute to the controller class',
10071
+ cweId: "CWE-862"
10072
+ });
10073
+ }
10074
+ }
10075
+ }
10076
+ }
10077
+ async function checkDangerousFunctions(structure, result) {
10078
+ const csharpPatterns = [
10079
+ { pattern: /Process\.Start\s*\([^)]*(?:user|input|param|request)/gi, name: "Process.Start with user input" },
10080
+ { pattern: /Assembly\.Load(?:From|File)?\s*\(/g, name: "Dynamic assembly loading" },
10081
+ { pattern: /Type\.GetType\s*\([^)]*(?:user|input|param|request)/gi, name: "Type.GetType with user input" },
10082
+ { pattern: /Activator\.CreateInstance\s*\([^)]*(?:user|input|param|request)/gi, name: "Activator.CreateInstance with user input" }
10083
+ ];
10084
+ const typescriptPatterns = [
10085
+ { pattern: /\beval\s*\(/g, name: "eval() usage" },
10086
+ { pattern: /new\s+Function\s*\(/g, name: "new Function() usage" },
10087
+ { pattern: /child_process\.exec\s*\(/g, name: "child_process.exec usage" },
10088
+ { pattern: /\.innerHTML\s*=/g, name: "innerHTML assignment" },
10089
+ { pattern: /dangerouslySetInnerHTML/g, name: "dangerouslySetInnerHTML usage" }
10090
+ ];
10091
+ const csFiles = await getFilesToScan(structure, ["**/*.cs"]);
10092
+ for (const file of csFiles) {
10093
+ if (isExcludedFile(file)) continue;
10094
+ const content = await readText(file);
10095
+ await checkPatterns(file, content, csharpPatterns, structure, result);
10096
+ }
10097
+ const tsFiles = await getFilesToScan(structure, ["**/*.ts", "**/*.tsx"]);
10098
+ for (const file of tsFiles) {
10099
+ if (isExcludedFile(file)) continue;
10100
+ const content = await readText(file);
10101
+ await checkPatterns(file, content, typescriptPatterns, structure, result);
10102
+ }
10103
+ }
10104
+ async function checkPatterns(file, content, patterns, structure, result) {
10105
+ const lines = content.split("\n");
10106
+ for (const { pattern, name } of patterns) {
10107
+ pattern.lastIndex = 0;
10108
+ let match;
10109
+ while ((match = pattern.exec(content)) !== null) {
10110
+ const lineNumber = getLineNumber(content, match.index);
10111
+ const lineContent = lines[lineNumber - 1]?.trim() || "";
10112
+ result.findings.push({
10113
+ severity: "critical",
10114
+ category: "dangerous-functions",
10115
+ message: `Dangerous function: ${name}`,
10116
+ file: path20.relative(structure.root, file),
10117
+ line: lineNumber,
10118
+ code: truncateCode(lineContent),
10119
+ suggestion: "Avoid using dangerous functions with user-controlled input. Use safe alternatives.",
10120
+ cweId: "CWE-78"
10121
+ });
10122
+ }
10123
+ }
10124
+ }
10125
+ async function checkInputValidation(structure, result) {
10126
+ if (!structure.application) {
10127
+ return;
10128
+ }
10129
+ const dtoFiles = await findFiles("**/*Dto.cs", { cwd: structure.application });
10130
+ const dtos = [];
10131
+ for (const file of dtoFiles) {
10132
+ const content = await readText(file);
10133
+ const dtoMatch = content.match(/class\s+(\w+Dto)/);
10134
+ if (dtoMatch) {
10135
+ dtos.push(dtoMatch[1]);
10136
+ }
10137
+ }
10138
+ const validatorFiles = await findFiles("**/*Validator.cs", { cwd: structure.application });
10139
+ const validators = [];
10140
+ for (const file of validatorFiles) {
10141
+ const content = await readText(file);
10142
+ const validatorMatch = content.match(/class\s+(\w+Validator)/);
10143
+ if (validatorMatch) {
10144
+ validators.push(validatorMatch[1]);
10145
+ }
10146
+ }
10147
+ for (const dto of dtos) {
10148
+ const expectedValidator = dto.replace(/Dto$/, "DtoValidator");
10149
+ const hasValidator = validators.some((v) => v === expectedValidator || v === `${dto}Validator`);
10150
+ if (!hasValidator) {
10151
+ result.findings.push({
10152
+ severity: "warning",
10153
+ category: "input-validation",
10154
+ message: `DTO ${dto} has no FluentValidation validator`,
10155
+ file: `Application/DTOs/${dto}.cs`,
10156
+ suggestion: `Create ${expectedValidator} class with FluentValidation rules`,
10157
+ cweId: "CWE-20"
10158
+ });
10159
+ }
10160
+ }
10161
+ }
10162
+ async function getFilesToScan(structure, patterns) {
10163
+ const allFiles = [];
10164
+ for (const pattern of patterns) {
10165
+ const files = await findFiles(pattern, { cwd: structure.root });
10166
+ for (const file of files) {
10167
+ try {
10168
+ validatePathSecurity(file, structure.root);
10169
+ allFiles.push(file);
10170
+ } catch {
10171
+ logger.warn("Skipping file outside project root", { file });
10172
+ }
10173
+ }
10174
+ }
10175
+ return [...new Set(allFiles)];
10176
+ }
10177
+ function isExcludedFile(filePath) {
10178
+ const exclusions = [
10179
+ /[/\\]bin[/\\]/,
10180
+ /[/\\]obj[/\\]/,
10181
+ /[/\\]node_modules[/\\]/,
10182
+ /\.example\./,
10183
+ /\.template\./,
10184
+ /\.test\./,
10185
+ /\.spec\./,
10186
+ /Tests[/\\]/,
10187
+ /appsettings\.Development\.json$/,
10188
+ /appsettings\.Template\.json$/
10189
+ ];
10190
+ return exclusions.some((pattern) => pattern.test(filePath));
10191
+ }
10192
+ function isPlaceholderValue(line) {
10193
+ const placeholderPatterns = [
10194
+ /\${[A-Z_][A-Z0-9_]*}/,
10195
+ // ${VAR_NAME} - env var syntax
10196
+ /%[A-Z_][A-Z0-9_]*%/,
10197
+ // %VAR_NAME% - Windows env var
10198
+ /\{\{[A-Za-z_][A-Za-z0-9_]*\}\}/,
10199
+ // {{varName}} - template syntax
10200
+ /["']your[_-]?(?:password|secret|key|token)["']/i,
10201
+ // "your_password" placeholders
10202
+ /["']changeme["']/i,
10203
+ /["']<[A-Z_]+>["']/,
10204
+ // "<REPLACE_ME>" syntax
10205
+ /["']xxx+["']/i,
10206
+ // "xxx" or "XXXX" placeholders
10207
+ /=\s*["']["']/
10208
+ // Empty string assignments
10209
+ ];
10210
+ return placeholderPatterns.some((pattern) => pattern.test(line));
10211
+ }
10212
+ function getLineNumber(content, index) {
10213
+ const normalizedContent = content.substring(0, index).replace(/\r\n/g, "\n");
10214
+ return normalizedContent.split("\n").length;
10215
+ }
10216
+ function truncateCode(code, maxLength = 80) {
10217
+ return code.length > maxLength ? code.substring(0, maxLength) + "..." : code;
10218
+ }
10219
+ function formatSecurityReport(result) {
10220
+ const lines = [];
10221
+ lines.push("# Security Validation Report");
10222
+ lines.push("");
10223
+ lines.push("## Summary");
10224
+ lines.push(`- **Status**: ${result.valid ? "\u2705 PASSED" : "\u274C FAILED"}`);
10225
+ lines.push(`- **Blocking**: ${result.stats.blocking}`);
10226
+ lines.push(`- **Critical**: ${result.stats.critical}`);
10227
+ lines.push(`- **Warnings**: ${result.stats.warning}`);
10228
+ lines.push(`- **Files Scanned**: ${result.stats.filesScanned}`);
10229
+ lines.push("");
10230
+ if (result.findings.length === 0) {
10231
+ lines.push("No security issues found.");
10232
+ return lines.join("\n");
10233
+ }
10234
+ const blocking = result.findings.filter((f) => f.severity === "blocking");
10235
+ const critical = result.findings.filter((f) => f.severity === "critical");
10236
+ const warnings = result.findings.filter((f) => f.severity === "warning");
10237
+ if (blocking.length > 0) {
10238
+ lines.push("## Blocking Issues");
10239
+ lines.push("");
10240
+ for (const finding of blocking) {
10241
+ formatFinding(finding, lines);
10242
+ }
10243
+ }
10244
+ if (critical.length > 0) {
10245
+ lines.push("## Critical Issues");
10246
+ lines.push("");
10247
+ for (const finding of critical) {
10248
+ formatFinding(finding, lines);
10249
+ }
10250
+ }
10251
+ if (warnings.length > 0) {
10252
+ lines.push("## Warnings");
10253
+ lines.push("");
10254
+ for (const finding of warnings) {
10255
+ formatFinding(finding, lines);
10256
+ }
10257
+ }
10258
+ return lines.join("\n");
10259
+ }
10260
+ function formatFinding(finding, lines) {
10261
+ lines.push(`### ${finding.category}: ${finding.message}`);
10262
+ if (finding.file) {
10263
+ lines.push(`- **File**: \`${finding.file}${finding.line ? `:${finding.line}` : ""}\``);
10264
+ }
10265
+ if (finding.code) {
10266
+ lines.push(`- **Code**: \`${finding.code}\``);
10267
+ }
10268
+ if (finding.cweId) {
10269
+ lines.push(`- **CWE**: ${finding.cweId}`);
10270
+ }
10271
+ lines.push(`- **Fix**: ${finding.suggestion}`);
10272
+ lines.push("");
10273
+ }
10274
+
10275
+ // src/tools/analyze-code-quality.ts
10276
+ import path21 from "path";
10277
+ var analyzeCodeQualityTool = {
10278
+ name: "analyze_code_quality",
10279
+ description: "Analyze code quality metrics for SmartStack projects: cognitive complexity, cyclomatic complexity, function size, nesting depth, and maintainability indicators.",
10280
+ inputSchema: {
10281
+ type: "object",
10282
+ properties: {
10283
+ path: {
10284
+ type: "string",
10285
+ description: "Project path to analyze (default: SmartStack.app path from config)"
10286
+ },
10287
+ metrics: {
10288
+ type: "array",
10289
+ items: {
10290
+ type: "string",
10291
+ enum: [
10292
+ "cognitive-complexity",
10293
+ "cyclomatic-complexity",
10294
+ "function-size",
10295
+ "nesting-depth",
10296
+ "parameter-count",
10297
+ "code-duplication",
10298
+ "file-size",
10299
+ "all"
10300
+ ]
10301
+ },
10302
+ description: "Metrics to analyze",
10303
+ default: ["all"]
10304
+ },
10305
+ threshold: {
10306
+ type: "string",
10307
+ enum: ["strict", "normal", "lenient"],
10308
+ description: "Threshold level for violations",
10309
+ default: "normal"
10310
+ },
10311
+ scope: {
10312
+ type: "string",
10313
+ enum: ["changed", "all"],
10314
+ description: "Analyze only changed files or all",
10315
+ default: "all"
10316
+ }
10317
+ }
10318
+ }
10319
+ };
10320
+ var THRESHOLDS = {
10321
+ strict: {
10322
+ cognitiveComplexity: 10,
10323
+ cyclomaticComplexity: 8,
10324
+ functionSize: 30,
10325
+ nestingDepth: 2,
10326
+ parameterCount: 3,
10327
+ fileSize: 300
10328
+ },
10329
+ normal: {
10330
+ cognitiveComplexity: 15,
10331
+ cyclomaticComplexity: 10,
10332
+ functionSize: 50,
10333
+ nestingDepth: 3,
10334
+ parameterCount: 4,
10335
+ fileSize: 500
10336
+ },
10337
+ lenient: {
10338
+ cognitiveComplexity: 25,
10339
+ cyclomaticComplexity: 15,
10340
+ functionSize: 80,
10341
+ nestingDepth: 4,
10342
+ parameterCount: 5,
10343
+ fileSize: 800
10344
+ }
10345
+ };
10346
+ async function handleAnalyzeCodeQuality(args, config) {
10347
+ const input = AnalyzeCodeQualityInputSchema.parse(args);
10348
+ const projectPath = input.path || config.smartstack.projectPath;
10349
+ const thresholdLevel = input.threshold;
10350
+ const thresholds = THRESHOLDS[thresholdLevel];
10351
+ logger.info("Analyzing code quality", { projectPath, threshold: thresholdLevel });
10352
+ const structure = await findSmartStackStructure(projectPath);
10353
+ const allFunctionMetrics = [];
10354
+ const fileMetrics = /* @__PURE__ */ new Map();
10355
+ const csFiles = await findFiles("**/*.cs", { cwd: structure.root });
10356
+ const filteredCsFiles = csFiles.filter((f) => !isExcludedPath(f));
10357
+ for (const file of filteredCsFiles) {
10358
+ const content = await readText(file);
10359
+ const relPath = path21.relative(structure.root, file);
10360
+ const lineCount = content.split("\n").length;
10361
+ const functions = extractCSharpFunctions(content, relPath);
10362
+ allFunctionMetrics.push(...functions);
10363
+ fileMetrics.set(relPath, { lineCount, functions: functions.length });
10364
+ }
10365
+ const tsFiles = await findFiles("**/*.{ts,tsx}", { cwd: structure.root });
10366
+ const filteredTsFiles = tsFiles.filter((f) => !isExcludedPath(f));
10367
+ for (const file of filteredTsFiles) {
10368
+ const content = await readText(file);
10369
+ const relPath = path21.relative(structure.root, file);
10370
+ const lineCount = content.split("\n").length;
10371
+ const functions = extractTypeScriptFunctions(content, relPath);
10372
+ allFunctionMetrics.push(...functions);
10373
+ fileMetrics.set(relPath, { lineCount, functions: functions.length });
10374
+ }
10375
+ const metrics = calculateMetrics(allFunctionMetrics, fileMetrics, thresholds);
10376
+ const hotspots = identifyHotspots(allFunctionMetrics, fileMetrics, thresholds);
10377
+ const summary = calculateSummary(allFunctionMetrics, fileMetrics, metrics, hotspots);
10378
+ const result = {
10379
+ summary,
10380
+ metrics,
10381
+ hotspots
10382
+ };
10383
+ return formatQualityReport(result, thresholds);
10384
+ }
10385
+ function extractCSharpFunctions(content, file) {
10386
+ const functions = [];
10387
+ const lines = content.split("\n");
10388
+ const methodPattern = /\b(public|private|protected|internal)\s+(?:async\s+)?(?:static\s+)?(?:virtual\s+)?(?:override\s+)?(?:[\w<>,\s\[\]]+)\s+(\w+)\s*\(([^)]*)\)/gm;
10389
+ let match;
10390
+ while ((match = methodPattern.exec(content)) !== null) {
10391
+ const methodName = match[2];
10392
+ const params = match[3];
10393
+ const startLine = getLineNumber2(content, match.index);
10394
+ const endLine = findMethodEnd(lines, startLine - 1);
10395
+ const lineCount = endLine - startLine + 1;
10396
+ const parameterCount = params.trim() ? params.split(",").length : 0;
10397
+ const methodBody = lines.slice(startLine - 1, endLine).join("\n");
10398
+ const cognitiveComplexity = calculateCognitiveComplexity(methodBody);
10399
+ const cyclomaticComplexity = calculateCyclomaticComplexity(methodBody);
10400
+ const maxNestingDepth = calculateNestingDepth(methodBody);
10401
+ functions.push({
10402
+ name: methodName,
10403
+ file,
10404
+ startLine,
10405
+ endLine,
10406
+ lineCount,
10407
+ parameterCount,
10408
+ maxNestingDepth,
10409
+ cognitiveComplexity,
10410
+ cyclomaticComplexity
10411
+ });
10412
+ }
10413
+ return functions;
10414
+ }
10415
+ function extractTypeScriptFunctions(content, file) {
10416
+ const functions = [];
10417
+ const lines = content.split("\n");
10418
+ const processedFunctions = /* @__PURE__ */ new Set();
10419
+ const patterns = [
10420
+ // Arrow functions assigned to const/let
10421
+ /(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w+)?\s*=>/g,
10422
+ // Function declarations
10423
+ /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g,
10424
+ // Class methods (removed ^ anchor for consistency)
10425
+ /\b(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[\w<>,\s\[\]]+)?\s*\{/gm
10426
+ ];
10427
+ for (const pattern of patterns) {
10428
+ pattern.lastIndex = 0;
10429
+ let match;
10430
+ while ((match = pattern.exec(content)) !== null) {
10431
+ const funcName = match[1];
10432
+ const params = match[2] || "";
10433
+ const startLine = getLineNumber2(content, match.index);
10434
+ if (["if", "for", "while", "switch", "catch", "constructor"].includes(funcName)) continue;
10435
+ const funcKey = `${file}:${startLine}:${funcName}`;
10436
+ if (processedFunctions.has(funcKey)) continue;
10437
+ processedFunctions.add(funcKey);
10438
+ const endLine = findMethodEnd(lines, startLine - 1);
10439
+ const lineCount = endLine - startLine + 1;
10440
+ const parameterCount = params.trim() ? params.split(",").length : 0;
10441
+ const methodBody = lines.slice(startLine - 1, endLine).join("\n");
10442
+ const cognitiveComplexity = calculateCognitiveComplexity(methodBody);
10443
+ const cyclomaticComplexity = calculateCyclomaticComplexity(methodBody);
10444
+ const maxNestingDepth = calculateNestingDepth(methodBody);
10445
+ functions.push({
10446
+ name: funcName,
10447
+ file,
10448
+ startLine,
10449
+ endLine,
10450
+ lineCount,
10451
+ parameterCount,
10452
+ maxNestingDepth,
10453
+ cognitiveComplexity,
10454
+ cyclomaticComplexity
10455
+ });
10456
+ }
10457
+ }
10458
+ return functions;
10459
+ }
10460
+ function findMethodEnd(lines, startIndex) {
10461
+ let braceCount = 0;
10462
+ let foundFirstBrace = false;
10463
+ for (let i = startIndex; i < lines.length; i++) {
10464
+ const line = stripStringsAndComments(lines[i]);
10465
+ for (const char of line) {
10466
+ if (char === "{") {
10467
+ braceCount++;
10468
+ foundFirstBrace = true;
10469
+ } else if (char === "}") {
10470
+ braceCount--;
10471
+ if (foundFirstBrace && braceCount === 0) {
10472
+ return i + 1;
10473
+ }
10474
+ }
10475
+ }
10476
+ }
10477
+ return startIndex + 1;
10478
+ }
10479
+ function stripStringsAndComments(line) {
10480
+ let result = line.replace(/\/\/.*$/, "");
10481
+ result = result.replace(/"(?:[^"\\]|\\.)*"/g, '""');
10482
+ result = result.replace(/'(?:[^'\\]|\\.)*'/g, "''");
10483
+ result = result.replace(/`(?:[^`\\]|\\.)*`/g, "``");
10484
+ return result;
10485
+ }
10486
+ function calculateCognitiveComplexity(code) {
10487
+ let complexity = 0;
10488
+ let nestingLevel = 0;
10489
+ const lines = code.split("\n");
10490
+ for (const line of lines) {
10491
+ const trimmed = line.trim();
10492
+ const controlFlowPatterns = [
10493
+ /\bif\s*\(/g,
10494
+ /\belse\s+if\s*\(/g,
10495
+ /\bfor\s*\(/g,
10496
+ /\bforeach\s*\(/g,
10497
+ /\bwhile\s*\(/g,
10498
+ /\bdo\s*\{/g,
10499
+ /\bswitch\s*\(/g,
10500
+ /\bcatch\s*\(/g
10501
+ ];
10502
+ for (const pattern of controlFlowPatterns) {
10503
+ const matches = trimmed.match(pattern) || [];
10504
+ complexity += matches.length * (1 + nestingLevel);
10505
+ }
10506
+ const logicalPatterns = [/&&/g, /\|\|/g, /\?\?/g, /\?\./g];
10507
+ for (const pattern of logicalPatterns) {
10508
+ const matches = trimmed.match(pattern) || [];
10509
+ complexity += matches.length;
10510
+ }
10511
+ const ternaryMatches = trimmed.match(/\?[^?:]+:/g) || [];
10512
+ complexity += ternaryMatches.length;
10513
+ const openBraces = (trimmed.match(/\{/g) || []).length;
10514
+ const closeBraces = (trimmed.match(/\}/g) || []).length;
10515
+ nestingLevel += openBraces - closeBraces;
10516
+ nestingLevel = Math.max(0, nestingLevel);
10517
+ }
10518
+ return complexity;
10519
+ }
10520
+ function calculateCyclomaticComplexity(code) {
10521
+ let complexity = 1;
10522
+ const patterns = [
10523
+ /\bif\b/g,
10524
+ /\belse\s+if\b/g,
10525
+ /\bwhile\b/g,
10526
+ /\bfor\b/g,
10527
+ /\bforeach\b/g,
10528
+ /\bcase\b/g,
10529
+ /\bcatch\b/g,
10530
+ /\?\?/g,
10531
+ /\?\./g,
10532
+ /&&/g,
10533
+ /\|\|/g
10534
+ ];
10535
+ for (const pattern of patterns) {
10536
+ const matches = code.match(pattern) || [];
10537
+ complexity += matches.length;
10538
+ }
10539
+ return complexity;
10540
+ }
10541
+ function calculateNestingDepth(code) {
10542
+ let maxDepth = 0;
10543
+ let currentDepth = 0;
10544
+ for (const char of code) {
10545
+ if (char === "{") {
10546
+ currentDepth++;
10547
+ maxDepth = Math.max(maxDepth, currentDepth);
10548
+ } else if (char === "}") {
10549
+ currentDepth = Math.max(0, currentDepth - 1);
10550
+ }
10551
+ }
10552
+ return maxDepth;
10553
+ }
10554
+ function calculateMetrics(functions, fileMetrics, thresholds) {
10555
+ const createStat = (values, threshold) => {
10556
+ const filtered = values.filter((v) => v > 0);
10557
+ const avg = filtered.length > 0 ? filtered.reduce((a, b) => a + b, 0) / filtered.length : 0;
10558
+ const max = filtered.length > 0 ? Math.max(...filtered) : 0;
10559
+ const violations = filtered.filter((v) => v > threshold).length;
10560
+ return {
10561
+ average: Math.round(avg * 10) / 10,
10562
+ max,
10563
+ threshold,
10564
+ violations
10565
+ };
10566
+ };
10567
+ const fileSizes = Array.from(fileMetrics.values()).map((f) => f.lineCount);
10568
+ return {
10569
+ cognitiveComplexity: createStat(
10570
+ functions.map((f) => f.cognitiveComplexity),
10571
+ thresholds.cognitiveComplexity
10572
+ ),
10573
+ cyclomaticComplexity: createStat(
10574
+ functions.map((f) => f.cyclomaticComplexity),
10575
+ thresholds.cyclomaticComplexity
10576
+ ),
10577
+ functionSize: createStat(
10578
+ functions.map((f) => f.lineCount),
10579
+ thresholds.functionSize
10580
+ ),
10581
+ nestingDepth: createStat(
10582
+ functions.map((f) => f.maxNestingDepth),
10583
+ thresholds.nestingDepth
10584
+ ),
10585
+ fileSize: createStat(fileSizes, thresholds.fileSize)
10586
+ };
10587
+ }
10588
+ function identifyHotspots(functions, fileMetrics, thresholds) {
10589
+ const hotspots = [];
10590
+ for (const func of functions) {
10591
+ const issues = [];
10592
+ if (func.cognitiveComplexity > thresholds.cognitiveComplexity) {
10593
+ issues.push(`Cognitive complexity: ${func.cognitiveComplexity} (threshold: ${thresholds.cognitiveComplexity})`);
10594
+ }
10595
+ if (func.cyclomaticComplexity > thresholds.cyclomaticComplexity) {
10596
+ issues.push(`Cyclomatic complexity: ${func.cyclomaticComplexity} (threshold: ${thresholds.cyclomaticComplexity})`);
10597
+ }
10598
+ if (func.lineCount > thresholds.functionSize) {
10599
+ issues.push(`Lines: ${func.lineCount} (threshold: ${thresholds.functionSize})`);
10600
+ }
10601
+ if (func.maxNestingDepth > thresholds.nestingDepth) {
10602
+ issues.push(`Nesting depth: ${func.maxNestingDepth} (threshold: ${thresholds.nestingDepth})`);
10603
+ }
10604
+ if (issues.length > 0) {
10605
+ const severity = issues.length >= 3 ? "high" : issues.length === 2 ? "medium" : "low";
10606
+ hotspots.push({
10607
+ file: func.file,
10608
+ function: func.name,
10609
+ issues,
10610
+ severity,
10611
+ metrics: {
10612
+ cognitiveComplexity: func.cognitiveComplexity,
10613
+ cyclomaticComplexity: func.cyclomaticComplexity,
10614
+ lineCount: func.lineCount,
10615
+ nestingDepth: func.maxNestingDepth
10616
+ }
10617
+ });
10618
+ }
10619
+ }
10620
+ for (const [file, metrics] of fileMetrics) {
10621
+ if (metrics.lineCount > thresholds.fileSize) {
10622
+ hotspots.push({
10623
+ file,
10624
+ issues: [`File size: ${metrics.lineCount} lines (threshold: ${thresholds.fileSize})`],
10625
+ severity: "medium",
10626
+ metrics: {
10627
+ lineCount: metrics.lineCount
10628
+ }
10629
+ });
10630
+ }
10631
+ }
10632
+ const severityOrder = { high: 0, medium: 1, low: 2 };
10633
+ hotspots.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
10634
+ return hotspots.slice(0, 20);
10635
+ }
10636
+ function calculateSummary(functions, fileMetrics, metrics, hotspots) {
10637
+ const totalViolations = metrics.cognitiveComplexity.violations + metrics.cyclomaticComplexity.violations + metrics.functionSize.violations + metrics.nestingDepth.violations + metrics.fileSize.violations;
10638
+ const totalFunctions = functions.length;
10639
+ const totalFiles = fileMetrics.size;
10640
+ const violationRate = totalFunctions > 0 ? totalViolations / totalFunctions : 0;
10641
+ const score = Math.max(0, Math.round(100 - violationRate * 100));
10642
+ let grade;
10643
+ if (score >= 90) grade = "A";
10644
+ else if (score >= 80) grade = "B";
10645
+ else if (score >= 70) grade = "C";
10646
+ else if (score >= 60) grade = "D";
10647
+ else grade = "F";
10648
+ return {
10649
+ score,
10650
+ grade,
10651
+ filesAnalyzed: totalFiles,
10652
+ functionsAnalyzed: totalFunctions,
10653
+ issuesFound: hotspots.length
10654
+ };
10655
+ }
10656
+ function isExcludedPath(filePath) {
10657
+ const exclusions = [
10658
+ /[/\\]bin[/\\]/,
10659
+ /[/\\]obj[/\\]/,
10660
+ /[/\\]node_modules[/\\]/,
10661
+ /[/\\]Migrations[/\\]/,
10662
+ /\.test\./,
10663
+ /\.spec\./,
10664
+ /Tests[/\\]/,
10665
+ /\.d\.ts$/,
10666
+ /\.min\./
10667
+ ];
10668
+ return exclusions.some((pattern) => pattern.test(filePath));
10669
+ }
10670
+ function getLineNumber2(content, index) {
10671
+ const normalizedContent = content.substring(0, index).replace(/\r\n/g, "\n");
10672
+ return normalizedContent.split("\n").length;
10673
+ }
10674
+ function formatQualityReport(result, thresholds) {
10675
+ const lines = [];
10676
+ lines.push("# Code Quality Report");
10677
+ lines.push("");
10678
+ lines.push("## Summary");
10679
+ lines.push(`- **Score**: ${result.summary.score}/100 (Grade: ${result.summary.grade})`);
10680
+ lines.push(`- **Files analyzed**: ${result.summary.filesAnalyzed}`);
10681
+ lines.push(`- **Functions analyzed**: ${result.summary.functionsAnalyzed}`);
10682
+ lines.push(`- **Issues found**: ${result.summary.issuesFound}`);
10683
+ lines.push("");
10684
+ lines.push("## Metrics Overview");
10685
+ lines.push("");
10686
+ lines.push("| Metric | Average | Max | Threshold | Status |");
10687
+ lines.push("|--------|---------|-----|-----------|--------|");
10688
+ const formatMetricRow = (name, stat2) => {
10689
+ const status = stat2.violations > 0 ? `${stat2.violations} violations` : "\u2705 OK";
10690
+ return `| ${name} | ${stat2.average} | ${stat2.max} | ${stat2.threshold} | ${status} |`;
10691
+ };
10692
+ lines.push(formatMetricRow("Cognitive Complexity", result.metrics.cognitiveComplexity));
10693
+ lines.push(formatMetricRow("Cyclomatic Complexity", result.metrics.cyclomaticComplexity));
10694
+ lines.push(formatMetricRow("Function Size", result.metrics.functionSize));
10695
+ lines.push(formatMetricRow("Nesting Depth", result.metrics.nestingDepth));
10696
+ lines.push(formatMetricRow("File Size", result.metrics.fileSize));
10697
+ lines.push("");
10698
+ if (result.hotspots.length > 0) {
10699
+ lines.push("## Hotspots (Needs Attention)");
10700
+ lines.push("");
10701
+ for (const hotspot of result.hotspots) {
10702
+ const severityEmoji = hotspot.severity === "high" ? "\u{1F534}" : hotspot.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
10703
+ const location = hotspot.function ? `\`${hotspot.function}\` (${hotspot.file})` : `\`${hotspot.file}\``;
10704
+ lines.push(`### ${severityEmoji} ${hotspot.severity.toUpperCase()}: ${location}`);
10705
+ for (const issue of hotspot.issues) {
10706
+ lines.push(`- ${issue}`);
10707
+ }
10708
+ if (hotspot.function) {
10709
+ if (hotspot.metrics.cognitiveComplexity && hotspot.metrics.cognitiveComplexity > thresholds.cognitiveComplexity) {
10710
+ lines.push(`- **Recommendation**: Extract logic into smaller, focused methods`);
10711
+ } else if (hotspot.metrics.lineCount && hotspot.metrics.lineCount > thresholds.functionSize) {
10712
+ lines.push(`- **Recommendation**: Split into multiple functions with single responsibilities`);
10713
+ }
10714
+ } else {
10715
+ lines.push(`- **Recommendation**: Consider splitting this file into smaller modules`);
10716
+ }
10717
+ lines.push("");
10718
+ }
10719
+ } else {
10720
+ lines.push("No hotspots found. Code quality is within acceptable thresholds.");
10721
+ }
10722
+ return lines.join("\n");
10723
+ }
10724
+
9738
10725
  // src/resources/conventions.ts
9739
10726
  var conventionsResourceTemplate = {
9740
10727
  uri: "smartstack://conventions",
@@ -10870,7 +11857,7 @@ Run specific or all checks:
10870
11857
  }
10871
11858
 
10872
11859
  // src/resources/project-info.ts
10873
- import path20 from "path";
11860
+ import path22 from "path";
10874
11861
  var projectInfoResourceTemplate = {
10875
11862
  uri: "smartstack://project",
10876
11863
  name: "SmartStack Project Info",
@@ -10907,16 +11894,16 @@ async function getProjectInfoResource(config) {
10907
11894
  lines.push("```");
10908
11895
  lines.push(`${projectInfo.name}/`);
10909
11896
  if (structure.domain) {
10910
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.domain)}/ # Domain layer (entities)`);
11897
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.domain)}/ # Domain layer (entities)`);
10911
11898
  }
10912
11899
  if (structure.application) {
10913
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.application)}/ # Application layer (services)`);
11900
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.application)}/ # Application layer (services)`);
10914
11901
  }
10915
11902
  if (structure.infrastructure) {
10916
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
11903
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
10917
11904
  }
10918
11905
  if (structure.api) {
10919
- lines.push(`\u251C\u2500\u2500 ${path20.basename(structure.api)}/ # API layer (controllers)`);
11906
+ lines.push(`\u251C\u2500\u2500 ${path22.basename(structure.api)}/ # API layer (controllers)`);
10920
11907
  }
10921
11908
  if (structure.web) {
10922
11909
  lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
@@ -10929,8 +11916,8 @@ async function getProjectInfoResource(config) {
10929
11916
  lines.push("| Project | Path |");
10930
11917
  lines.push("|---------|------|");
10931
11918
  for (const csproj of projectInfo.csprojFiles) {
10932
- const name = path20.basename(csproj, ".csproj");
10933
- const relativePath = path20.relative(projectPath, csproj);
11919
+ const name = path22.basename(csproj, ".csproj");
11920
+ const relativePath = path22.relative(projectPath, csproj);
10934
11921
  lines.push(`| ${name} | \`${relativePath}\` |`);
10935
11922
  }
10936
11923
  lines.push("");
@@ -10940,10 +11927,10 @@ async function getProjectInfoResource(config) {
10940
11927
  cwd: structure.migrations,
10941
11928
  ignore: ["*.Designer.cs"]
10942
11929
  });
10943
- const migrations = migrationFiles.map((f) => path20.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
11930
+ const migrations = migrationFiles.map((f) => path22.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
10944
11931
  lines.push("## EF Core Migrations");
10945
11932
  lines.push("");
10946
- lines.push(`**Location**: \`${path20.relative(projectPath, structure.migrations)}\``);
11933
+ lines.push(`**Location**: \`${path22.relative(projectPath, structure.migrations)}\``);
10947
11934
  lines.push(`**Total Migrations**: ${migrations.length}`);
10948
11935
  lines.push("");
10949
11936
  if (migrations.length > 0) {
@@ -10978,11 +11965,11 @@ async function getProjectInfoResource(config) {
10978
11965
  lines.push("dotnet build");
10979
11966
  lines.push("");
10980
11967
  lines.push("# Run API");
10981
- lines.push(`cd ${structure.api ? path20.relative(projectPath, structure.api) : "src/Api"}`);
11968
+ lines.push(`cd ${structure.api ? path22.relative(projectPath, structure.api) : "src/Api"}`);
10982
11969
  lines.push("dotnet run");
10983
11970
  lines.push("");
10984
11971
  lines.push("# Run frontend");
10985
- lines.push(`cd ${structure.web ? path20.relative(projectPath, structure.web) : "web"}`);
11972
+ lines.push(`cd ${structure.web ? path22.relative(projectPath, structure.web) : "web"}`);
10986
11973
  lines.push("npm run dev");
10987
11974
  lines.push("");
10988
11975
  lines.push("# Create migration");
@@ -11005,7 +11992,7 @@ async function getProjectInfoResource(config) {
11005
11992
  }
11006
11993
 
11007
11994
  // src/resources/api-endpoints.ts
11008
- import path21 from "path";
11995
+ import path23 from "path";
11009
11996
  var apiEndpointsResourceTemplate = {
11010
11997
  uri: "smartstack://api/",
11011
11998
  name: "SmartStack API Endpoints",
@@ -11030,7 +12017,7 @@ async function getApiEndpointsResource(config, endpointFilter) {
11030
12017
  }
11031
12018
  async function parseController(filePath, _rootPath) {
11032
12019
  const content = await readText(filePath);
11033
- const fileName = path21.basename(filePath, ".cs");
12020
+ const fileName = path23.basename(filePath, ".cs");
11034
12021
  const controllerName = fileName.replace("Controller", "");
11035
12022
  const endpoints = [];
11036
12023
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
@@ -11177,7 +12164,7 @@ function getMethodEmoji(method) {
11177
12164
  }
11178
12165
 
11179
12166
  // src/resources/db-schema.ts
11180
- import path22 from "path";
12167
+ import path24 from "path";
11181
12168
  var dbSchemaResourceTemplate = {
11182
12169
  uri: "smartstack://schema/",
11183
12170
  name: "SmartStack Database Schema",
@@ -11267,7 +12254,7 @@ async function parseEntity(filePath, rootPath, _config) {
11267
12254
  tableName,
11268
12255
  properties,
11269
12256
  relationships,
11270
- file: path22.relative(rootPath, filePath)
12257
+ file: path24.relative(rootPath, filePath)
11271
12258
  };
11272
12259
  }
11273
12260
  async function enrichFromConfigurations(entities, infrastructurePath, _config) {
@@ -11413,7 +12400,7 @@ function formatSchema(entities, filter, _config) {
11413
12400
  }
11414
12401
 
11415
12402
  // src/resources/entities.ts
11416
- import path23 from "path";
12403
+ import path25 from "path";
11417
12404
  var entitiesResourceTemplate = {
11418
12405
  uri: "smartstack://entities/",
11419
12406
  name: "SmartStack Entities",
@@ -11473,7 +12460,7 @@ async function parseEntitySummary(filePath, rootPath, config) {
11473
12460
  hasSoftDelete,
11474
12461
  hasRowVersion,
11475
12462
  file: filePath,
11476
- relativePath: path23.relative(rootPath, filePath)
12463
+ relativePath: path25.relative(rootPath, filePath)
11477
12464
  };
11478
12465
  }
11479
12466
  function inferTableInfo(entityName, config) {
@@ -11673,7 +12660,10 @@ async function createServer() {
11673
12660
  validateFrontendRoutesTool,
11674
12661
  // Frontend Extension Tools
11675
12662
  scaffoldFrontendExtensionTool,
11676
- analyzeExtensionPointsTool
12663
+ analyzeExtensionPointsTool,
12664
+ // Security & Code Quality Tools
12665
+ validateSecurityTool,
12666
+ analyzeCodeQualityTool
11677
12667
  ]
11678
12668
  };
11679
12669
  });
@@ -11732,6 +12722,13 @@ async function createServer() {
11732
12722
  case "analyze_extension_points":
11733
12723
  result = await handleAnalyzeExtensionPoints(args ?? {}, config);
11734
12724
  break;
12725
+ // Security & Code Quality Tools
12726
+ case "validate_security":
12727
+ result = await handleValidateSecurity(args ?? {}, config);
12728
+ break;
12729
+ case "analyze_code_quality":
12730
+ result = await handleAnalyzeCodeQuality(args ?? {}, config);
12731
+ break;
11735
12732
  default:
11736
12733
  throw new Error(`Unknown tool: ${name}`);
11737
12734
  }