@atlashub/smartstack-mcp 1.6.0 → 1.8.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
@@ -75,17 +75,17 @@ if (envLevel && ["debug", "info", "warn", "error"].includes(envLevel)) {
75
75
  }
76
76
 
77
77
  // src/config.ts
78
- import path2 from "path";
78
+ import path3 from "path";
79
79
 
80
80
  // src/utils/fs.ts
81
81
  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, path21, cause) {
85
+ constructor(message, operation, path22, cause) {
86
86
  super(message);
87
87
  this.operation = operation;
88
- this.path = path21;
88
+ this.path = path22;
89
89
  this.cause = cause;
90
90
  this.name = "FileSystemError";
91
91
  }
@@ -184,6 +184,125 @@ async function findFiles(pattern, options = {}) {
184
184
  return files;
185
185
  }
186
186
 
187
+ // src/utils/dotnet.ts
188
+ import { exec } from "child_process";
189
+ import { promisify } from "util";
190
+ import path2 from "path";
191
+ var execAsync = promisify(exec);
192
+ async function findCsprojFiles(cwd) {
193
+ return findFiles("**/*.csproj", { cwd: cwd || process.cwd() });
194
+ }
195
+ async function hasEfCore(csprojPath) {
196
+ try {
197
+ const content = await readText(csprojPath);
198
+ return content.includes("Microsoft.EntityFrameworkCore");
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+ async function findDbContextName(projectPath) {
204
+ try {
205
+ const csFiles = await findFiles("**/*.cs", { cwd: projectPath });
206
+ for (const file of csFiles) {
207
+ const content = await readText(file);
208
+ const match = content.match(/class\s+(\w+)\s*:\s*(?:\w+,\s*)*DbContext/);
209
+ if (match) {
210
+ return match[1];
211
+ }
212
+ }
213
+ return null;
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+ async function getTargetFramework(csprojPath) {
219
+ try {
220
+ const content = await readText(csprojPath);
221
+ const match = content.match(/<TargetFramework>([^<]+)<\/TargetFramework>/);
222
+ return match ? match[1] : null;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
227
+ async function detectNamespaces(csprojFiles) {
228
+ if (csprojFiles.length === 0) {
229
+ return null;
230
+ }
231
+ const projectNames = csprojFiles.map((f) => path2.basename(f, ".csproj"));
232
+ const layerSuffixes = ["Domain", "Application", "Infrastructure", "Api", "Web", "Core", "Tests"];
233
+ const baseNamespaces = [];
234
+ for (const name of projectNames) {
235
+ const parts = name.split(".");
236
+ for (let i = parts.length - 1; i >= 0; i--) {
237
+ const part = parts[i];
238
+ if (layerSuffixes.some((s) => s.toLowerCase() === part.toLowerCase())) {
239
+ const baseParts = parts.slice(0, i);
240
+ if (baseParts.length > 0) {
241
+ baseNamespaces.push(baseParts.join("."));
242
+ }
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ if (baseNamespaces.length === 0) {
248
+ const commonPrefix = findCommonPrefix(projectNames);
249
+ if (commonPrefix && commonPrefix.length > 0) {
250
+ const base = commonPrefix.replace(/\.$/, "");
251
+ if (base) {
252
+ baseNamespaces.push(base);
253
+ }
254
+ }
255
+ }
256
+ if (baseNamespaces.length === 0) {
257
+ return null;
258
+ }
259
+ const baseNamespace = getMostCommon(baseNamespaces) || baseNamespaces[0];
260
+ return {
261
+ baseNamespace,
262
+ domain: `${baseNamespace}.Domain`,
263
+ application: `${baseNamespace}.Application`,
264
+ infrastructure: `${baseNamespace}.Infrastructure`,
265
+ api: `${baseNamespace}.Api`
266
+ };
267
+ }
268
+ function findCommonPrefix(strings) {
269
+ if (strings.length === 0) return "";
270
+ if (strings.length === 1) {
271
+ const parts = strings[0].split(".");
272
+ if (parts.length > 1) {
273
+ return parts.slice(0, -1).join(".") + ".";
274
+ }
275
+ return strings[0] + ".";
276
+ }
277
+ let prefix = strings[0];
278
+ for (let i = 1; i < strings.length; i++) {
279
+ while (strings[i].indexOf(prefix) !== 0 && prefix.length > 0) {
280
+ prefix = prefix.slice(0, -1);
281
+ }
282
+ }
283
+ const lastDot = prefix.lastIndexOf(".");
284
+ if (lastDot > 0 && !prefix.endsWith(".")) {
285
+ prefix = prefix.slice(0, lastDot + 1);
286
+ }
287
+ return prefix;
288
+ }
289
+ function getMostCommon(arr) {
290
+ if (arr.length === 0) return null;
291
+ const counts = /* @__PURE__ */ new Map();
292
+ for (const item of arr) {
293
+ counts.set(item, (counts.get(item) || 0) + 1);
294
+ }
295
+ let maxCount = 0;
296
+ let mostCommon = arr[0];
297
+ for (const [item, count] of counts) {
298
+ if (count > maxCount) {
299
+ maxCount = count;
300
+ mostCommon = item;
301
+ }
302
+ }
303
+ return mostCommon;
304
+ }
305
+
187
306
  // src/config.ts
188
307
  var defaultConfig = {
189
308
  version: "1.0.0",
@@ -218,10 +337,12 @@ var defaultConfig = {
218
337
  },
219
338
  migrationFormat: "{context}_v{version}_{sequence}_{Description}",
220
339
  namespaces: {
221
- domain: "SmartStack.Domain",
222
- application: "SmartStack.Application",
223
- infrastructure: "SmartStack.Infrastructure",
224
- api: "SmartStack.Api"
340
+ // Empty = auto-detect from .csproj files
341
+ // Can be overridden in config file for custom namespaces
342
+ domain: "",
343
+ application: "",
344
+ infrastructure: "",
345
+ api: ""
225
346
  },
226
347
  servicePattern: {
227
348
  interface: "I{Name}Service",
@@ -268,7 +389,7 @@ async function getConfig() {
268
389
  if (cachedConfig) {
269
390
  return cachedConfig;
270
391
  }
271
- const configPath = path2.join(process.cwd(), "config", "default-config.json");
392
+ const configPath = path3.join(process.cwd(), "config", "default-config.json");
272
393
  if (await fileExists(configPath)) {
273
394
  try {
274
395
  const fileConfig = await readJson(configPath);
@@ -285,6 +406,32 @@ async function getConfig() {
285
406
  cachedConfig.smartstack.projectPath = resolveProjectPath(
286
407
  cachedConfig.smartstack.projectPath
287
408
  );
409
+ const namespacesEmpty = !cachedConfig.conventions.namespaces.domain || !cachedConfig.conventions.namespaces.application || !cachedConfig.conventions.namespaces.infrastructure || !cachedConfig.conventions.namespaces.api;
410
+ if (namespacesEmpty && cachedConfig.smartstack.projectPath) {
411
+ try {
412
+ const csprojFiles = await findCsprojFiles(cachedConfig.smartstack.projectPath);
413
+ const detected = await detectNamespaces(csprojFiles);
414
+ if (detected) {
415
+ if (!cachedConfig.conventions.namespaces.domain) {
416
+ cachedConfig.conventions.namespaces.domain = detected.domain;
417
+ }
418
+ if (!cachedConfig.conventions.namespaces.application) {
419
+ cachedConfig.conventions.namespaces.application = detected.application;
420
+ }
421
+ if (!cachedConfig.conventions.namespaces.infrastructure) {
422
+ cachedConfig.conventions.namespaces.infrastructure = detected.infrastructure;
423
+ }
424
+ if (!cachedConfig.conventions.namespaces.api) {
425
+ cachedConfig.conventions.namespaces.api = detected.api;
426
+ }
427
+ logger.info("Namespaces auto-detected from project", { baseNamespace: detected.baseNamespace });
428
+ } else {
429
+ logger.warn("Could not auto-detect namespaces from .csproj files. Configure manually in config file.");
430
+ }
431
+ } catch (error) {
432
+ logger.warn("Failed to auto-detect namespaces", { error });
433
+ }
434
+ }
288
435
  if (process.env.SMARTSTACK_API_URL) {
289
436
  cachedConfig.smartstack.apiUrl = process.env.SMARTSTACK_API_URL;
290
437
  }
@@ -492,6 +639,7 @@ var SuggestTestScenariosInputSchema = z.object({
492
639
  depth: z.enum(["basic", "comprehensive", "security-focused"]).default("comprehensive").describe("Depth of analysis")
493
640
  });
494
641
  var ScaffoldApiClientInputSchema = z.object({
642
+ path: z.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
495
643
  navRoute: z.string().min(1).describe('NavRoute path (e.g., "platform.administration.users")'),
496
644
  name: z.string().min(1).describe('Entity name in PascalCase (e.g., "User", "Order")'),
497
645
  methods: z.array(z.enum(["getAll", "getById", "create", "update", "delete", "search", "export"])).default(["getAll", "getById", "create", "update", "delete"]).describe("API methods to generate"),
@@ -503,6 +651,7 @@ var ScaffoldApiClientInputSchema = z.object({
503
651
  }).optional()
504
652
  });
505
653
  var ScaffoldRoutesInputSchema = z.object({
654
+ path: z.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
506
655
  source: z.enum(["controllers", "navigation", "manual"]).default("controllers").describe("Source for route discovery: controllers (scan NavRoute attributes), navigation (from DB), manual (from config)"),
507
656
  scope: z.enum(["all", "platform", "business", "extensions"]).default("all").describe("Scope of routes to generate"),
508
657
  options: z.object({
@@ -514,6 +663,7 @@ var ScaffoldRoutesInputSchema = z.object({
514
663
  }).optional()
515
664
  });
516
665
  var ValidateFrontendRoutesInputSchema = z.object({
666
+ path: z.string().optional().describe("Path to SmartStack project root (defaults to configured project path)"),
517
667
  scope: z.enum(["api-clients", "routes", "registry", "all"]).default("all").describe("Scope of validation"),
518
668
  options: z.object({
519
669
  fix: z.boolean().default(false).describe("Auto-fix minor issues"),
@@ -522,13 +672,13 @@ var ValidateFrontendRoutesInputSchema = z.object({
522
672
  });
523
673
 
524
674
  // src/lib/detector.ts
525
- import path4 from "path";
675
+ import path5 from "path";
526
676
 
527
677
  // src/utils/git.ts
528
- import { exec } from "child_process";
529
- import { promisify } from "util";
530
- import path3 from "path";
531
- var execAsync = promisify(exec);
678
+ import { exec as exec2 } from "child_process";
679
+ import { promisify as promisify2 } from "util";
680
+ import path4 from "path";
681
+ var execAsync2 = promisify2(exec2);
532
682
  var GitError = class extends Error {
533
683
  constructor(message, command, cwd, cause) {
534
684
  super(message);
@@ -541,7 +691,7 @@ var GitError = class extends Error {
541
691
  async function git(command, cwd) {
542
692
  const options = cwd ? { cwd, maxBuffer: 10 * 1024 * 1024 } : { maxBuffer: 10 * 1024 * 1024 };
543
693
  try {
544
- const { stdout } = await execAsync(`git ${command}`, options);
694
+ const { stdout } = await execAsync2(`git ${command}`, options);
545
695
  return stdout.trim();
546
696
  } catch (error) {
547
697
  const err = error instanceof Error ? error : new Error(String(error));
@@ -555,7 +705,7 @@ async function git(command, cwd) {
555
705
  }
556
706
  }
557
707
  async function isGitRepo(cwd) {
558
- const gitDir = path3.join(cwd || process.cwd(), ".git");
708
+ const gitDir = path4.join(cwd || process.cwd(), ".git");
559
709
  return directoryExists(gitDir);
560
710
  }
561
711
  async function getCurrentBranch(cwd) {
@@ -589,51 +739,37 @@ async function getDiff(fromBranch, toBranch, filePath, cwd) {
589
739
  }
590
740
  }
591
741
 
592
- // src/utils/dotnet.ts
593
- import { exec as exec2 } from "child_process";
594
- import { promisify as promisify2 } from "util";
595
- var execAsync2 = promisify2(exec2);
596
- async function findCsprojFiles(cwd) {
597
- return findFiles("**/*.csproj", { cwd: cwd || process.cwd() });
598
- }
599
- async function hasEfCore(csprojPath) {
600
- try {
601
- const content = await readText(csprojPath);
602
- return content.includes("Microsoft.EntityFrameworkCore");
603
- } catch {
604
- return false;
742
+ // src/lib/detector.ts
743
+ import { glob as glob2 } from "glob";
744
+ async function findWebProjectFolder(projectPath) {
745
+ const webSubfolders = await glob2("web/*/", { cwd: projectPath, absolute: true });
746
+ for (const folder of webSubfolders) {
747
+ const packageJsonPath = path5.join(folder, "package.json");
748
+ if (await fileExists(packageJsonPath)) {
749
+ return folder;
750
+ }
605
751
  }
606
- }
607
- async function findDbContextName(projectPath) {
608
- try {
609
- const csFiles = await findFiles("**/*.cs", { cwd: projectPath });
610
- for (const file of csFiles) {
611
- const content = await readText(file);
612
- const match = content.match(/class\s+(\w+)\s*:\s*(?:\w+,\s*)*DbContext/);
613
- if (match) {
614
- return match[1];
615
- }
752
+ const webDirect = path5.join(projectPath, "web");
753
+ if (await fileExists(path5.join(webDirect, "package.json"))) {
754
+ return webDirect;
755
+ }
756
+ const alternativeFolders = ["client", "frontend", "ui"];
757
+ for (const folderName of alternativeFolders) {
758
+ const altPath = path5.join(projectPath, folderName);
759
+ if (await fileExists(path5.join(altPath, "package.json"))) {
760
+ return altPath;
616
761
  }
617
- return null;
618
- } catch {
619
- return null;
620
762
  }
621
- }
622
- async function getTargetFramework(csprojPath) {
623
- try {
624
- const content = await readText(csprojPath);
625
- const match = content.match(/<TargetFramework>([^<]+)<\/TargetFramework>/);
626
- return match ? match[1] : null;
627
- } catch {
628
- return null;
763
+ const srcWeb = path5.join(projectPath, "src", "web");
764
+ if (await fileExists(path5.join(srcWeb, "package.json"))) {
765
+ return srcWeb;
629
766
  }
767
+ return null;
630
768
  }
631
-
632
- // src/lib/detector.ts
633
769
  async function detectProject(projectPath) {
634
770
  logger.debug("Detecting project info", { path: projectPath });
635
771
  const info = {
636
- name: path4.basename(projectPath),
772
+ name: path5.basename(projectPath),
637
773
  version: "0.0.0",
638
774
  isGitRepo: false,
639
775
  hasDotNet: false,
@@ -655,22 +791,25 @@ async function detectProject(projectPath) {
655
791
  for (const csproj of info.csprojFiles) {
656
792
  if (await hasEfCore(csproj)) {
657
793
  info.hasEfCore = true;
658
- info.dbContextName = await findDbContextName(path4.dirname(csproj)) || void 0;
794
+ info.dbContextName = await findDbContextName(path5.dirname(csproj)) || void 0;
659
795
  break;
660
796
  }
661
797
  }
662
798
  }
663
- const packageJsonPath = path4.join(projectPath, "web", "smartstack-web", "package.json");
664
- if (await fileExists(packageJsonPath)) {
665
- try {
666
- const packageJson = JSON.parse(await readText(packageJsonPath));
667
- info.hasReact = !!packageJson.dependencies?.react;
668
- info.version = packageJson.version || info.version;
669
- } catch {
799
+ const webFolder = await findWebProjectFolder(projectPath);
800
+ if (webFolder) {
801
+ const packageJsonPath = path5.join(webFolder, "package.json");
802
+ if (await fileExists(packageJsonPath)) {
803
+ try {
804
+ const packageJson = JSON.parse(await readText(packageJsonPath));
805
+ info.hasReact = !!packageJson.dependencies?.react;
806
+ info.version = packageJson.version || info.version;
807
+ } catch {
808
+ }
670
809
  }
671
810
  }
672
811
  if (info.csprojFiles.length > 0) {
673
- const mainCsproj = info.csprojFiles.find((f) => f.includes("SmartStack.Api")) || info.csprojFiles[0];
812
+ const mainCsproj = info.csprojFiles.find((f) => /\.Api\.csproj$/i.test(f)) || info.csprojFiles.find((f) => /Api\.csproj$/i.test(f)) || info.csprojFiles[0];
674
813
  const targetFramework = await getTargetFramework(mainCsproj);
675
814
  if (targetFramework) {
676
815
  logger.debug("Target framework detected", { framework: targetFramework });
@@ -683,8 +822,8 @@ async function findSmartStackStructure(projectPath) {
683
822
  const structure = { root: projectPath };
684
823
  const csprojFiles = await findCsprojFiles(projectPath);
685
824
  for (const csproj of csprojFiles) {
686
- const projectName = path4.basename(csproj, ".csproj").toLowerCase();
687
- const projectDir = path4.dirname(csproj);
825
+ const projectName = path5.basename(csproj, ".csproj").toLowerCase();
826
+ const projectDir = path5.dirname(csproj);
688
827
  if (projectName.includes("domain")) {
689
828
  structure.domain = projectDir;
690
829
  } else if (projectName.includes("application")) {
@@ -696,14 +835,14 @@ async function findSmartStackStructure(projectPath) {
696
835
  }
697
836
  }
698
837
  if (structure.infrastructure) {
699
- const migrationsPath = path4.join(structure.infrastructure, "Persistence", "Migrations");
838
+ const migrationsPath = path5.join(structure.infrastructure, "Persistence", "Migrations");
700
839
  if (await directoryExists(migrationsPath)) {
701
840
  structure.migrations = migrationsPath;
702
841
  }
703
842
  }
704
- const webPath = path4.join(projectPath, "web", "smartstack-web");
705
- if (await directoryExists(webPath)) {
706
- structure.web = webPath;
843
+ const webFolder = await findWebProjectFolder(projectPath);
844
+ if (webFolder) {
845
+ structure.web = webFolder;
707
846
  }
708
847
  return structure;
709
848
  }
@@ -724,7 +863,7 @@ async function findControllerFiles(apiPath) {
724
863
  }
725
864
 
726
865
  // src/tools/validate-conventions.ts
727
- import path5 from "path";
866
+ import path6 from "path";
728
867
  var validateConventionsTool = {
729
868
  name: "validate_conventions",
730
869
  description: "Validate AtlasHub/SmartStack conventions: SQL schemas (core/extensions), domain table prefixes (auth_, nav_, ai_, etc.), migration naming ({context}_v{version}_{sequence}_*), service interfaces (I*Service), namespace structure, controller routes (NavRoute)",
@@ -813,7 +952,7 @@ async function validateTablePrefixes(structure, config, result) {
813
952
  type: "error",
814
953
  category: "tables",
815
954
  message: `Table "${tableName}" uses invalid schema "${schemaName}"`,
816
- file: path5.relative(structure.root, file),
955
+ file: path6.relative(structure.root, file),
817
956
  suggestion: `Use schema "${config.conventions.schemas.platform}" for SmartStack tables or "${config.conventions.schemas.extensions}" for client extensions`
818
957
  });
819
958
  }
@@ -823,7 +962,7 @@ async function validateTablePrefixes(structure, config, result) {
823
962
  type: "warning",
824
963
  category: "tables",
825
964
  message: `Table "${tableName}" does not use a standard domain prefix`,
826
- file: path5.relative(structure.root, file),
965
+ file: path6.relative(structure.root, file),
827
966
  suggestion: `Consider using a domain prefix: ${validPrefixes.slice(0, 5).join(", ")}, etc.`
828
967
  });
829
968
  }
@@ -838,7 +977,7 @@ async function validateTablePrefixes(structure, config, result) {
838
977
  type: "error",
839
978
  category: "tables",
840
979
  message: `Table "${tableName}" uses unknown schema constant "${schemaConstant}"`,
841
- file: path5.relative(structure.root, file),
980
+ file: path6.relative(structure.root, file),
842
981
  suggestion: `Use SchemaConstants.Core for SmartStack tables or SchemaConstants.Extensions for client extensions`
843
982
  });
844
983
  }
@@ -848,7 +987,7 @@ async function validateTablePrefixes(structure, config, result) {
848
987
  type: "warning",
849
988
  category: "tables",
850
989
  message: `Table "${tableName}" does not use a standard domain prefix`,
851
- file: path5.relative(structure.root, file),
990
+ file: path6.relative(structure.root, file),
852
991
  suggestion: `Consider using a domain prefix: ${validPrefixes.slice(0, 5).join(", ")}, etc.`
853
992
  });
854
993
  }
@@ -861,7 +1000,7 @@ async function validateTablePrefixes(structure, config, result) {
861
1000
  type: "error",
862
1001
  category: "tables",
863
1002
  message: `Table "${tableName}" is missing schema specification`,
864
- file: path5.relative(structure.root, file),
1003
+ file: path6.relative(structure.root, file),
865
1004
  suggestion: `Add schema: .ToTable("${tableName}", SchemaConstants.Core)`
866
1005
  });
867
1006
  }
@@ -881,7 +1020,7 @@ async function validateMigrationNaming(structure, _config, result) {
881
1020
  const migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
882
1021
  const designerPattern = /\.Designer\.cs$/;
883
1022
  for (const file of migrationFiles) {
884
- const fileName = path5.basename(file);
1023
+ const fileName = path6.basename(file);
885
1024
  if (designerPattern.test(fileName) || fileName.includes("ModelSnapshot")) {
886
1025
  continue;
887
1026
  }
@@ -890,12 +1029,12 @@ async function validateMigrationNaming(structure, _config, result) {
890
1029
  type: "error",
891
1030
  category: "migrations",
892
1031
  message: `Migration "${fileName}" does not follow naming convention`,
893
- file: path5.relative(structure.root, file),
1032
+ file: path6.relative(structure.root, file),
894
1033
  suggestion: `Expected format: {context}_v{version}_{sequence}_{Description}.cs (e.g., core_v1.0.0_001_CreateAuthUsers.cs)`
895
1034
  });
896
1035
  }
897
1036
  }
898
- const orderedMigrations = migrationFiles.map((f) => path5.basename(f)).filter((f) => migrationPattern.test(f) && !f.includes("Designer")).sort();
1037
+ const orderedMigrations = migrationFiles.map((f) => path6.basename(f)).filter((f) => migrationPattern.test(f) && !f.includes("Designer")).sort();
899
1038
  for (let i = 1; i < orderedMigrations.length; i++) {
900
1039
  const prev = orderedMigrations[i - 1];
901
1040
  const curr = orderedMigrations[i];
@@ -928,7 +1067,7 @@ async function validateServiceInterfaces(structure, _config, result) {
928
1067
  });
929
1068
  for (const file of serviceFiles) {
930
1069
  const content = await readText(file);
931
- const fileName = path5.basename(file, ".cs");
1070
+ const fileName = path6.basename(file, ".cs");
932
1071
  if (fileName.startsWith("I")) continue;
933
1072
  const expectedInterface = `I${fileName}`;
934
1073
  const interfacePattern = new RegExp(`:\\s*${expectedInterface}\\b`);
@@ -939,7 +1078,7 @@ async function validateServiceInterfaces(structure, _config, result) {
939
1078
  type: "warning",
940
1079
  category: "services",
941
1080
  message: `Service "${fileName}" should implement "${expectedInterface}"`,
942
- file: path5.relative(structure.root, file),
1081
+ file: path6.relative(structure.root, file),
943
1082
  suggestion: `Create interface ${expectedInterface} and implement it`
944
1083
  });
945
1084
  }
@@ -966,7 +1105,7 @@ async function validateNamespaces(structure, config, result) {
966
1105
  type: "error",
967
1106
  category: "namespaces",
968
1107
  message: `${layer.name} file has incorrect namespace "${namespace}"`,
969
- file: path5.relative(structure.root, file),
1108
+ file: path6.relative(structure.root, file),
970
1109
  suggestion: `Should start with "${layer.expected}"`
971
1110
  });
972
1111
  }
@@ -986,7 +1125,7 @@ async function validateEntities(structure, _config, result) {
986
1125
  const entityFiles = await findFiles("**/*.cs", { cwd: structure.domain });
987
1126
  for (const file of entityFiles) {
988
1127
  const content = await readText(file);
989
- const fileName = path5.basename(file, ".cs");
1128
+ const fileName = path6.basename(file, ".cs");
990
1129
  if (fileName.endsWith("Dto") || fileName.endsWith("Command") || fileName.endsWith("Query") || fileName.endsWith("Handler") || fileName.endsWith("Validator") || fileName.endsWith("Exception") || fileName.startsWith("I")) {
991
1130
  continue;
992
1131
  }
@@ -1005,7 +1144,7 @@ async function validateEntities(structure, _config, result) {
1005
1144
  type: "warning",
1006
1145
  category: "entities",
1007
1146
  message: `Entity "${entityName}" inherits BaseEntity but doesn't implement ITenantEntity`,
1008
- file: path5.relative(structure.root, file),
1147
+ file: path6.relative(structure.root, file),
1009
1148
  suggestion: "Add ITenantEntity interface for multi-tenant support, or use SystemEntity for platform-level entities"
1010
1149
  });
1011
1150
  }
@@ -1014,7 +1153,7 @@ async function validateEntities(structure, _config, result) {
1014
1153
  type: "warning",
1015
1154
  category: "entities",
1016
1155
  message: `Entity "${entityName}" is missing private constructor for EF Core`,
1017
- file: path5.relative(structure.root, file),
1156
+ file: path6.relative(structure.root, file),
1018
1157
  suggestion: `Add: private ${entityName}() { }`
1019
1158
  });
1020
1159
  }
@@ -1023,7 +1162,7 @@ async function validateEntities(structure, _config, result) {
1023
1162
  type: "warning",
1024
1163
  category: "entities",
1025
1164
  message: `Entity "${entityName}" is missing factory method`,
1026
- file: path5.relative(structure.root, file),
1165
+ file: path6.relative(structure.root, file),
1027
1166
  suggestion: `Add factory method: public static ${entityName} Create(...)`
1028
1167
  });
1029
1168
  }
@@ -1062,7 +1201,7 @@ async function validateTenantAwareness(structure, _config, result) {
1062
1201
  type: "error",
1063
1202
  category: "tenants",
1064
1203
  message: `Entity "${entityName}" implements ITenantEntity but is missing TenantId property`,
1065
- file: path5.relative(structure.root, file),
1204
+ file: path6.relative(structure.root, file),
1066
1205
  suggestion: "Add: public Guid TenantId { get; private set; }"
1067
1206
  });
1068
1207
  }
@@ -1072,7 +1211,7 @@ async function validateTenantAwareness(structure, _config, result) {
1072
1211
  type: "error",
1073
1212
  category: "tenants",
1074
1213
  message: `Entity "${entityName}" implements ITenantEntity but Create() doesn't require tenantId`,
1075
- file: path5.relative(structure.root, file),
1214
+ file: path6.relative(structure.root, file),
1076
1215
  suggestion: "Add tenantId as first parameter: Create(Guid tenantId, ...)"
1077
1216
  });
1078
1217
  }
@@ -1084,7 +1223,7 @@ async function validateTenantAwareness(structure, _config, result) {
1084
1223
  type: "error",
1085
1224
  category: "tenants",
1086
1225
  message: `System entity "${entityName}" should not have TenantId`,
1087
- file: path5.relative(structure.root, file),
1226
+ file: path6.relative(structure.root, file),
1088
1227
  suggestion: "Remove TenantId from system entities"
1089
1228
  });
1090
1229
  }
@@ -1095,7 +1234,7 @@ async function validateTenantAwareness(structure, _config, result) {
1095
1234
  type: "warning",
1096
1235
  category: "tenants",
1097
1236
  message: `Entity "${entityName}" has TenantId but doesn't implement ITenantEntity`,
1098
- file: path5.relative(structure.root, file),
1237
+ file: path6.relative(structure.root, file),
1099
1238
  suggestion: "Add ITenantEntity interface for explicit tenant-awareness"
1100
1239
  });
1101
1240
  }
@@ -1150,7 +1289,7 @@ async function validateControllerRoutes(structure, _config, result) {
1150
1289
  let systemControllerCount = 0;
1151
1290
  for (const file of controllerFiles) {
1152
1291
  const content = await readText(file);
1153
- const fileName = path5.basename(file, ".cs");
1292
+ const fileName = path6.basename(file, ".cs");
1154
1293
  if (systemControllers.includes(fileName)) {
1155
1294
  systemControllerCount++;
1156
1295
  continue;
@@ -1168,7 +1307,7 @@ async function validateControllerRoutes(structure, _config, result) {
1168
1307
  type: "warning",
1169
1308
  category: "controllers",
1170
1309
  message: `Controller "${fileName}" has NavRoute with insufficient depth: "${routePath}"`,
1171
- file: path5.relative(structure.root, file),
1310
+ file: path6.relative(structure.root, file),
1172
1311
  suggestion: 'NavRoute should have at least 2 levels: "context.application" (e.g., "platform.administration")'
1173
1312
  });
1174
1313
  }
@@ -1178,7 +1317,7 @@ async function validateControllerRoutes(structure, _config, result) {
1178
1317
  type: "error",
1179
1318
  category: "controllers",
1180
1319
  message: `Controller "${fileName}" has NavRoute with uppercase characters: "${routePath}"`,
1181
- file: path5.relative(structure.root, file),
1320
+ file: path6.relative(structure.root, file),
1182
1321
  suggestion: 'NavRoute paths must be lowercase (e.g., "platform.administration.users")'
1183
1322
  });
1184
1323
  }
@@ -1189,7 +1328,7 @@ async function validateControllerRoutes(structure, _config, result) {
1189
1328
  type: "warning",
1190
1329
  category: "controllers",
1191
1330
  message: `Controller "${fileName}" uses hardcoded Route instead of NavRoute`,
1192
- file: path5.relative(structure.root, file),
1331
+ file: path6.relative(structure.root, file),
1193
1332
  suggestion: 'Use [NavRoute("context.application.module")] for navigation-based routing'
1194
1333
  });
1195
1334
  }
@@ -1257,7 +1396,7 @@ function formatResult(result) {
1257
1396
  }
1258
1397
 
1259
1398
  // src/tools/check-migrations.ts
1260
- import path6 from "path";
1399
+ import path7 from "path";
1261
1400
  var checkMigrationsTool = {
1262
1401
  name: "check_migrations",
1263
1402
  description: "Analyze EF Core migrations for conflicts, ordering issues, and ModelSnapshot discrepancies between branches",
@@ -1316,7 +1455,7 @@ async function parseMigrations(migrationsPath, rootPath) {
1316
1455
  const migrations = [];
1317
1456
  const pattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d{3})_(.+)\.cs$/;
1318
1457
  for (const file of files) {
1319
- const fileName = path6.basename(file);
1458
+ const fileName = path7.basename(file);
1320
1459
  if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
1321
1460
  continue;
1322
1461
  }
@@ -1331,7 +1470,7 @@ async function parseMigrations(migrationsPath, rootPath) {
1331
1470
  sequence: match[3],
1332
1471
  // Sequence number (001, 002, etc.)
1333
1472
  description: match[4],
1334
- file: path6.relative(rootPath, file),
1473
+ file: path7.relative(rootPath, file),
1335
1474
  applied: true
1336
1475
  // We'd need DB connection to check this
1337
1476
  });
@@ -1342,7 +1481,7 @@ async function parseMigrations(migrationsPath, rootPath) {
1342
1481
  version: "0.0.0",
1343
1482
  sequence: "000",
1344
1483
  description: fileName.replace(".cs", ""),
1345
- file: path6.relative(rootPath, file),
1484
+ file: path7.relative(rootPath, file),
1346
1485
  applied: true
1347
1486
  });
1348
1487
  }
@@ -1432,10 +1571,10 @@ function checkChronologicalOrder(result) {
1432
1571
  }
1433
1572
  async function checkBranchConflicts(result, structure, currentBranch, compareBranch, projectPath) {
1434
1573
  if (!structure.migrations) return;
1435
- const migrationsRelPath = path6.relative(projectPath, structure.migrations).replace(/\\/g, "/");
1574
+ const migrationsRelPath = path7.relative(projectPath, structure.migrations).replace(/\\/g, "/");
1436
1575
  const snapshotFiles = await findFiles("*ModelSnapshot.cs", { cwd: structure.migrations });
1437
1576
  if (snapshotFiles.length > 0) {
1438
- const snapshotRelPath = path6.relative(projectPath, snapshotFiles[0]).replace(/\\/g, "/");
1577
+ const snapshotRelPath = path7.relative(projectPath, snapshotFiles[0]).replace(/\\/g, "/");
1439
1578
  const currentSnapshot = await readText(snapshotFiles[0]);
1440
1579
  const compareSnapshot = await getFileFromBranch(compareBranch, snapshotRelPath, projectPath);
1441
1580
  if (compareSnapshot && currentSnapshot !== compareSnapshot) {
@@ -1475,7 +1614,7 @@ async function checkModelSnapshot(result, structure) {
1475
1614
  result.conflicts.push({
1476
1615
  type: "snapshot",
1477
1616
  description: "Multiple ModelSnapshot files found",
1478
- files: snapshotFiles.map((f) => path6.relative(structure.root, f)),
1617
+ files: snapshotFiles.map((f) => path7.relative(structure.root, f)),
1479
1618
  resolution: "Remove duplicate snapshots, keep only one per DbContext"
1480
1619
  });
1481
1620
  }
@@ -1562,7 +1701,7 @@ function formatResult2(result, currentBranch, compareBranch) {
1562
1701
 
1563
1702
  // src/tools/scaffold-extension.ts
1564
1703
  import Handlebars from "handlebars";
1565
- import path7 from "path";
1704
+ import path8 from "path";
1566
1705
  var scaffoldExtensionTool = {
1567
1706
  name: "scaffold_extension",
1568
1707
  description: "Generate code to extend SmartStack: feature (full-stack), entity, service, controller, component, dto, validator, repository, or test",
@@ -1891,9 +2030,9 @@ services.AddScoped<I{{name}}Service, {{name}}Service>();
1891
2030
  const diContent = Handlebars.compile(diTemplate)(context);
1892
2031
  const projectRoot = config.smartstack.projectPath;
1893
2032
  const basePath = structure.application || projectRoot;
1894
- const servicesPath = path7.join(basePath, "Services");
1895
- const interfacePath = path7.join(servicesPath, `I${name}Service.cs`);
1896
- const implementationPath = path7.join(servicesPath, `${name}Service.cs`);
2033
+ const servicesPath = path8.join(basePath, "Services");
2034
+ const interfacePath = path8.join(servicesPath, `I${name}Service.cs`);
2035
+ const implementationPath = path8.join(servicesPath, `${name}Service.cs`);
1897
2036
  validatePathSecurity(interfacePath, projectRoot);
1898
2037
  validatePathSecurity(implementationPath, projectRoot);
1899
2038
  if (!dryRun) {
@@ -2137,15 +2276,15 @@ public class {{name}}Configuration : IEntityTypeConfiguration<{{name}}>
2137
2276
  const entityContent = Handlebars.compile(entityTemplate)(context);
2138
2277
  const configContent = Handlebars.compile(configTemplate)(context);
2139
2278
  const projectRoot = config.smartstack.projectPath;
2140
- const domainPath = structure.domain || path7.join(projectRoot, "Domain");
2141
- const infraPath = structure.infrastructure || path7.join(projectRoot, "Infrastructure");
2142
- const entityFilePath = path7.join(domainPath, `${name}.cs`);
2143
- const configFilePath = path7.join(infraPath, "Persistence", "Configurations", `${name}Configuration.cs`);
2279
+ const domainPath = structure.domain || path8.join(projectRoot, "Domain");
2280
+ const infraPath = structure.infrastructure || path8.join(projectRoot, "Infrastructure");
2281
+ const entityFilePath = path8.join(domainPath, `${name}.cs`);
2282
+ const configFilePath = path8.join(infraPath, "Persistence", "Configurations", `${name}Configuration.cs`);
2144
2283
  validatePathSecurity(entityFilePath, projectRoot);
2145
2284
  validatePathSecurity(configFilePath, projectRoot);
2146
2285
  if (!dryRun) {
2147
2286
  await ensureDirectory(domainPath);
2148
- await ensureDirectory(path7.join(infraPath, "Persistence", "Configurations"));
2287
+ await ensureDirectory(path8.join(infraPath, "Persistence", "Configurations"));
2149
2288
  await writeText(entityFilePath, entityContent);
2150
2289
  await writeText(configFilePath, configContent);
2151
2290
  }
@@ -2259,9 +2398,9 @@ public record Update{{name}}Request();
2259
2398
  };
2260
2399
  const controllerContent = Handlebars.compile(controllerTemplate)(context);
2261
2400
  const projectRoot = config.smartstack.projectPath;
2262
- const apiPath = structure.api || path7.join(projectRoot, "Api");
2263
- const controllersPath = path7.join(apiPath, "Controllers");
2264
- const controllerFilePath = path7.join(controllersPath, `${name}Controller.cs`);
2401
+ const apiPath = structure.api || path8.join(projectRoot, "Api");
2402
+ const controllersPath = path8.join(apiPath, "Controllers");
2403
+ const controllerFilePath = path8.join(controllersPath, `${name}Controller.cs`);
2265
2404
  validatePathSecurity(controllerFilePath, projectRoot);
2266
2405
  if (!dryRun) {
2267
2406
  await ensureDirectory(controllersPath);
@@ -2441,11 +2580,11 @@ export function use{{name}}(options: Use{{name}}Options = {}) {
2441
2580
  const componentContent = Handlebars.compile(componentTemplate)(context);
2442
2581
  const hookContent = Handlebars.compile(hookTemplate)(context);
2443
2582
  const projectRoot = config.smartstack.projectPath;
2444
- const webPath = structure.web || path7.join(projectRoot, "web", "smartstack-web");
2445
- const componentsPath = options?.outputPath || path7.join(webPath, "src", "components");
2446
- const hooksPath = path7.join(webPath, "src", "hooks");
2447
- const componentFilePath = path7.join(componentsPath, `${name}.tsx`);
2448
- const hookFilePath = path7.join(hooksPath, `use${name}.ts`);
2583
+ const webPath = structure.web || path8.join(projectRoot, "web");
2584
+ const componentsPath = options?.outputPath || path8.join(webPath, "src", "components");
2585
+ const hooksPath = path8.join(webPath, "src", "hooks");
2586
+ const componentFilePath = path8.join(componentsPath, `${name}.tsx`);
2587
+ const hookFilePath = path8.join(hooksPath, `use${name}.ts`);
2449
2588
  validatePathSecurity(componentFilePath, projectRoot);
2450
2589
  validatePathSecurity(hookFilePath, projectRoot);
2451
2590
  if (!dryRun) {
@@ -2570,8 +2709,8 @@ public class {{name}}ServiceTests
2570
2709
  isSystemEntity
2571
2710
  };
2572
2711
  const testContent = Handlebars.compile(serviceTestTemplate2)(context);
2573
- const testsPath = structure.application ? path7.join(path7.dirname(structure.application), `${path7.basename(structure.application)}.Tests`, "Services") : path7.join(config.smartstack.projectPath, "Application.Tests", "Services");
2574
- const testFilePath = path7.join(testsPath, `${name}ServiceTests.cs`);
2712
+ const testsPath = structure.application ? path8.join(path8.dirname(structure.application), `${path8.basename(structure.application)}.Tests`, "Services") : path8.join(config.smartstack.projectPath, "Application.Tests", "Services");
2713
+ const testFilePath = path8.join(testsPath, `${name}ServiceTests.cs`);
2575
2714
  if (!dryRun) {
2576
2715
  await ensureDirectory(testsPath);
2577
2716
  await writeText(testFilePath, testContent);
@@ -2703,10 +2842,10 @@ public record Update{{name}}Dto
2703
2842
  const createContent = Handlebars.compile(createDtoTemplate)(context);
2704
2843
  const updateContent = Handlebars.compile(updateDtoTemplate)(context);
2705
2844
  const basePath = structure.application || config.smartstack.projectPath;
2706
- const dtosPath = path7.join(basePath, "DTOs", name);
2707
- const responseFilePath = path7.join(dtosPath, `${name}ResponseDto.cs`);
2708
- const createFilePath = path7.join(dtosPath, `Create${name}Dto.cs`);
2709
- const updateFilePath = path7.join(dtosPath, `Update${name}Dto.cs`);
2845
+ const dtosPath = path8.join(basePath, "DTOs", name);
2846
+ const responseFilePath = path8.join(dtosPath, `${name}ResponseDto.cs`);
2847
+ const createFilePath = path8.join(dtosPath, `Create${name}Dto.cs`);
2848
+ const updateFilePath = path8.join(dtosPath, `Update${name}Dto.cs`);
2710
2849
  if (!dryRun) {
2711
2850
  await ensureDirectory(dtosPath);
2712
2851
  await writeText(responseFilePath, responseContent);
@@ -2800,9 +2939,9 @@ public class Update{{name}}DtoValidator : AbstractValidator<Update{{name}}Dto>
2800
2939
  const createValidatorContent = Handlebars.compile(createValidatorTemplate)(context);
2801
2940
  const updateValidatorContent = Handlebars.compile(updateValidatorTemplate)(context);
2802
2941
  const basePath = structure.application || config.smartstack.projectPath;
2803
- const validatorsPath = path7.join(basePath, "Validators");
2804
- const createValidatorFilePath = path7.join(validatorsPath, `Create${name}DtoValidator.cs`);
2805
- const updateValidatorFilePath = path7.join(validatorsPath, `Update${name}DtoValidator.cs`);
2942
+ const validatorsPath = path8.join(basePath, "Validators");
2943
+ const createValidatorFilePath = path8.join(validatorsPath, `Create${name}DtoValidator.cs`);
2944
+ const updateValidatorFilePath = path8.join(validatorsPath, `Update${name}DtoValidator.cs`);
2806
2945
  if (!dryRun) {
2807
2946
  await ensureDirectory(validatorsPath);
2808
2947
  await writeText(createValidatorFilePath, createValidatorContent);
@@ -2935,12 +3074,12 @@ public class {{name}}Repository : I{{name}}Repository
2935
3074
  const interfaceContent = Handlebars.compile(interfaceTemplate)(context);
2936
3075
  const implementationContent = Handlebars.compile(implementationTemplate)(context);
2937
3076
  const appPath = structure.application || config.smartstack.projectPath;
2938
- const infraPath = structure.infrastructure || path7.join(config.smartstack.projectPath, "Infrastructure");
2939
- const interfaceFilePath = path7.join(appPath, "Repositories", `I${name}Repository.cs`);
2940
- const implementationFilePath = path7.join(infraPath, "Repositories", `${name}Repository.cs`);
3077
+ const infraPath = structure.infrastructure || path8.join(config.smartstack.projectPath, "Infrastructure");
3078
+ const interfaceFilePath = path8.join(appPath, "Repositories", `I${name}Repository.cs`);
3079
+ const implementationFilePath = path8.join(infraPath, "Repositories", `${name}Repository.cs`);
2941
3080
  if (!dryRun) {
2942
- await ensureDirectory(path7.join(appPath, "Repositories"));
2943
- await ensureDirectory(path7.join(infraPath, "Repositories"));
3081
+ await ensureDirectory(path8.join(appPath, "Repositories"));
3082
+ await ensureDirectory(path8.join(infraPath, "Repositories"));
2944
3083
  await writeText(interfaceFilePath, interfaceContent);
2945
3084
  await writeText(implementationFilePath, implementationContent);
2946
3085
  }
@@ -2965,7 +3104,7 @@ function formatResult3(result, type, name, dryRun = false) {
2965
3104
  lines.push(dryRun ? "## \u{1F4C4} Files to Generate" : "## \u2705 Files Generated");
2966
3105
  lines.push("");
2967
3106
  for (const file of result.files) {
2968
- lines.push(`### ${file.type === "created" ? "\u{1F4C4}" : "\u270F\uFE0F"} ${path7.basename(file.path)}`);
3107
+ lines.push(`### ${file.type === "created" ? "\u{1F4C4}" : "\u270F\uFE0F"} ${path8.basename(file.path)}`);
2969
3108
  lines.push(`**Path**: \`${file.path}\``);
2970
3109
  lines.push("");
2971
3110
  lines.push("```" + (file.path.endsWith(".cs") ? "csharp" : "typescript"));
@@ -3010,7 +3149,7 @@ function formatResult3(result, type, name, dryRun = false) {
3010
3149
 
3011
3150
  // src/tools/api-docs.ts
3012
3151
  import axios from "axios";
3013
- import path8 from "path";
3152
+ import path9 from "path";
3014
3153
  var apiDocsTool = {
3015
3154
  name: "api_docs",
3016
3155
  description: "Get API documentation for SmartStack endpoints. Can fetch from Swagger/OpenAPI or parse controller files directly.",
@@ -3115,7 +3254,7 @@ async function parseControllers(structure) {
3115
3254
  const endpoints = [];
3116
3255
  for (const file of controllerFiles) {
3117
3256
  const content = await readText(file);
3118
- const fileName = path8.basename(file, ".cs");
3257
+ const fileName = path9.basename(file, ".cs");
3119
3258
  const controllerName = fileName.replace("Controller", "");
3120
3259
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
3121
3260
  const baseRoute = routeMatch ? routeMatch[1].replace("[controller]", controllerName.toLowerCase()) : `/api/${controllerName.toLowerCase()}`;
@@ -3375,7 +3514,7 @@ function formatAsOpenApi(endpoints) {
3375
3514
 
3376
3515
  // src/tools/suggest-migration.ts
3377
3516
  import { z as z2 } from "zod";
3378
- import path9 from "path";
3517
+ import path10 from "path";
3379
3518
  var suggestMigrationTool = {
3380
3519
  name: "suggest_migration",
3381
3520
  description: "Suggest a migration name following SmartStack conventions ({context}_v{version}_{sequence}_{Description})",
@@ -3463,13 +3602,13 @@ async function handleSuggestMigration(args, config) {
3463
3602
  }
3464
3603
  async function findExistingMigrations(structure, config, context) {
3465
3604
  const migrations = [];
3466
- const infraPath = structure.infrastructure || path9.join(config.smartstack.projectPath, "Infrastructure");
3467
- const migrationsPath = path9.join(infraPath, "Migrations");
3605
+ const infraPath = structure.infrastructure || path10.join(config.smartstack.projectPath, "Infrastructure");
3606
+ const migrationsPath = path10.join(infraPath, "Migrations");
3468
3607
  try {
3469
3608
  const migrationFiles = await findFiles("*.cs", { cwd: migrationsPath });
3470
3609
  const migrationPattern = /^(\w+)_v(\d+\.\d+\.\d+)_(\d+)_(\w+)\.cs$/;
3471
3610
  for (const file of migrationFiles) {
3472
- const fileName = path9.basename(file);
3611
+ const fileName = path10.basename(file);
3473
3612
  if (fileName.includes(".Designer.") || fileName.includes("ModelSnapshot")) {
3474
3613
  continue;
3475
3614
  }
@@ -3512,7 +3651,7 @@ function compareVersions2(a, b) {
3512
3651
 
3513
3652
  // src/tools/scaffold-tests.ts
3514
3653
  import Handlebars2 from "handlebars";
3515
- import path10 from "path";
3654
+ import path11 from "path";
3516
3655
  var scaffoldTestsTool = {
3517
3656
  name: "scaffold_tests",
3518
3657
  description: "Generate unit, integration, and security tests for SmartStack entities, services, controllers, validators, and repositories. Ensures non-regression and maximum security coverage.",
@@ -4942,14 +5081,14 @@ async function scaffoldEntityTests(name, options, testTypes, structure, config,
4942
5081
  };
4943
5082
  if (testTypes.includes("unit")) {
4944
5083
  const content = Handlebars2.compile(entityTestTemplate)(context);
4945
- const testPath = path10.join(structure.root, "Tests", "Unit", "Domain", `${name}Tests.cs`);
5084
+ const testPath = path11.join(structure.root, "Tests", "Unit", "Domain", `${name}Tests.cs`);
4946
5085
  validatePathSecurity(testPath, structure.root);
4947
5086
  if (!dryRun) {
4948
- await ensureDirectory(path10.dirname(testPath));
5087
+ await ensureDirectory(path11.dirname(testPath));
4949
5088
  await writeText(testPath, content);
4950
5089
  }
4951
5090
  result.files.push({
4952
- path: path10.relative(structure.root, testPath),
5091
+ path: path11.relative(structure.root, testPath),
4953
5092
  content,
4954
5093
  type: "created"
4955
5094
  });
@@ -4960,14 +5099,14 @@ async function scaffoldEntityTests(name, options, testTypes, structure, config,
4960
5099
  nameLower: name.charAt(0).toLowerCase() + name.slice(1),
4961
5100
  apiNamespace: config.conventions.namespaces.api
4962
5101
  });
4963
- const securityPath = path10.join(structure.root, "Tests", "Security", `${name}SecurityTests.cs`);
5102
+ const securityPath = path11.join(structure.root, "Tests", "Security", `${name}SecurityTests.cs`);
4964
5103
  validatePathSecurity(securityPath, structure.root);
4965
5104
  if (!dryRun) {
4966
- await ensureDirectory(path10.dirname(securityPath));
5105
+ await ensureDirectory(path11.dirname(securityPath));
4967
5106
  await writeText(securityPath, securityContent);
4968
5107
  }
4969
5108
  result.files.push({
4970
- path: path10.relative(structure.root, securityPath),
5109
+ path: path11.relative(structure.root, securityPath),
4971
5110
  content: securityContent,
4972
5111
  type: "created"
4973
5112
  });
@@ -4988,14 +5127,14 @@ async function scaffoldServiceTests(name, options, testTypes, structure, config,
4988
5127
  };
4989
5128
  if (testTypes.includes("unit")) {
4990
5129
  const content = Handlebars2.compile(serviceTestTemplate)(context);
4991
- const testPath = path10.join(structure.root, "Tests", "Unit", "Services", `${name}ServiceTests.cs`);
5130
+ const testPath = path11.join(structure.root, "Tests", "Unit", "Services", `${name}ServiceTests.cs`);
4992
5131
  validatePathSecurity(testPath, structure.root);
4993
5132
  if (!dryRun) {
4994
- await ensureDirectory(path10.dirname(testPath));
5133
+ await ensureDirectory(path11.dirname(testPath));
4995
5134
  await writeText(testPath, content);
4996
5135
  }
4997
5136
  result.files.push({
4998
- path: path10.relative(structure.root, testPath),
5137
+ path: path11.relative(structure.root, testPath),
4999
5138
  content,
5000
5139
  type: "created"
5001
5140
  });
@@ -5018,28 +5157,28 @@ async function scaffoldControllerTests(name, options, testTypes, structure, conf
5018
5157
  };
5019
5158
  if (testTypes.includes("integration")) {
5020
5159
  const content = Handlebars2.compile(controllerTestTemplate)(context);
5021
- const testPath = path10.join(structure.root, "Tests", "Integration", "Controllers", `${name}ControllerTests.cs`);
5160
+ const testPath = path11.join(structure.root, "Tests", "Integration", "Controllers", `${name}ControllerTests.cs`);
5022
5161
  validatePathSecurity(testPath, structure.root);
5023
5162
  if (!dryRun) {
5024
- await ensureDirectory(path10.dirname(testPath));
5163
+ await ensureDirectory(path11.dirname(testPath));
5025
5164
  await writeText(testPath, content);
5026
5165
  }
5027
5166
  result.files.push({
5028
- path: path10.relative(structure.root, testPath),
5167
+ path: path11.relative(structure.root, testPath),
5029
5168
  content,
5030
5169
  type: "created"
5031
5170
  });
5032
5171
  }
5033
5172
  if (testTypes.includes("security")) {
5034
5173
  const securityContent = Handlebars2.compile(securityTestTemplate)(context);
5035
- const securityPath = path10.join(structure.root, "Tests", "Security", `${name}SecurityTests.cs`);
5174
+ const securityPath = path11.join(structure.root, "Tests", "Security", `${name}SecurityTests.cs`);
5036
5175
  validatePathSecurity(securityPath, structure.root);
5037
5176
  if (!dryRun) {
5038
- await ensureDirectory(path10.dirname(securityPath));
5177
+ await ensureDirectory(path11.dirname(securityPath));
5039
5178
  await writeText(securityPath, securityContent);
5040
5179
  }
5041
5180
  result.files.push({
5042
- path: path10.relative(structure.root, securityPath),
5181
+ path: path11.relative(structure.root, securityPath),
5043
5182
  content: securityContent,
5044
5183
  type: "created"
5045
5184
  });
@@ -5057,14 +5196,14 @@ async function scaffoldValidatorTests(name, options, testTypes, structure, confi
5057
5196
  };
5058
5197
  if (testTypes.includes("unit")) {
5059
5198
  const content = Handlebars2.compile(validatorTestTemplate)(context);
5060
- const testPath = path10.join(structure.root, "Tests", "Unit", "Validators", `${name}ValidatorTests.cs`);
5199
+ const testPath = path11.join(structure.root, "Tests", "Unit", "Validators", `${name}ValidatorTests.cs`);
5061
5200
  validatePathSecurity(testPath, structure.root);
5062
5201
  if (!dryRun) {
5063
- await ensureDirectory(path10.dirname(testPath));
5202
+ await ensureDirectory(path11.dirname(testPath));
5064
5203
  await writeText(testPath, content);
5065
5204
  }
5066
5205
  result.files.push({
5067
- path: path10.relative(structure.root, testPath),
5206
+ path: path11.relative(structure.root, testPath),
5068
5207
  content,
5069
5208
  type: "created"
5070
5209
  });
@@ -5084,14 +5223,14 @@ async function scaffoldRepositoryTests(name, options, testTypes, structure, conf
5084
5223
  };
5085
5224
  if (testTypes.includes("integration")) {
5086
5225
  const content = Handlebars2.compile(repositoryTestTemplate)(context);
5087
- const testPath = path10.join(structure.root, "Tests", "Integration", "Repositories", `${name}RepositoryTests.cs`);
5226
+ const testPath = path11.join(structure.root, "Tests", "Integration", "Repositories", `${name}RepositoryTests.cs`);
5088
5227
  validatePathSecurity(testPath, structure.root);
5089
5228
  if (!dryRun) {
5090
- await ensureDirectory(path10.dirname(testPath));
5229
+ await ensureDirectory(path11.dirname(testPath));
5091
5230
  await writeText(testPath, content);
5092
5231
  }
5093
5232
  result.files.push({
5094
- path: path10.relative(structure.root, testPath),
5233
+ path: path11.relative(structure.root, testPath),
5095
5234
  content,
5096
5235
  type: "created"
5097
5236
  });
@@ -5141,7 +5280,7 @@ function formatTestResult(result, _target, name, dryRun) {
5141
5280
  }
5142
5281
 
5143
5282
  // src/tools/analyze-test-coverage.ts
5144
- import path11 from "path";
5283
+ import path12 from "path";
5145
5284
  var analyzeTestCoverageTool = {
5146
5285
  name: "analyze_test_coverage",
5147
5286
  description: "Analyze test coverage for a SmartStack project. Identifies entities, services, and controllers without tests, calculates coverage ratios, and provides recommendations.",
@@ -5214,7 +5353,7 @@ async function analyzeEntityCoverage(structure, result) {
5214
5353
  }
5215
5354
  const entityFiles = await findFiles("**/*.cs", { cwd: structure.domain });
5216
5355
  const entityNames = extractComponentNames(entityFiles, ["Entity", "Aggregate"]);
5217
- const testPath = path11.join(structure.root, "Tests", "Unit", "Domain");
5356
+ const testPath = path12.join(structure.root, "Tests", "Unit", "Domain");
5218
5357
  let testFiles = [];
5219
5358
  try {
5220
5359
  testFiles = await findFiles("**/*Tests.cs", { cwd: testPath });
@@ -5243,17 +5382,17 @@ async function analyzeServiceCoverage(structure, result) {
5243
5382
  }
5244
5383
  const serviceFiles = await findFiles("**/I*Service.cs", { cwd: structure.application });
5245
5384
  const serviceNames = serviceFiles.map((f) => {
5246
- const basename = path11.basename(f, ".cs");
5385
+ const basename = path12.basename(f, ".cs");
5247
5386
  return basename.startsWith("I") ? basename.slice(1) : basename;
5248
5387
  }).filter((n) => n.endsWith("Service")).map((n) => n.replace(/Service$/, ""));
5249
- const testPath = path11.join(structure.root, "Tests", "Unit", "Services");
5388
+ const testPath = path12.join(structure.root, "Tests", "Unit", "Services");
5250
5389
  let testFiles = [];
5251
5390
  try {
5252
5391
  testFiles = await findFiles("**/*ServiceTests.cs", { cwd: testPath });
5253
5392
  } catch {
5254
5393
  }
5255
5394
  const testedServices = testFiles.map((f) => {
5256
- const basename = path11.basename(f, ".cs");
5395
+ const basename = path12.basename(f, ".cs");
5257
5396
  return basename.replace(/ServiceTests$/, "");
5258
5397
  });
5259
5398
  result.services.total = serviceNames.length;
@@ -5278,17 +5417,17 @@ async function analyzeControllerCoverage(structure, result) {
5278
5417
  }
5279
5418
  const controllerFiles = await findFiles("**/*Controller.cs", { cwd: structure.api });
5280
5419
  const controllerNames = controllerFiles.map((f) => {
5281
- const basename = path11.basename(f, ".cs");
5420
+ const basename = path12.basename(f, ".cs");
5282
5421
  return basename.replace(/Controller$/, "");
5283
5422
  }).filter((n) => n !== "Base");
5284
- const testPath = path11.join(structure.root, "Tests", "Integration", "Controllers");
5423
+ const testPath = path12.join(structure.root, "Tests", "Integration", "Controllers");
5285
5424
  let testFiles = [];
5286
5425
  try {
5287
5426
  testFiles = await findFiles("**/*ControllerTests.cs", { cwd: testPath });
5288
5427
  } catch {
5289
5428
  }
5290
5429
  const testedControllers = testFiles.map((f) => {
5291
- const basename = path11.basename(f, ".cs");
5430
+ const basename = path12.basename(f, ".cs");
5292
5431
  return basename.replace(/ControllerTests$/, "");
5293
5432
  });
5294
5433
  result.controllers.total = controllerNames.length;
@@ -5307,7 +5446,7 @@ async function analyzeControllerCoverage(structure, result) {
5307
5446
  }
5308
5447
  }
5309
5448
  function extractComponentNames(files, excludeSuffixes = []) {
5310
- return files.map((f) => path11.basename(f, ".cs")).filter((name) => {
5449
+ return files.map((f) => path12.basename(f, ".cs")).filter((name) => {
5311
5450
  const excludePatterns = [
5312
5451
  "Configuration",
5313
5452
  "Extensions",
@@ -5325,7 +5464,7 @@ function extractComponentNames(files, excludeSuffixes = []) {
5325
5464
  }
5326
5465
  function extractTestedNames(testFiles) {
5327
5466
  return testFiles.map((f) => {
5328
- const basename = path11.basename(f, ".cs");
5467
+ const basename = path12.basename(f, ".cs");
5329
5468
  return basename.replace(/Tests$/, "");
5330
5469
  });
5331
5470
  }
@@ -5463,7 +5602,7 @@ function formatCoverageCell(category) {
5463
5602
  }
5464
5603
 
5465
5604
  // src/tools/validate-test-conventions.ts
5466
- import path12 from "path";
5605
+ import path13 from "path";
5467
5606
  var validateTestConventionsTool = {
5468
5607
  name: "validate_test_conventions",
5469
5608
  description: "Validate that tests in a SmartStack project follow conventions: naming ({Method}_When{Condition}_Should{Result}), structure (Tests/Unit, Tests/Integration), patterns (AAA), assertions (FluentAssertions), and mocking (Moq).",
@@ -5526,7 +5665,7 @@ async function handleValidateTestConventions(args, config) {
5526
5665
  suggestions: [],
5527
5666
  autoFixedCount: 0
5528
5667
  };
5529
- const testsPath = path12.join(structure.root, "Tests");
5668
+ const testsPath = path13.join(structure.root, "Tests");
5530
5669
  try {
5531
5670
  let testFiles = [];
5532
5671
  try {
@@ -5543,7 +5682,7 @@ async function handleValidateTestConventions(args, config) {
5543
5682
  await validateStructure(testsPath, testFiles, result);
5544
5683
  }
5545
5684
  for (const testFile of testFiles) {
5546
- const fullPath = path12.join(testsPath, testFile);
5685
+ const fullPath = path13.join(testsPath, testFile);
5547
5686
  const content = await readText(fullPath);
5548
5687
  if (checks.includes("naming")) {
5549
5688
  validateNaming(testFile, content, result, input.autoFix);
@@ -5569,7 +5708,7 @@ async function validateStructure(testsPath, testFiles, result) {
5569
5708
  const expectedDirs = ["Unit", "Integration"];
5570
5709
  const foundDirs = /* @__PURE__ */ new Set();
5571
5710
  for (const file of testFiles) {
5572
- const parts = file.split(path12.sep);
5711
+ const parts = file.split(path13.sep);
5573
5712
  if (parts.length > 1) {
5574
5713
  foundDirs.add(parts[0]);
5575
5714
  }
@@ -5586,8 +5725,8 @@ async function validateStructure(testsPath, testFiles, result) {
5586
5725
  });
5587
5726
  }
5588
5727
  }
5589
- const hasUnitDomain = testFiles.some((f) => f.includes(path12.join("Unit", "Domain")));
5590
- const hasIntegrationControllers = testFiles.some((f) => f.includes(path12.join("Integration", "Controllers")));
5728
+ const hasUnitDomain = testFiles.some((f) => f.includes(path13.join("Unit", "Domain")));
5729
+ const hasIntegrationControllers = testFiles.some((f) => f.includes(path13.join("Integration", "Controllers")));
5591
5730
  if (!hasUnitDomain && testFiles.some((f) => f.includes("Unit"))) {
5592
5731
  result.suggestions.push("Consider organizing unit tests into subdirectories: Unit/Domain, Unit/Services, Unit/Validators");
5593
5732
  }
@@ -5847,7 +5986,7 @@ function formatValidationResult(result) {
5847
5986
  }
5848
5987
 
5849
5988
  // src/tools/suggest-test-scenarios.ts
5850
- import path13 from "path";
5989
+ import path14 from "path";
5851
5990
  var suggestTestScenariosTool = {
5852
5991
  name: "suggest_test_scenarios",
5853
5992
  description: "Analyze source code and suggest test scenarios based on detected methods, parameters, and patterns. Generates comprehensive test case recommendations for SmartStack components.",
@@ -5944,7 +6083,7 @@ async function findSourceFile(target, name, structure, _config) {
5944
6083
  pattern = `**/${name}Controller.cs`;
5945
6084
  break;
5946
6085
  case "file":
5947
- return path13.isAbsolute(name) ? name : path13.join(structure.root, name);
6086
+ return path14.isAbsolute(name) ? name : path14.join(structure.root, name);
5948
6087
  default:
5949
6088
  return null;
5950
6089
  }
@@ -5953,11 +6092,11 @@ async function findSourceFile(target, name, structure, _config) {
5953
6092
  const altPattern = `**/*${name}*.cs`;
5954
6093
  const altFiles = await findFiles(altPattern, { cwd: searchPath });
5955
6094
  if (altFiles.length > 0) {
5956
- return path13.join(searchPath, altFiles[0]);
6095
+ return path14.join(searchPath, altFiles[0]);
5957
6096
  }
5958
6097
  return null;
5959
6098
  }
5960
- return path13.join(searchPath, files[0]);
6099
+ return path14.join(searchPath, files[0]);
5961
6100
  }
5962
6101
  function parseSourceCode(content) {
5963
6102
  const methods = [];
@@ -6351,7 +6490,7 @@ function getTypeEmoji(type) {
6351
6490
  }
6352
6491
 
6353
6492
  // src/tools/scaffold-api-client.ts
6354
- import path14 from "path";
6493
+ import path15 from "path";
6355
6494
  var scaffoldApiClientTool = {
6356
6495
  name: "scaffold_api_client",
6357
6496
  description: `Generate TypeScript API client with NavRoute integration.
@@ -6370,6 +6509,10 @@ ensuring frontend routes stay synchronized with backend NavRoute attributes.`,
6370
6509
  inputSchema: {
6371
6510
  type: "object",
6372
6511
  properties: {
6512
+ path: {
6513
+ type: "string",
6514
+ description: "Path to SmartStack project root (defaults to configured project path)"
6515
+ },
6373
6516
  navRoute: {
6374
6517
  type: "string",
6375
6518
  description: 'NavRoute path (e.g., "platform.administration.users")'
@@ -6418,14 +6561,14 @@ async function scaffoldApiClient(input, config) {
6418
6561
  const includeHook = options?.includeHook ?? true;
6419
6562
  const nameLower = name.charAt(0).toLowerCase() + name.slice(1);
6420
6563
  const apiPath = navRouteToApiPath(navRoute);
6421
- const projectRoot = config.smartstack.projectPath;
6564
+ const projectRoot = input.path || config.smartstack.projectPath;
6422
6565
  const structure = await findSmartStackStructure(projectRoot);
6423
- const webPath = structure.web || path14.join(projectRoot, "web", "smartstack-web");
6424
- const servicesPath = options?.outputPath || path14.join(webPath, "src", "services", "api");
6425
- const hooksPath = path14.join(webPath, "src", "hooks");
6426
- const typesPath = path14.join(webPath, "src", "types");
6566
+ const webPath = structure.web || path15.join(projectRoot, "web");
6567
+ const servicesPath = options?.outputPath || path15.join(webPath, "src", "services", "api");
6568
+ const hooksPath = path15.join(webPath, "src", "hooks");
6569
+ const typesPath = path15.join(webPath, "src", "types");
6427
6570
  const apiClientContent = generateApiClient(name, nameLower, navRoute, apiPath, methods);
6428
- const apiClientFile = path14.join(servicesPath, `${nameLower}.ts`);
6571
+ const apiClientFile = path15.join(servicesPath, `${nameLower}.ts`);
6429
6572
  if (!dryRun) {
6430
6573
  await ensureDirectory(servicesPath);
6431
6574
  await writeText(apiClientFile, apiClientContent);
@@ -6433,7 +6576,7 @@ async function scaffoldApiClient(input, config) {
6433
6576
  result.files.push({ path: apiClientFile, content: apiClientContent, type: "created" });
6434
6577
  if (includeTypes) {
6435
6578
  const typesContent = generateTypes(name);
6436
- const typesFile = path14.join(typesPath, `${nameLower}.ts`);
6579
+ const typesFile = path15.join(typesPath, `${nameLower}.ts`);
6437
6580
  if (!dryRun) {
6438
6581
  await ensureDirectory(typesPath);
6439
6582
  await writeText(typesFile, typesContent);
@@ -6442,7 +6585,7 @@ async function scaffoldApiClient(input, config) {
6442
6585
  }
6443
6586
  if (includeHook) {
6444
6587
  const hookContent = generateHook(name, nameLower, methods);
6445
- const hookFile = path14.join(hooksPath, `use${name}.ts`);
6588
+ const hookFile = path15.join(hooksPath, `use${name}.ts`);
6446
6589
  if (!dryRun) {
6447
6590
  await ensureDirectory(hooksPath);
6448
6591
  await writeText(hookFile, hookContent);
@@ -6763,8 +6906,8 @@ function formatResult4(result, input) {
6763
6906
  }
6764
6907
 
6765
6908
  // src/tools/scaffold-routes.ts
6766
- import path15 from "path";
6767
- import { glob as glob2 } from "glob";
6909
+ import path16 from "path";
6910
+ import { glob as glob3 } from "glob";
6768
6911
  var scaffoldRoutesTool = {
6769
6912
  name: "scaffold_routes",
6770
6913
  description: `Generate React Router configuration from backend NavRoute attributes.
@@ -6782,6 +6925,10 @@ and generates corresponding frontend routing infrastructure.`,
6782
6925
  inputSchema: {
6783
6926
  type: "object",
6784
6927
  properties: {
6928
+ path: {
6929
+ type: "string",
6930
+ description: "Path to SmartStack project root (defaults to configured project path)"
6931
+ },
6785
6932
  source: {
6786
6933
  type: "string",
6787
6934
  enum: ["controllers", "navigation", "manual"],
@@ -6824,10 +6971,10 @@ async function scaffoldRoutes(input, config) {
6824
6971
  const includeLayouts = options?.includeLayouts ?? true;
6825
6972
  const includeGuards = options?.includeGuards ?? true;
6826
6973
  const generateRegistry = options?.generateRegistry ?? true;
6827
- const projectRoot = config.smartstack.projectPath;
6974
+ const projectRoot = input.path || config.smartstack.projectPath;
6828
6975
  const structure = await findSmartStackStructure(projectRoot);
6829
- const webPath = structure.web || path15.join(projectRoot, "web", "smartstack-web");
6830
- const routesPath = options?.outputPath || path15.join(webPath, "src", "routes");
6976
+ const webPath = structure.web || path16.join(projectRoot, "web");
6977
+ const routesPath = options?.outputPath || path16.join(webPath, "src", "routes");
6831
6978
  const navRoutes = await discoverNavRoutes(structure, scope);
6832
6979
  if (navRoutes.length === 0) {
6833
6980
  result.success = false;
@@ -6836,7 +6983,7 @@ async function scaffoldRoutes(input, config) {
6836
6983
  }
6837
6984
  if (generateRegistry) {
6838
6985
  const registryContent = generateNavRouteRegistry(navRoutes);
6839
- const registryFile = path15.join(routesPath, "navRoutes.generated.ts");
6986
+ const registryFile = path16.join(routesPath, "navRoutes.generated.ts");
6840
6987
  if (!dryRun) {
6841
6988
  await ensureDirectory(routesPath);
6842
6989
  await writeText(registryFile, registryContent);
@@ -6844,18 +6991,18 @@ async function scaffoldRoutes(input, config) {
6844
6991
  result.files.push({ path: registryFile, content: registryContent, type: "created" });
6845
6992
  }
6846
6993
  const routerContent = generateRouterConfig(navRoutes, includeGuards);
6847
- const routerFile = path15.join(routesPath, "index.tsx");
6994
+ const routerFile = path16.join(routesPath, "index.tsx");
6848
6995
  if (!dryRun) {
6849
6996
  await ensureDirectory(routesPath);
6850
6997
  await writeText(routerFile, routerContent);
6851
6998
  }
6852
6999
  result.files.push({ path: routerFile, content: routerContent, type: "created" });
6853
7000
  if (includeLayouts) {
6854
- const layoutsPath = path15.join(webPath, "src", "layouts");
7001
+ const layoutsPath = path16.join(webPath, "src", "layouts");
6855
7002
  const contexts = [...new Set(navRoutes.map((r) => r.navRoute.split(".")[0]))];
6856
7003
  for (const context of contexts) {
6857
7004
  const layoutContent = generateLayout(context);
6858
- const layoutFile = path15.join(layoutsPath, `${capitalize(context)}Layout.tsx`);
7005
+ const layoutFile = path16.join(layoutsPath, `${capitalize(context)}Layout.tsx`);
6859
7006
  if (!dryRun) {
6860
7007
  await ensureDirectory(layoutsPath);
6861
7008
  await writeText(layoutFile, layoutContent);
@@ -6865,7 +7012,7 @@ async function scaffoldRoutes(input, config) {
6865
7012
  }
6866
7013
  if (includeGuards) {
6867
7014
  const guardsContent = generateRouteGuards();
6868
- const guardsFile = path15.join(routesPath, "guards.tsx");
7015
+ const guardsFile = path16.join(routesPath, "guards.tsx");
6869
7016
  if (!dryRun) {
6870
7017
  await writeText(guardsFile, guardsContent);
6871
7018
  }
@@ -6883,7 +7030,7 @@ async function discoverNavRoutes(structure, scope) {
6883
7030
  logger.warn("No API project found");
6884
7031
  return routes;
6885
7032
  }
6886
- const controllerFiles = await glob2("**/*Controller.cs", {
7033
+ const controllerFiles = await glob3("**/*Controller.cs", {
6887
7034
  cwd: apiPath,
6888
7035
  absolute: true,
6889
7036
  ignore: ["**/obj/**", "**/bin/**"]
@@ -6899,7 +7046,7 @@ async function discoverNavRoutes(structure, scope) {
6899
7046
  if (scope !== "all" && context !== scope) {
6900
7047
  continue;
6901
7048
  }
6902
- const controllerMatch = path15.basename(file).match(/(.+)Controller\.cs$/);
7049
+ const controllerMatch = path16.basename(file).match(/(.+)Controller\.cs$/);
6903
7050
  const controllerName = controllerMatch ? controllerMatch[1] : "Unknown";
6904
7051
  const methods = [];
6905
7052
  if (content.includes("[HttpGet]")) methods.push("GET");
@@ -7244,8 +7391,8 @@ function formatResult5(result, input) {
7244
7391
  }
7245
7392
 
7246
7393
  // src/tools/validate-frontend-routes.ts
7247
- import path16 from "path";
7248
- import { glob as glob3 } from "glob";
7394
+ import path17 from "path";
7395
+ import { glob as glob4 } from "glob";
7249
7396
  var validateFrontendRoutesTool = {
7250
7397
  name: "validate_frontend_routes",
7251
7398
  description: `Validate frontend routes against backend NavRoute attributes.
@@ -7263,6 +7410,10 @@ Reports issues and provides actionable recommendations for synchronization.`,
7263
7410
  inputSchema: {
7264
7411
  type: "object",
7265
7412
  properties: {
7413
+ path: {
7414
+ type: "string",
7415
+ description: "Path to SmartStack project root (defaults to configured project path)"
7416
+ },
7266
7417
  scope: {
7267
7418
  type: "string",
7268
7419
  enum: ["api-clients", "routes", "registry", "all"],
@@ -7306,9 +7457,9 @@ async function validateFrontendRoutes(input, config) {
7306
7457
  recommendations: []
7307
7458
  };
7308
7459
  const { scope } = input;
7309
- const projectRoot = config.smartstack.projectPath;
7460
+ const projectRoot = input.path || config.smartstack.projectPath;
7310
7461
  const structure = await findSmartStackStructure(projectRoot);
7311
- const webPath = structure.web || path16.join(projectRoot, "web", "smartstack-web");
7462
+ const webPath = structure.web || path17.join(projectRoot, "web");
7312
7463
  const backendRoutes = await discoverBackendNavRoutes(structure);
7313
7464
  if (scope === "all" || scope === "registry") {
7314
7465
  await validateRegistry(webPath, backendRoutes, result);
@@ -7329,7 +7480,7 @@ async function discoverBackendNavRoutes(structure) {
7329
7480
  if (!apiPath) {
7330
7481
  return routes;
7331
7482
  }
7332
- const controllerFiles = await glob3("**/*Controller.cs", {
7483
+ const controllerFiles = await glob4("**/*Controller.cs", {
7333
7484
  cwd: apiPath,
7334
7485
  absolute: true,
7335
7486
  ignore: ["**/obj/**", "**/bin/**"]
@@ -7342,7 +7493,7 @@ async function discoverBackendNavRoutes(structure) {
7342
7493
  const navRoute = navRouteMatch[1];
7343
7494
  const suffix = navRouteMatch[2];
7344
7495
  const fullNavRoute = suffix ? `${navRoute}.${suffix}` : navRoute;
7345
- const controllerMatch = path16.basename(file).match(/(.+)Controller\.cs$/);
7496
+ const controllerMatch = path17.basename(file).match(/(.+)Controller\.cs$/);
7346
7497
  const controllerName = controllerMatch ? controllerMatch[1] : "Unknown";
7347
7498
  const methods = [];
7348
7499
  if (content.includes("[HttpGet]")) methods.push("GET");
@@ -7370,7 +7521,7 @@ async function discoverBackendNavRoutes(structure) {
7370
7521
  return routes;
7371
7522
  }
7372
7523
  async function validateRegistry(webPath, backendRoutes, result) {
7373
- const registryPath = path16.join(webPath, "src", "routes", "navRoutes.generated.ts");
7524
+ const registryPath = path17.join(webPath, "src", "routes", "navRoutes.generated.ts");
7374
7525
  if (!await fileExists(registryPath)) {
7375
7526
  result.registry.exists = false;
7376
7527
  result.recommendations.push("Run `scaffold_routes` to generate navRoutes.generated.ts");
@@ -7400,8 +7551,8 @@ async function validateRegistry(webPath, backendRoutes, result) {
7400
7551
  }
7401
7552
  }
7402
7553
  async function validateApiClients(webPath, backendRoutes, result) {
7403
- const servicesPath = path16.join(webPath, "src", "services", "api");
7404
- const clientFiles = await glob3("**/*.ts", {
7554
+ const servicesPath = path17.join(webPath, "src", "services", "api");
7555
+ const clientFiles = await glob4("**/*.ts", {
7405
7556
  cwd: servicesPath,
7406
7557
  absolute: true,
7407
7558
  ignore: ["**/index.ts"]
@@ -7410,7 +7561,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
7410
7561
  for (const file of clientFiles) {
7411
7562
  try {
7412
7563
  const content = await readText(file);
7413
- const relativePath = path16.relative(webPath, file);
7564
+ const relativePath = path17.relative(webPath, file);
7414
7565
  const usesRegistry = content.includes("getRoute('") || content.includes('getRoute("');
7415
7566
  if (!usesRegistry) {
7416
7567
  const hardcodedMatch = content.match(/apiClient\.(get|post|put|delete)\s*[<(]\s*['"`]([^'"`]+)['"`]/);
@@ -7448,7 +7599,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
7448
7599
  }
7449
7600
  }
7450
7601
  async function validateRoutes(webPath, backendRoutes, result) {
7451
- const routesPath = path16.join(webPath, "src", "routes", "index.tsx");
7602
+ const routesPath = path17.join(webPath, "src", "routes", "index.tsx");
7452
7603
  if (!await fileExists(routesPath)) {
7453
7604
  result.routes.total = 0;
7454
7605
  result.routes.missing = backendRoutes.map((r) => r.navRoute);
@@ -8583,7 +8734,7 @@ Run specific or all checks:
8583
8734
  }
8584
8735
 
8585
8736
  // src/resources/project-info.ts
8586
- import path17 from "path";
8737
+ import path18 from "path";
8587
8738
  var projectInfoResourceTemplate = {
8588
8739
  uri: "smartstack://project",
8589
8740
  name: "SmartStack Project Info",
@@ -8620,16 +8771,16 @@ async function getProjectInfoResource(config) {
8620
8771
  lines.push("```");
8621
8772
  lines.push(`${projectInfo.name}/`);
8622
8773
  if (structure.domain) {
8623
- lines.push(`\u251C\u2500\u2500 ${path17.basename(structure.domain)}/ # Domain layer (entities)`);
8774
+ lines.push(`\u251C\u2500\u2500 ${path18.basename(structure.domain)}/ # Domain layer (entities)`);
8624
8775
  }
8625
8776
  if (structure.application) {
8626
- lines.push(`\u251C\u2500\u2500 ${path17.basename(structure.application)}/ # Application layer (services)`);
8777
+ lines.push(`\u251C\u2500\u2500 ${path18.basename(structure.application)}/ # Application layer (services)`);
8627
8778
  }
8628
8779
  if (structure.infrastructure) {
8629
- lines.push(`\u251C\u2500\u2500 ${path17.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
8780
+ lines.push(`\u251C\u2500\u2500 ${path18.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
8630
8781
  }
8631
8782
  if (structure.api) {
8632
- lines.push(`\u251C\u2500\u2500 ${path17.basename(structure.api)}/ # API layer (controllers)`);
8783
+ lines.push(`\u251C\u2500\u2500 ${path18.basename(structure.api)}/ # API layer (controllers)`);
8633
8784
  }
8634
8785
  if (structure.web) {
8635
8786
  lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
@@ -8642,8 +8793,8 @@ async function getProjectInfoResource(config) {
8642
8793
  lines.push("| Project | Path |");
8643
8794
  lines.push("|---------|------|");
8644
8795
  for (const csproj of projectInfo.csprojFiles) {
8645
- const name = path17.basename(csproj, ".csproj");
8646
- const relativePath = path17.relative(projectPath, csproj);
8796
+ const name = path18.basename(csproj, ".csproj");
8797
+ const relativePath = path18.relative(projectPath, csproj);
8647
8798
  lines.push(`| ${name} | \`${relativePath}\` |`);
8648
8799
  }
8649
8800
  lines.push("");
@@ -8653,10 +8804,10 @@ async function getProjectInfoResource(config) {
8653
8804
  cwd: structure.migrations,
8654
8805
  ignore: ["*.Designer.cs"]
8655
8806
  });
8656
- const migrations = migrationFiles.map((f) => path17.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
8807
+ const migrations = migrationFiles.map((f) => path18.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
8657
8808
  lines.push("## EF Core Migrations");
8658
8809
  lines.push("");
8659
- lines.push(`**Location**: \`${path17.relative(projectPath, structure.migrations)}\``);
8810
+ lines.push(`**Location**: \`${path18.relative(projectPath, structure.migrations)}\``);
8660
8811
  lines.push(`**Total Migrations**: ${migrations.length}`);
8661
8812
  lines.push("");
8662
8813
  if (migrations.length > 0) {
@@ -8691,11 +8842,11 @@ async function getProjectInfoResource(config) {
8691
8842
  lines.push("dotnet build");
8692
8843
  lines.push("");
8693
8844
  lines.push("# Run API");
8694
- lines.push(`cd ${structure.api ? path17.relative(projectPath, structure.api) : "SmartStack.Api"}`);
8845
+ lines.push(`cd ${structure.api ? path18.relative(projectPath, structure.api) : "src/Api"}`);
8695
8846
  lines.push("dotnet run");
8696
8847
  lines.push("");
8697
8848
  lines.push("# Run frontend");
8698
- lines.push(`cd ${structure.web ? path17.relative(projectPath, structure.web) : "web/smartstack-web"}`);
8849
+ lines.push(`cd ${structure.web ? path18.relative(projectPath, structure.web) : "web"}`);
8699
8850
  lines.push("npm run dev");
8700
8851
  lines.push("");
8701
8852
  lines.push("# Create migration");
@@ -8718,7 +8869,7 @@ async function getProjectInfoResource(config) {
8718
8869
  }
8719
8870
 
8720
8871
  // src/resources/api-endpoints.ts
8721
- import path18 from "path";
8872
+ import path19 from "path";
8722
8873
  var apiEndpointsResourceTemplate = {
8723
8874
  uri: "smartstack://api/",
8724
8875
  name: "SmartStack API Endpoints",
@@ -8743,7 +8894,7 @@ async function getApiEndpointsResource(config, endpointFilter) {
8743
8894
  }
8744
8895
  async function parseController(filePath, _rootPath) {
8745
8896
  const content = await readText(filePath);
8746
- const fileName = path18.basename(filePath, ".cs");
8897
+ const fileName = path19.basename(filePath, ".cs");
8747
8898
  const controllerName = fileName.replace("Controller", "");
8748
8899
  const endpoints = [];
8749
8900
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
@@ -8890,7 +9041,7 @@ function getMethodEmoji(method) {
8890
9041
  }
8891
9042
 
8892
9043
  // src/resources/db-schema.ts
8893
- import path19 from "path";
9044
+ import path20 from "path";
8894
9045
  var dbSchemaResourceTemplate = {
8895
9046
  uri: "smartstack://schema/",
8896
9047
  name: "SmartStack Database Schema",
@@ -8980,7 +9131,7 @@ async function parseEntity(filePath, rootPath, _config) {
8980
9131
  tableName,
8981
9132
  properties,
8982
9133
  relationships,
8983
- file: path19.relative(rootPath, filePath)
9134
+ file: path20.relative(rootPath, filePath)
8984
9135
  };
8985
9136
  }
8986
9137
  async function enrichFromConfigurations(entities, infrastructurePath, _config) {
@@ -9126,7 +9277,7 @@ function formatSchema(entities, filter, _config) {
9126
9277
  }
9127
9278
 
9128
9279
  // src/resources/entities.ts
9129
- import path20 from "path";
9280
+ import path21 from "path";
9130
9281
  var entitiesResourceTemplate = {
9131
9282
  uri: "smartstack://entities/",
9132
9283
  name: "SmartStack Entities",
@@ -9186,7 +9337,7 @@ async function parseEntitySummary(filePath, rootPath, config) {
9186
9337
  hasSoftDelete,
9187
9338
  hasRowVersion,
9188
9339
  file: filePath,
9189
- relativePath: path20.relative(rootPath, filePath)
9340
+ relativePath: path21.relative(rootPath, filePath)
9190
9341
  };
9191
9342
  }
9192
9343
  function inferTableInfo(entityName, config) {