@atlashub/smartstack-mcp 1.20.0 → 1.22.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
@@ -386,6 +386,9 @@ var QualityMetricSchema = z.enum([
386
386
  "parameter-count",
387
387
  "code-duplication",
388
388
  "file-size",
389
+ "unused-members",
390
+ "duplicated-strings",
391
+ "unassigned-fields",
389
392
  "all"
390
393
  ]);
391
394
  var AnalyzeCodeQualityInputSchema = z.object({
@@ -2435,6 +2438,27 @@ Handlebars.registerHelper("camelCase", (str) => {
2435
2438
  Handlebars.registerHelper("kebabCase", (str) => {
2436
2439
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
2437
2440
  });
2441
+ function resolveHierarchy(navRoute) {
2442
+ if (!navRoute) {
2443
+ return { context: "", application: "", module: "", domainPath: "", infraPath: "", controllerArea: "" };
2444
+ }
2445
+ const segments = navRoute.split(".");
2446
+ const toPascal = (s) => s.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
2447
+ const context = segments[0] ? toPascal(segments[0]) : "";
2448
+ const application = segments[1] ? toPascal(segments[1]) : "";
2449
+ const module = segments[2] ? toPascal(segments[2]) : segments[1] ? toPascal(segments[1]) : "";
2450
+ let domainPath = "";
2451
+ if (segments.length >= 3) {
2452
+ domainPath = path8.join(context, application, module);
2453
+ } else if (segments.length === 2) {
2454
+ domainPath = path8.join(context, module);
2455
+ } else if (segments.length === 1) {
2456
+ domainPath = context;
2457
+ }
2458
+ const infraPath = module || "";
2459
+ const controllerArea = context;
2460
+ return { context, application, module, domainPath, infraPath, controllerArea };
2461
+ }
2438
2462
  async function handleScaffoldExtension(args, config) {
2439
2463
  const input = ScaffoldExtensionInputSchema.parse(args);
2440
2464
  const dryRun = input.options?.dryRun || false;
@@ -2579,17 +2603,21 @@ async function scaffoldFeature(name, options, structure, config, result, dryRun
2579
2603
  result.instructions.push(`${withRepository ? withValidation ? "5" : "4" : withValidation ? "4" : "3"}. Create migration: \`dotnet ef migrations add ${migrationPrefix}_vX.X.X_XXX_Add${name} --context ${dbContextName}\``);
2580
2604
  result.instructions.push(`${withRepository ? withValidation ? "6" : "5" : withValidation ? "5" : "4"}. Run migration: \`dotnet ef database update --context ${dbContextName}\``);
2581
2605
  if (!skipComponent) {
2582
- result.instructions.push(`Import component: \`import { ${name} } from './components/${name}';\``);
2606
+ const featureHierarchy = resolveHierarchy(options?.navRoute);
2607
+ const featureComponentPath = featureHierarchy.context && featureHierarchy.module ? `@/components/${featureHierarchy.context.toLowerCase()}/${featureHierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
2608
+ result.instructions.push(`Import component: \`import { ${name} } from '${featureComponentPath}';\``);
2583
2609
  }
2584
2610
  }
2585
2611
  async function scaffoldService(name, options, structure, config, result, dryRun = false) {
2586
- const namespace = options?.namespace || `${config.conventions.namespaces.application}.Services`;
2612
+ const hierarchy = resolveHierarchy(options?.navRoute);
2613
+ const interfaceNamespace = options?.namespace || `${config.conventions.namespaces.application}.Common.Interfaces`;
2614
+ const implNamespace = hierarchy.infraPath ? `${config.conventions.namespaces.infrastructure}.Services.${hierarchy.infraPath}` : `${config.conventions.namespaces.infrastructure}.Services`;
2587
2615
  const methods = options?.methods || ["GetByIdAsync", "GetAllAsync", "CreateAsync", "UpdateAsync", "DeleteAsync"];
2588
2616
  const interfaceTemplate = `using System.Threading;
2589
2617
  using System.Threading.Tasks;
2590
2618
  using System.Collections.Generic;
2591
2619
 
2592
- namespace {{namespace}};
2620
+ namespace {{interfaceNamespace}};
2593
2621
 
2594
2622
  /// <summary>
2595
2623
  /// Service interface for {{name}} operations
@@ -2610,7 +2638,7 @@ using System.Threading.Tasks;
2610
2638
  using System.Collections.Generic;
2611
2639
  using Microsoft.Extensions.Logging;
2612
2640
 
2613
- namespace {{namespace}};
2641
+ namespace {{implNamespace}};
2614
2642
 
2615
2643
  /// <summary>
2616
2644
  /// Service implementation for {{name}} operations
@@ -2640,19 +2668,22 @@ public class {{name}}Service : I{{name}}Service
2640
2668
  const diTemplate = `// Add to DependencyInjection.cs or ServiceCollectionExtensions.cs:
2641
2669
  services.AddScoped<I{{name}}Service, {{name}}Service>();
2642
2670
  `;
2643
- const context = { namespace, name, methods };
2671
+ const context = { interfaceNamespace, implNamespace, name, methods };
2644
2672
  const interfaceContent = Handlebars.compile(interfaceTemplate)(context);
2645
2673
  const implementationContent = Handlebars.compile(implementationTemplate)(context);
2646
2674
  const diContent = Handlebars.compile(diTemplate)(context);
2647
2675
  const projectRoot = config.smartstack.projectPath;
2648
- const basePath = structure.application || projectRoot;
2649
- const servicesPath = path8.join(basePath, "Services");
2650
- const interfacePath = path8.join(servicesPath, `I${name}Service.cs`);
2651
- const implementationPath = path8.join(servicesPath, `${name}Service.cs`);
2676
+ const appPath = structure.application || projectRoot;
2677
+ const infraPath = structure.infrastructure || path8.join(projectRoot, "Infrastructure");
2678
+ const interfacesDir = path8.join(appPath, "Common", "Interfaces");
2679
+ const implDir = hierarchy.infraPath ? path8.join(infraPath, "Services", hierarchy.infraPath) : path8.join(infraPath, "Services");
2680
+ const interfacePath = path8.join(interfacesDir, `I${name}Service.cs`);
2681
+ const implementationPath = path8.join(implDir, `${name}Service.cs`);
2652
2682
  validatePathSecurity(interfacePath, projectRoot);
2653
2683
  validatePathSecurity(implementationPath, projectRoot);
2654
2684
  if (!dryRun) {
2655
- await ensureDirectory(servicesPath);
2685
+ await ensureDirectory(interfacesDir);
2686
+ await ensureDirectory(implDir);
2656
2687
  await writeText(interfacePath, interfaceContent);
2657
2688
  await writeText(implementationPath, implementationContent);
2658
2689
  }
@@ -2662,7 +2693,8 @@ services.AddScoped<I{{name}}Service, {{name}}Service>();
2662
2693
  result.instructions.push(diContent);
2663
2694
  }
2664
2695
  async function scaffoldEntity(name, options, structure, config, result, dryRun = false) {
2665
- const namespace = options?.namespace || config.conventions.namespaces.domain;
2696
+ const hierarchy = resolveHierarchy(options?.navRoute);
2697
+ const namespace = options?.namespace || (hierarchy.domainPath ? `${config.conventions.namespaces.domain}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}` : config.conventions.namespaces.domain);
2666
2698
  const baseEntity = options?.baseEntity;
2667
2699
  const isSystemEntity = options?.isSystemEntity || false;
2668
2700
  const tablePrefix = options?.tablePrefix || "ref_";
@@ -2892,15 +2924,17 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
2892
2924
  const entityContent = Handlebars.compile(entityTemplate)(context);
2893
2925
  const configContent = Handlebars.compile(configTemplate)(context);
2894
2926
  const projectRoot = config.smartstack.projectPath;
2895
- const domainPath = structure.domain || path8.join(projectRoot, "Domain");
2896
- const infraPath = structure.infrastructure || path8.join(projectRoot, "Infrastructure");
2897
- const entityFilePath = path8.join(domainPath, `${name}.cs`);
2898
- const configFilePath = path8.join(infraPath, "Persistence", "Configurations", `${name}Configuration.cs`);
2927
+ const domainBase = structure.domain || path8.join(projectRoot, "Domain");
2928
+ const infraBase = structure.infrastructure || path8.join(projectRoot, "Infrastructure");
2929
+ const entityDir = hierarchy.domainPath ? path8.join(domainBase, hierarchy.domainPath) : domainBase;
2930
+ const configDir = hierarchy.infraPath ? path8.join(infraBase, "Persistence", "Configurations", hierarchy.infraPath) : path8.join(infraBase, "Persistence", "Configurations");
2931
+ const entityFilePath = path8.join(entityDir, `${name}.cs`);
2932
+ const configFilePath = path8.join(configDir, `${name}Configuration.cs`);
2899
2933
  validatePathSecurity(entityFilePath, projectRoot);
2900
2934
  validatePathSecurity(configFilePath, projectRoot);
2901
2935
  if (!dryRun) {
2902
- await ensureDirectory(domainPath);
2903
- await ensureDirectory(path8.join(infraPath, "Persistence", "Configurations"));
2936
+ await ensureDirectory(entityDir);
2937
+ await ensureDirectory(configDir);
2904
2938
  await writeText(entityFilePath, entityContent);
2905
2939
  await writeText(configFilePath, configContent);
2906
2940
  }
@@ -3217,7 +3251,8 @@ GO
3217
3251
  result.instructions.push("- Group memberships (user belongs to parent groups)");
3218
3252
  }
3219
3253
  async function scaffoldController(name, options, structure, config, result, dryRun = false) {
3220
- const namespace = options?.namespace || `${config.conventions.namespaces.api}.Controllers`;
3254
+ const hierarchy = resolveHierarchy(options?.navRoute);
3255
+ const namespace = options?.namespace || (hierarchy.controllerArea ? `${config.conventions.namespaces.api}.Controllers.${hierarchy.controllerArea}` : `${config.conventions.namespaces.api}.Controllers`);
3221
3256
  const navRoute = options?.navRoute;
3222
3257
  const navRouteSuffix = options?.navRouteSuffix;
3223
3258
  const routeAttribute = navRoute ? navRouteSuffix ? `[NavRoute("${navRoute}", Suffix = "${navRouteSuffix}")]` : `[NavRoute("${navRoute}")]` : `[Route("api/[controller]")]`;
@@ -3313,11 +3348,11 @@ public record Update{{name}}Request();
3313
3348
  const controllerContent = Handlebars.compile(controllerTemplate)(context);
3314
3349
  const projectRoot = config.smartstack.projectPath;
3315
3350
  const apiPath = structure.api || path8.join(projectRoot, "Api");
3316
- const controllersPath = path8.join(apiPath, "Controllers");
3317
- const controllerFilePath = path8.join(controllersPath, `${name}Controller.cs`);
3351
+ const controllersDir = hierarchy.controllerArea ? path8.join(apiPath, "Controllers", hierarchy.controllerArea) : path8.join(apiPath, "Controllers");
3352
+ const controllerFilePath = path8.join(controllersDir, `${name}Controller.cs`);
3318
3353
  validatePathSecurity(controllerFilePath, projectRoot);
3319
3354
  if (!dryRun) {
3320
- await ensureDirectory(controllersPath);
3355
+ await ensureDirectory(controllersDir);
3321
3356
  await writeText(controllerFilePath, controllerContent);
3322
3357
  }
3323
3358
  result.files.push({ path: controllerFilePath, content: controllerContent, type: "created" });
@@ -3343,6 +3378,7 @@ public record Update{{name}}Request();
3343
3378
  }
3344
3379
  }
3345
3380
  async function scaffoldComponent(name, options, structure, config, result, dryRun = false) {
3381
+ const hierarchy = resolveHierarchy(options?.navRoute);
3346
3382
  const componentTemplate = `import React, { useState, useEffect } from 'react';
3347
3383
 
3348
3384
  interface {{name}}Props {
@@ -3495,7 +3531,8 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
3495
3531
  const hookContent = Handlebars.compile(hookTemplate)(context);
3496
3532
  const projectRoot = config.smartstack.projectPath;
3497
3533
  const webPath = structure.web || path8.join(projectRoot, "web");
3498
- const componentsPath = options?.outputPath || path8.join(webPath, "src", "components");
3534
+ const componentsBase = path8.join(webPath, "src", "components");
3535
+ const componentsPath = options?.outputPath ? options.outputPath : hierarchy.context && hierarchy.module ? path8.join(componentsBase, hierarchy.context.toLowerCase(), hierarchy.module.toLowerCase()) : componentsBase;
3499
3536
  const hooksPath = path8.join(webPath, "src", "hooks");
3500
3537
  const componentFilePath = path8.join(componentsPath, `${name}.tsx`);
3501
3538
  const hookFilePath = path8.join(hooksPath, `use${name}.ts`);
@@ -3510,8 +3547,9 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
3510
3547
  result.files.push({ path: componentFilePath, content: componentContent, type: "created" });
3511
3548
  result.files.push({ path: hookFilePath, content: hookContent, type: "created" });
3512
3549
  result.instructions.push("Import and use the component:");
3513
- result.instructions.push(`import { ${name} } from './components/${name}';`);
3514
- result.instructions.push(`import { use${name} } from './hooks/use${name}';`);
3550
+ const componentImportPath = hierarchy.context && hierarchy.module ? `@/components/${hierarchy.context.toLowerCase()}/${hierarchy.module.toLowerCase()}/${name}` : `./components/${name}`;
3551
+ result.instructions.push(`import { ${name} } from '${componentImportPath}';`);
3552
+ result.instructions.push(`import { use${name} } from '@/hooks/use${name}';`);
3515
3553
  }
3516
3554
  async function scaffoldTest(name, options, structure, config, result, dryRun = false) {
3517
3555
  const isSystemEntity = options?.isSystemEntity || false;
@@ -3639,7 +3677,8 @@ public class {{name}}ServiceTests
3639
3677
  result.instructions.push("- FluentAssertions");
3640
3678
  }
3641
3679
  async function scaffoldDtos(name, options, structure, config, result, dryRun = false) {
3642
- const namespace = options?.namespace || `${config.conventions.namespaces.application}.DTOs`;
3680
+ const hierarchy = resolveHierarchy(options?.navRoute);
3681
+ const namespace = options?.namespace || (hierarchy.domainPath ? `${config.conventions.namespaces.application}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}.DTOs` : `${config.conventions.namespaces.application}.DTOs`);
3643
3682
  const isSystemEntity = options?.isSystemEntity || false;
3644
3683
  const properties = options?.entityProperties || [
3645
3684
  { name: "Name", type: "string", required: true, maxLength: 200 },
@@ -3756,7 +3795,7 @@ public record Update{{name}}Dto
3756
3795
  const createContent = Handlebars.compile(createDtoTemplate)(context);
3757
3796
  const updateContent = Handlebars.compile(updateDtoTemplate)(context);
3758
3797
  const basePath = structure.application || config.smartstack.projectPath;
3759
- const dtosPath = path8.join(basePath, "DTOs", name);
3798
+ const dtosPath = hierarchy.domainPath ? path8.join(basePath, hierarchy.domainPath, "DTOs") : path8.join(basePath, "DTOs", name);
3760
3799
  const responseFilePath = path8.join(dtosPath, `${name}ResponseDto.cs`);
3761
3800
  const createFilePath = path8.join(dtosPath, `Create${name}Dto.cs`);
3762
3801
  const updateFilePath = path8.join(dtosPath, `Update${name}Dto.cs`);
@@ -3775,7 +3814,8 @@ public record Update{{name}}Dto
3775
3814
  result.instructions.push(`- Update${name}Dto: For PUT requests`);
3776
3815
  }
3777
3816
  async function scaffoldValidator(name, options, structure, config, result, dryRun = false) {
3778
- const namespace = options?.namespace || `${config.conventions.namespaces.application}.Validators`;
3817
+ const hierarchy = resolveHierarchy(options?.navRoute);
3818
+ const namespace = options?.namespace || (hierarchy.domainPath ? `${config.conventions.namespaces.application}.${hierarchy.domainPath.replace(/[\\/]/g, ".")}.Validators` : `${config.conventions.namespaces.application}.Validators`);
3779
3819
  const properties = options?.entityProperties || [
3780
3820
  { name: "Name", type: "string", required: true, maxLength: 200 },
3781
3821
  { name: "Description", type: "string?", required: false, maxLength: 500 }
@@ -3853,7 +3893,7 @@ public class Update{{name}}DtoValidator : AbstractValidator<Update{{name}}Dto>
3853
3893
  const createValidatorContent = Handlebars.compile(createValidatorTemplate)(context);
3854
3894
  const updateValidatorContent = Handlebars.compile(updateValidatorTemplate)(context);
3855
3895
  const basePath = structure.application || config.smartstack.projectPath;
3856
- const validatorsPath = path8.join(basePath, "Validators");
3896
+ const validatorsPath = hierarchy.domainPath ? path8.join(basePath, hierarchy.domainPath, "Validators") : path8.join(basePath, "Validators");
3857
3897
  const createValidatorFilePath = path8.join(validatorsPath, `Create${name}DtoValidator.cs`);
3858
3898
  const updateValidatorFilePath = path8.join(validatorsPath, `Update${name}DtoValidator.cs`);
3859
3899
  if (!dryRun) {
@@ -3869,6 +3909,7 @@ public class Update{{name}}DtoValidator : AbstractValidator<Update{{name}}Dto>
3869
3909
  result.instructions.push("Required package: FluentValidation.DependencyInjectionExtensions");
3870
3910
  }
3871
3911
  async function scaffoldRepository(name, options, structure, config, result, dryRun = false) {
3912
+ const hierarchy = resolveHierarchy(options?.navRoute);
3872
3913
  const isSystemEntity = options?.isSystemEntity || false;
3873
3914
  const schema = options?.schema || config.conventions.schemas.platform;
3874
3915
  const dbContextName = schema === "extensions" ? "ExtensionsDbContext" : "CoreDbContext";
@@ -3992,11 +4033,13 @@ public class {{name}}Repository : I{{name}}Repository
3992
4033
  const implementationContent = Handlebars.compile(implementationTemplate)(context);
3993
4034
  const appPath = structure.application || config.smartstack.projectPath;
3994
4035
  const infraPath = structure.infrastructure || path8.join(config.smartstack.projectPath, "Infrastructure");
3995
- const interfaceFilePath = path8.join(appPath, "Repositories", `I${name}Repository.cs`);
3996
- const implementationFilePath = path8.join(infraPath, "Repositories", `${name}Repository.cs`);
4036
+ const appRepoDir = hierarchy.infraPath ? path8.join(appPath, "Repositories", hierarchy.infraPath) : path8.join(appPath, "Repositories");
4037
+ const infraRepoDir = hierarchy.infraPath ? path8.join(infraPath, "Repositories", hierarchy.infraPath) : path8.join(infraPath, "Repositories");
4038
+ const interfaceFilePath = path8.join(appRepoDir, `I${name}Repository.cs`);
4039
+ const implementationFilePath = path8.join(infraRepoDir, `${name}Repository.cs`);
3997
4040
  if (!dryRun) {
3998
- await ensureDirectory(path8.join(appPath, "Repositories"));
3999
- await ensureDirectory(path8.join(infraPath, "Repositories"));
4041
+ await ensureDirectory(appRepoDir);
4042
+ await ensureDirectory(infraRepoDir);
4000
4043
  await writeText(interfaceFilePath, interfaceContent);
4001
4044
  await writeText(implementationFilePath, implementationContent);
4002
4045
  }
@@ -10866,7 +10909,7 @@ function formatFinding(finding, lines) {
10866
10909
  import path21 from "path";
10867
10910
  var analyzeCodeQualityTool = {
10868
10911
  name: "analyze_code_quality",
10869
- description: "Analyze code quality metrics for SmartStack projects: cognitive complexity, cyclomatic complexity, function size, nesting depth, and maintainability indicators.",
10912
+ description: "Analyze code quality metrics for SmartStack projects: cognitive complexity, cyclomatic complexity, function size, nesting depth, unused members (S1144), duplicated strings (S1192), and unassigned fields (S3459).",
10870
10913
  inputSchema: {
10871
10914
  type: "object",
10872
10915
  properties: {
@@ -10886,6 +10929,9 @@ var analyzeCodeQualityTool = {
10886
10929
  "parameter-count",
10887
10930
  "code-duplication",
10888
10931
  "file-size",
10932
+ "unused-members",
10933
+ "duplicated-strings",
10934
+ "unassigned-fields",
10889
10935
  "all"
10890
10936
  ]
10891
10937
  },
@@ -10938,10 +10984,15 @@ async function handleAnalyzeCodeQuality(args, config) {
10938
10984
  const projectPath = input.path || config.smartstack.projectPath;
10939
10985
  const thresholdLevel = input.threshold;
10940
10986
  const thresholds = THRESHOLDS[thresholdLevel];
10987
+ const requestedMetrics = input.metrics || ["all"];
10988
+ const analyzeAll = requestedMetrics.includes("all");
10941
10989
  logger.info("Analyzing code quality", { projectPath, threshold: thresholdLevel });
10942
10990
  const structure = await findSmartStackStructure(projectPath);
10943
10991
  const allFunctionMetrics = [];
10944
10992
  const fileMetrics = /* @__PURE__ */ new Map();
10993
+ const fileContents = /* @__PURE__ */ new Map();
10994
+ const allUnusedMembers = [];
10995
+ const allUnassignedFields = [];
10945
10996
  const csFiles = await findFiles("**/*.cs", { cwd: structure.root });
10946
10997
  const filteredCsFiles = csFiles.filter((f) => !isExcludedPath(f));
10947
10998
  for (const file of filteredCsFiles) {
@@ -10951,6 +11002,13 @@ async function handleAnalyzeCodeQuality(args, config) {
10951
11002
  const functions = extractCSharpFunctions(content, relPath);
10952
11003
  allFunctionMetrics.push(...functions);
10953
11004
  fileMetrics.set(relPath, { lineCount, functions: functions.length });
11005
+ fileContents.set(relPath, content);
11006
+ if (analyzeAll || requestedMetrics.includes("unused-members")) {
11007
+ allUnusedMembers.push(...detectUnusedMembers(content, relPath));
11008
+ }
11009
+ if (analyzeAll || requestedMetrics.includes("unassigned-fields")) {
11010
+ allUnassignedFields.push(...detectUnassignedFields(content, relPath));
11011
+ }
10954
11012
  }
10955
11013
  const tsFiles = await findFiles("**/*.{ts,tsx}", { cwd: structure.root });
10956
11014
  const filteredTsFiles = tsFiles.filter((f) => !isExcludedPath(f));
@@ -10961,6 +11019,7 @@ async function handleAnalyzeCodeQuality(args, config) {
10961
11019
  const functions = extractTypeScriptFunctions(content, relPath);
10962
11020
  allFunctionMetrics.push(...functions);
10963
11021
  fileMetrics.set(relPath, { lineCount, functions: functions.length });
11022
+ fileContents.set(relPath, content);
10964
11023
  }
10965
11024
  const metrics = calculateMetrics(allFunctionMetrics, fileMetrics, thresholds);
10966
11025
  const hotspots = identifyHotspots(allFunctionMetrics, fileMetrics, thresholds);
@@ -10970,6 +11029,13 @@ async function handleAnalyzeCodeQuality(args, config) {
10970
11029
  metrics,
10971
11030
  hotspots
10972
11031
  };
11032
+ let duplicatedStrings = [];
11033
+ if (analyzeAll || requestedMetrics.includes("duplicated-strings")) {
11034
+ duplicatedStrings = detectDuplicatedStrings(fileContents);
11035
+ }
11036
+ if (allUnusedMembers.length > 0 || duplicatedStrings.length > 0 || allUnassignedFields.length > 0) {
11037
+ return formatExtendedReport(result, thresholds, allUnusedMembers, duplicatedStrings, allUnassignedFields);
11038
+ }
10973
11039
  return formatQualityReport(result, thresholds);
10974
11040
  }
10975
11041
  function extractCSharpFunctions(content, file) {
@@ -11311,6 +11377,174 @@ function formatQualityReport(result, thresholds) {
11311
11377
  }
11312
11378
  return lines.join("\n");
11313
11379
  }
11380
+ function detectUnusedMembers(content, file) {
11381
+ const unusedMembers = [];
11382
+ const privateMethodPattern = /\b(private|internal)\s+(?:async\s+)?(?:static\s+)?[\w<>,\s\[\]]+\s+(\w+)\s*\(/gm;
11383
+ let match;
11384
+ while ((match = privateMethodPattern.exec(content)) !== null) {
11385
+ const visibility = match[1];
11386
+ const methodName = match[2];
11387
+ const line = getLineNumber2(content, match.index);
11388
+ if (["get", "set", "Dispose", "InitializeComponent"].includes(methodName)) continue;
11389
+ const regex = new RegExp(`\\b${methodName}\\b`, "g");
11390
+ const occurrences = (content.match(regex) || []).length;
11391
+ if (occurrences === 1) {
11392
+ unusedMembers.push({
11393
+ name: methodName,
11394
+ type: "method",
11395
+ visibility,
11396
+ file,
11397
+ line,
11398
+ rule: "S1144"
11399
+ });
11400
+ }
11401
+ }
11402
+ const privateFieldPattern = /\b(private|internal)\s+(?:readonly\s+)?(?:static\s+)?[\w<>,\[\]]+\s+_?(\w+)\s*[;=]/gm;
11403
+ while ((match = privateFieldPattern.exec(content)) !== null) {
11404
+ const visibility = match[1];
11405
+ const fieldName = match[2];
11406
+ const line = getLineNumber2(content, match.index);
11407
+ const regex = new RegExp(`\\b${fieldName}\\b`, "g");
11408
+ const occurrences = (content.match(regex) || []).length;
11409
+ if (occurrences === 1) {
11410
+ unusedMembers.push({
11411
+ name: fieldName,
11412
+ type: "field",
11413
+ visibility,
11414
+ file,
11415
+ line,
11416
+ rule: "S1144"
11417
+ });
11418
+ }
11419
+ }
11420
+ return unusedMembers;
11421
+ }
11422
+ function detectDuplicatedStrings(fileContents) {
11423
+ const stringOccurrences = /* @__PURE__ */ new Map();
11424
+ for (const [file, content] of fileContents) {
11425
+ const stringPattern = /"([^"\\]|\\.){5,}"/g;
11426
+ let match;
11427
+ while ((match = stringPattern.exec(content)) !== null) {
11428
+ const literal = match[0];
11429
+ const line = getLineNumber2(content, match.index);
11430
+ if (isIgnoredStringLiteral(literal)) continue;
11431
+ if (!stringOccurrences.has(literal)) {
11432
+ stringOccurrences.set(literal, []);
11433
+ }
11434
+ stringOccurrences.get(literal).push({ file, line });
11435
+ }
11436
+ }
11437
+ const duplicated = [];
11438
+ for (const [literal, locations] of stringOccurrences) {
11439
+ if (locations.length >= 3) {
11440
+ duplicated.push({
11441
+ literal,
11442
+ occurrences: locations.length,
11443
+ locations: locations.slice(0, 5),
11444
+ // Limit to 5 examples
11445
+ suggestedConstantName: suggestConstantName(literal),
11446
+ rule: "S1192"
11447
+ });
11448
+ }
11449
+ }
11450
+ duplicated.sort((a, b) => b.occurrences - a.occurrences);
11451
+ return duplicated.slice(0, 10);
11452
+ }
11453
+ function isIgnoredStringLiteral(literal) {
11454
+ const ignoredPatterns = [
11455
+ /^"\s*"$/,
11456
+ // Empty or whitespace
11457
+ /^"[,.\-:;\/\\]+"$/,
11458
+ // Punctuation only
11459
+ /^"https?:\/\//,
11460
+ // URLs
11461
+ /^"[a-z]{1,4}:"$/i,
11462
+ // Protocol prefixes
11463
+ /^"\{[0-9]+\}"$/,
11464
+ // Format placeholders
11465
+ /^"[a-z_]+:[a-z_]+"$/i
11466
+ // Resource keys
11467
+ ];
11468
+ return ignoredPatterns.some((p) => p.test(literal));
11469
+ }
11470
+ function suggestConstantName(literal) {
11471
+ const content = literal.slice(1, -1);
11472
+ const words = content.replace(/[^a-zA-Z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 0).slice(0, 4);
11473
+ if (words.length === 0) return "STRING_CONSTANT";
11474
+ return words.map((w) => w.toUpperCase()).join("_");
11475
+ }
11476
+ function detectUnassignedFields(content, file) {
11477
+ const unassignedFields = [];
11478
+ const fieldPattern = /\b(private|protected|internal|public)\s+(?:readonly\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*;/gm;
11479
+ let match;
11480
+ while ((match = fieldPattern.exec(content)) !== null) {
11481
+ const fieldType = match[2];
11482
+ const fieldName = match[3];
11483
+ const line = getLineNumber2(content, match.index);
11484
+ const assignmentPattern = new RegExp(`\\b${fieldName}\\s*=`, "g");
11485
+ const assignments = content.match(assignmentPattern) || [];
11486
+ const constructorAssignmentPattern = new RegExp(`this\\.${fieldName}\\s*=`, "g");
11487
+ const constructorAssignments = content.match(constructorAssignmentPattern) || [];
11488
+ if (assignments.length === 0 && constructorAssignments.length === 0) {
11489
+ unassignedFields.push({
11490
+ name: fieldName,
11491
+ type: fieldType,
11492
+ file,
11493
+ line,
11494
+ rule: "S3459"
11495
+ });
11496
+ }
11497
+ }
11498
+ return unassignedFields;
11499
+ }
11500
+ function formatExtendedReport(result, thresholds, unusedMembers, duplicatedStrings, unassignedFields) {
11501
+ let report = formatQualityReport(result, thresholds);
11502
+ if (unusedMembers.length > 0 || duplicatedStrings.length > 0 || unassignedFields.length > 0) {
11503
+ report += "\n\n## SonarCloud-Style Detections\n";
11504
+ if (unusedMembers.length > 0) {
11505
+ report += "\n### \u{1F50D} Unused Private Members (S1144)\n";
11506
+ report += `Found ${unusedMembers.length} unused private members:
11507
+
11508
+ `;
11509
+ for (const member of unusedMembers.slice(0, 10)) {
11510
+ report += `- \`${member.name}\` (${member.type}) in \`${member.file}:${member.line}\`
11511
+ `;
11512
+ }
11513
+ if (unusedMembers.length > 10) {
11514
+ report += `
11515
+ ... and ${unusedMembers.length - 10} more
11516
+ `;
11517
+ }
11518
+ }
11519
+ if (duplicatedStrings.length > 0) {
11520
+ report += "\n### \u{1F504} Duplicated String Literals (S1192)\n";
11521
+ report += `Found ${duplicatedStrings.length} duplicated strings (3+ occurrences):
11522
+
11523
+ `;
11524
+ for (const dup of duplicatedStrings) {
11525
+ const truncated = dup.literal.length > 50 ? dup.literal.slice(0, 47) + '..."' : dup.literal;
11526
+ report += `- ${truncated} (${dup.occurrences}x) \u2192 suggested: \`${dup.suggestedConstantName}\`
11527
+ `;
11528
+ }
11529
+ }
11530
+ if (unassignedFields.length > 0) {
11531
+ report += "\n### \u26A0\uFE0F Unassigned Fields (S3459)\n";
11532
+ report += `Found ${unassignedFields.length} fields that are never assigned:
11533
+
11534
+ `;
11535
+ for (const field of unassignedFields.slice(0, 10)) {
11536
+ report += `- \`${field.type} ${field.name}\` in \`${field.file}:${field.line}\`
11537
+ `;
11538
+ }
11539
+ if (unassignedFields.length > 10) {
11540
+ report += `
11541
+ ... and ${unassignedFields.length - 10} more
11542
+ `;
11543
+ }
11544
+ }
11545
+ }
11546
+ return report;
11547
+ }
11314
11548
 
11315
11549
  // src/tools/analyze-hierarchy-patterns.ts
11316
11550
  import path22 from "path";