@solid-stack/mason 1.0.20 → 1.0.22

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/bin.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  var cac = require('cac');
5
5
  var p14 = require('@clack/prompts');
6
- var pc14 = require('picocolors');
6
+ var pc15 = require('picocolors');
7
7
  var fs9 = require('fs');
8
8
  var path2 = require('path');
9
9
  var url = require('url');
@@ -33,7 +33,7 @@ function _interopNamespace(e) {
33
33
  }
34
34
 
35
35
  var p14__namespace = /*#__PURE__*/_interopNamespace(p14);
36
- var pc14__default = /*#__PURE__*/_interopDefault(pc14);
36
+ var pc15__default = /*#__PURE__*/_interopDefault(pc15);
37
37
  var fs9__default = /*#__PURE__*/_interopDefault(fs9);
38
38
  var path2__default = /*#__PURE__*/_interopDefault(path2);
39
39
  var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
@@ -76,17 +76,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
76
76
  function printGeneratedFiles(files, isDryRun = false) {
77
77
  console.log("");
78
78
  if (isDryRun) {
79
- p14__namespace.log.warn(pc14__default.default.yellow(pc14__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
79
+ p14__namespace.log.warn(pc15__default.default.yellow(pc15__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
80
80
  }
81
81
  for (const f of files) {
82
82
  if (f.action === "created") {
83
- p14__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
83
+ p14__namespace.log.success(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(created)")}`);
84
84
  } else if (f.action === "overwritten") {
85
- p14__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
85
+ p14__namespace.log.warn(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(overwritten)")}`);
86
86
  } else if (f.action === "skipped") {
87
- p14__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
87
+ p14__namespace.log.info(`${pc15__default.default.dim(f.relativePath)} ${pc15__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
88
88
  } else {
89
- p14__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(preview)")}`);
89
+ p14__namespace.log.info(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(preview)")}`);
90
90
  }
91
91
  }
92
92
  }
@@ -1773,13 +1773,13 @@ function handleCancel(result) {
1773
1773
  }
1774
1774
  function printBanner() {
1775
1775
  console.log("");
1776
- p14__namespace.intro(pc14__default.default.bgCyan(pc14__default.default.black(" @solid-stack/mason ")));
1776
+ p14__namespace.intro(pc15__default.default.bgCyan(pc15__default.default.black(" @solid-stack/mason ")));
1777
1777
  }
1778
1778
  async function promptProperties(options = {}) {
1779
1779
  const properties = options.defaults ? [...options.defaults] : [];
1780
1780
  if (properties.length > 0) {
1781
1781
  const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
1782
- p14__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
1782
+ p14__namespace.log.info(pc15__default.default.dim(`Pre-configured fields: ${summary}`));
1783
1783
  const addMore = handleCancel(
1784
1784
  await p14__namespace.confirm({
1785
1785
  message: "Would you like to add custom fields/properties?",
@@ -1790,7 +1790,7 @@ async function promptProperties(options = {}) {
1790
1790
  return properties;
1791
1791
  }
1792
1792
  }
1793
- p14__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
1793
+ p14__namespace.log.info(pc15__default.default.cyan("Enter properties (leave name blank when finished):"));
1794
1794
  while (true) {
1795
1795
  const name = handleCancel(
1796
1796
  await p14__namespace.text({
@@ -1838,7 +1838,7 @@ async function promptProperties(options = {}) {
1838
1838
  optional
1839
1839
  });
1840
1840
  p14__namespace.log.success(
1841
- pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
1841
+ pc15__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
1842
1842
  );
1843
1843
  }
1844
1844
  return properties;
@@ -2098,7 +2098,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2098
2098
  }
2099
2099
  }
2100
2100
  const s = p14__namespace.spinner();
2101
- s.start(`Generating feature '${pc14__default.default.cyan(normalizedName)}'...`);
2101
+ s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
2102
2102
  const options = {
2103
2103
  name: normalizedName,
2104
2104
  projectRoot,
@@ -2145,7 +2145,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2145
2145
  s.stop("Feature scaffolded successfully!");
2146
2146
  printGeneratedFiles(files, options.dryRun);
2147
2147
  p14__namespace.outro(
2148
- pc14__default.default.green(
2148
+ pc15__default.default.green(
2149
2149
  `\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2150
2150
  )
2151
2151
  );
@@ -2301,7 +2301,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2301
2301
  );
2302
2302
  }
2303
2303
  const s = p14__namespace.spinner();
2304
- s.start(`Generating shared module '${pc14__default.default.cyan(normalizedName)}'...`);
2304
+ s.start(`Generating shared module '${pc15__default.default.cyan(normalizedName)}'...`);
2305
2305
  const files = generateShared({
2306
2306
  name: normalizedName,
2307
2307
  description,
@@ -2315,7 +2315,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2315
2315
  s.stop("Shared module generated successfully!");
2316
2316
  printGeneratedFiles(files, Boolean(flags.dryRun));
2317
2317
  p14__namespace.outro(
2318
- pc14__default.default.green(
2318
+ pc15__default.default.green(
2319
2319
  `\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2320
2320
  )
2321
2321
  );
@@ -2443,7 +2443,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2443
2443
  }
2444
2444
  }
2445
2445
  const s = p14__namespace.spinner();
2446
- s.start(`Generating use case '${pc14__default.default.cyan(ucName)}'...`);
2446
+ s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
2447
2447
  const options = {
2448
2448
  name: ucName,
2449
2449
  feature,
@@ -2475,7 +2475,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2475
2475
  }
2476
2476
  s.stop("Use case generated successfully!");
2477
2477
  printGeneratedFiles(files, options.dryRun);
2478
- p14__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2478
+ p14__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2479
2479
  }
2480
2480
  async function runDomainCommand(domainNameArg, flags = {}) {
2481
2481
  const projectRoot = findProjectRoot();
@@ -2546,7 +2546,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2546
2546
  });
2547
2547
  }
2548
2548
  const s = p14__namespace.spinner();
2549
- s.start(`Generating domain model '${pc14__default.default.cyan(entityName)}'...`);
2549
+ s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
2550
2550
  const options = {
2551
2551
  name: entityName,
2552
2552
  feature,
@@ -2559,7 +2559,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2559
2559
  const file = generateDomain(options);
2560
2560
  s.stop("Domain model generated successfully!");
2561
2561
  printGeneratedFiles([file], options.dryRun);
2562
- p14__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2562
+ p14__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2563
2563
  }
2564
2564
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2565
2565
  const projectRoot = findProjectRoot();
@@ -2728,7 +2728,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2728
2728
  registerInProvider = !flags.noProvider;
2729
2729
  }
2730
2730
  const s = p14__namespace.spinner();
2731
- s.start(`Generating infrastructure class '${pc14__default.default.cyan(className)}'...`);
2731
+ s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
2732
2732
  const files = [];
2733
2733
  const options = {
2734
2734
  name: className,
@@ -2763,7 +2763,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2763
2763
  }
2764
2764
  s.stop("Infrastructure class generated successfully!");
2765
2765
  printGeneratedFiles(files, options.dryRun);
2766
- p14__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
2766
+ p14__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
2767
2767
  }
2768
2768
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2769
2769
  const projectRoot = findProjectRoot();
@@ -2876,7 +2876,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2876
2876
  }
2877
2877
  }
2878
2878
  const s = p14__namespace.spinner();
2879
- s.start(`Generating interface '${pc14__default.default.cyan(ifaceName)}'...`);
2879
+ s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
2880
2880
  const files = [];
2881
2881
  const options = {
2882
2882
  name: ifaceName,
@@ -2925,7 +2925,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2925
2925
  }
2926
2926
  s.stop("Interface generated successfully!");
2927
2927
  printGeneratedFiles(files, options.dryRun);
2928
- p14__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2928
+ p14__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2929
2929
  }
2930
2930
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2931
2931
  const projectRoot = findProjectRoot();
@@ -3042,7 +3042,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3042
3042
  }
3043
3043
  }
3044
3044
  const s = p14__namespace.spinner();
3045
- s.start(`Generating HTTP handler '${pc14__default.default.cyan(className)}'...`);
3045
+ s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
3046
3046
  const options = {
3047
3047
  name: className,
3048
3048
  feature,
@@ -3060,7 +3060,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3060
3060
  const files = generateHttpHandler(options);
3061
3061
  s.stop("HTTP handler generated successfully!");
3062
3062
  printGeneratedFiles(files, options.dryRun);
3063
- p14__namespace.outro(pc14__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3063
+ p14__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3064
3064
  }
3065
3065
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
3066
3066
  const formattedDeps = dependencies.map((dep) => ({
@@ -3216,7 +3216,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3216
3216
  );
3217
3217
  }
3218
3218
  const s = p14__namespace.spinner();
3219
- s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
3219
+ s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
3220
3220
  const options = {
3221
3221
  name: className,
3222
3222
  feature,
@@ -3230,7 +3230,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3230
3230
  const files = generateInjectable(options);
3231
3231
  s.stop("Injectable class generated successfully!");
3232
3232
  printGeneratedFiles(files, options.dryRun);
3233
- p14__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
3233
+ p14__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
3234
3234
  }
3235
3235
  async function runInteractiveMenu(flags = {}) {
3236
3236
  printBanner();
@@ -3344,7 +3344,7 @@ async function handleCreate(type, name, flags = {}) {
3344
3344
  break;
3345
3345
  default:
3346
3346
  p14__namespace.log.error(
3347
- pc14__default.default.red(
3347
+ pc15__default.default.red(
3348
3348
  `Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3349
3349
  )
3350
3350
  );
@@ -3580,7 +3580,7 @@ function installNpmPackages(projectRoot, packages, pm) {
3580
3580
  installCmd = `npm install ${packages.join(" ")}`;
3581
3581
  break;
3582
3582
  }
3583
- p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
3583
+ p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
3584
3584
  child_process.execSync(installCmd, {
3585
3585
  cwd: projectRoot,
3586
3586
  stdio: "inherit"
@@ -3736,19 +3736,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3736
3736
  );
3737
3737
  }
3738
3738
  if (type !== "shared" && type !== "feature") {
3739
- p14__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3739
+ p14__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3740
3740
  process.exit(1);
3741
3741
  }
3742
3742
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3743
3743
  const s = p14__namespace.spinner();
3744
- s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
3744
+ s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
3745
3745
  let index;
3746
3746
  try {
3747
3747
  index = await fetchRegistryIndex(registryBase);
3748
3748
  s.stop("Registry index loaded.");
3749
3749
  } catch (err) {
3750
- s.stop(pc14__default.default.red("Failed to load registry index."));
3751
- p14__namespace.log.error(pc14__default.default.red(err.message || String(err)));
3750
+ s.stop(pc15__default.default.red("Failed to load registry index."));
3751
+ p14__namespace.log.error(pc15__default.default.red(err.message || String(err)));
3752
3752
  process.exit(1);
3753
3753
  }
3754
3754
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3772,9 +3772,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3772
3772
  }
3773
3773
  if (!candidateNames.includes(name)) {
3774
3774
  const closest = findClosestMatch(name, candidateNames);
3775
- p14__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3775
+ p14__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3776
3776
  if (closest) {
3777
- p14__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
3777
+ p14__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
3778
3778
  }
3779
3779
  process.exit(1);
3780
3780
  }
@@ -3786,7 +3786,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3786
3786
  try {
3787
3787
  manifest = await fetchModuleManifest(registryBase, modType, modName);
3788
3788
  } catch (err) {
3789
- p14__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3789
+ p14__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3790
3790
  process.exit(1);
3791
3791
  }
3792
3792
  nodes.set(key, {
@@ -3826,7 +3826,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3826
3826
  buildTopologicalOrder(rootKey);
3827
3827
  const resolutions = /* @__PURE__ */ new Map();
3828
3828
  const globalAliasMap = /* @__PURE__ */ new Map();
3829
- p14__namespace.log.step(`Importing ${type}: ${pc14__default.default.cyan(name)}...`);
3829
+ p14__namespace.log.step(`Importing ${type}: ${pc15__default.default.cyan(name)}...`);
3830
3830
  for (const key of orderedKeys) {
3831
3831
  const node = nodes.get(key);
3832
3832
  const { type: modType, name: registryName, manifest } = node;
@@ -3859,7 +3859,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3859
3859
  if (programmaticPointTo) {
3860
3860
  if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
3861
3861
  p14__namespace.log.error(
3862
- pc14__default.default.red(
3862
+ pc15__default.default.red(
3863
3863
  `Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
3864
3864
  )
3865
3865
  );
@@ -3895,7 +3895,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3895
3895
  }
3896
3896
  const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
3897
3897
  if (!existingDir) {
3898
- p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14__default.default.cyan(registryName)}`);
3898
+ p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc15__default.default.cyan(registryName)}`);
3899
3899
  resolutions.set(key, {
3900
3900
  type: modType,
3901
3901
  registryName,
@@ -4126,7 +4126,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4126
4126
  const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
4127
4127
  if (key !== rootKey) {
4128
4128
  p14__namespace.log.step(
4129
- `Importing ${res.type}: ${pc14__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14__default.default.dim(` (from ${res.registryName})`) : ""}...`
4129
+ `Importing ${res.type}: ${pc15__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc15__default.default.dim(` (from ${res.registryName})`) : ""}...`
4130
4130
  );
4131
4131
  }
4132
4132
  if (res.action === "overwrite" && !flags.dryRun) {
@@ -4145,7 +4145,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4145
4145
  const localFilePath = path2__default.default.join(targetDir, file);
4146
4146
  if (fs9__default.default.existsSync(localFilePath) && !isOverwrite) {
4147
4147
  p14__namespace.log.warn(
4148
- ` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4148
+ ` \u21B3 Skipping existing file: ${pc15__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4149
4149
  );
4150
4150
  continue;
4151
4151
  }
@@ -4157,13 +4157,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4157
4157
  }
4158
4158
  const displayPath = path2__default.default.relative(projectRoot, localFilePath);
4159
4159
  totalWrittenFiles.push(displayPath);
4160
- p14__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
4160
+ p14__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
4161
4161
  }
4162
4162
  }
4163
4163
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
4164
4164
  if (missingNpm.length > 0) {
4165
4165
  if (flags.skipInstall || flags.dryRun) {
4166
- p14__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
4166
+ p14__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
4167
4167
  } else {
4168
4168
  try {
4169
4169
  installNpmPackages(projectRoot, missingNpm);
@@ -4177,18 +4177,901 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4177
4177
  }
4178
4178
  }
4179
4179
  if (flags.dryRun) {
4180
- p14__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
4180
+ p14__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
4181
4181
  } else {
4182
4182
  const rootRes = resolutions.get(rootKey);
4183
4183
  const baseDir = getModuleBaseDir(projectRoot, type);
4184
4184
  const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, rootRes.targetName));
4185
4185
  p14__namespace.log.success(
4186
- pc14__default.default.green(
4186
+ pc15__default.default.green(
4187
4187
  `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
4188
4188
  )
4189
4189
  );
4190
4190
  }
4191
4191
  }
4192
+ var DependencyRuleError = class extends Error {
4193
+ violations;
4194
+ isFailFast;
4195
+ constructor(message, violations, isFailFast = false) {
4196
+ super(message);
4197
+ this.name = "DependencyRuleError";
4198
+ this.violations = violations;
4199
+ this.isFailFast = isFailFast;
4200
+ }
4201
+ };
4202
+ function isTestFile2(filePath, projectRoot) {
4203
+ const norm = (projectRoot ? path2__default.default.relative(projectRoot, filePath) : filePath).replace(/\\/g, "/");
4204
+ const base = path2__default.default.basename(norm);
4205
+ return base.endsWith(".test.ts") || base.endsWith(".test.js") || base.endsWith(".spec.ts") || base.endsWith(".spec.js") || base.endsWith(".d.ts") || norm.includes("/__tests__/") || norm.startsWith("__tests__/") || /(?:^|\/)tests?\//.test(norm);
4206
+ }
4207
+ function scanDirFiles(dir, projectRoot) {
4208
+ if (!fs9__default.default.existsSync(dir)) return [];
4209
+ const entries = fs9__default.default.readdirSync(dir, { withFileTypes: true });
4210
+ const files = [];
4211
+ for (const entry of entries) {
4212
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "__tests__" || entry.name.startsWith(".")) {
4213
+ continue;
4214
+ }
4215
+ const fullPath = path2__default.default.join(dir, entry.name);
4216
+ if (entry.isDirectory()) {
4217
+ files.push(...scanDirFiles(fullPath, projectRoot));
4218
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !isTestFile2(fullPath, projectRoot)) {
4219
+ files.push(fullPath);
4220
+ }
4221
+ }
4222
+ return files;
4223
+ }
4224
+ function classifyFile(fullPath, projectRoot) {
4225
+ const rel = path2__default.default.relative(projectRoot, fullPath).replace(/\\/g, "/");
4226
+ const featureMatch = rel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/.*)?$/);
4227
+ if (!featureMatch || !featureMatch[1] || !featureMatch[2]) return {};
4228
+ const feature = featureMatch[1];
4229
+ const subfolder = featureMatch[2];
4230
+ let layer;
4231
+ if (subfolder === "domain" || subfolder === "errors") {
4232
+ layer = "domain";
4233
+ } else if (subfolder === "useCases" || subfolder === "usecases") {
4234
+ layer = "useCases";
4235
+ } else if (subfolder === "services") {
4236
+ layer = "services";
4237
+ } else if (subfolder === "infrastructure") {
4238
+ layer = "infrastructure";
4239
+ } else if (subfolder === "pxExpress" || subfolder === "presentation" || subfolder === "handlers" || subfolder.startsWith("px")) {
4240
+ layer = "presentation";
4241
+ }
4242
+ return { feature, layer };
4243
+ }
4244
+ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4245
+ if (specifier === "@solid-stack/di" || specifier.startsWith("@solid-stack/di/")) {
4246
+ return { isExternal: true, isDiFramework: true };
4247
+ }
4248
+ if (specifier.startsWith("node:") || ["crypto", "fs", "path", "http", "https", "url", "os", "child_process", "stream", "express", "cors", "dotenv"].includes(specifier)) {
4249
+ return { isExternal: true, isDiFramework: false };
4250
+ }
4251
+ let resolvedRel = null;
4252
+ if (specifier.startsWith(".")) {
4253
+ const abs = path2__default.default.resolve(path2__default.default.dirname(sourceFileAbs), specifier);
4254
+ resolvedRel = path2__default.default.relative(projectRoot, abs).replace(/\\/g, "/");
4255
+ } else if (specifier.startsWith("@/features/") || specifier.startsWith("~/features/")) {
4256
+ resolvedRel = specifier.replace(/^[@~]\//, "src/");
4257
+ } else if (specifier.startsWith("@features/")) {
4258
+ resolvedRel = "src/" + specifier.slice(1);
4259
+ } else if (specifier.startsWith("@/shared/") || specifier.startsWith("~/shared/")) {
4260
+ resolvedRel = specifier.replace(/^[@~]\//, "src/");
4261
+ } else if (specifier.startsWith("@shared/")) {
4262
+ resolvedRel = "src/" + specifier.slice(1);
4263
+ } else if (specifier.startsWith("@/") || specifier.startsWith("~/")) {
4264
+ resolvedRel = "src/" + specifier.slice(2);
4265
+ } else if (specifier.startsWith("features/") || specifier.startsWith("shared/")) {
4266
+ resolvedRel = specifier;
4267
+ }
4268
+ if (!resolvedRel) {
4269
+ return { isExternal: true, isDiFramework: false };
4270
+ }
4271
+ const featMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/(.*))?$/);
4272
+ if (featMatch && featMatch[1] && featMatch[2]) {
4273
+ const targetFeature = featMatch[1];
4274
+ const targetSubfolder = featMatch[2];
4275
+ const rest = featMatch[3] || "";
4276
+ let targetLayer;
4277
+ if (targetSubfolder === "domain" || targetSubfolder === "errors") {
4278
+ targetLayer = "domain";
4279
+ } else if (targetSubfolder === "useCases" || targetSubfolder === "usecases") {
4280
+ targetLayer = "useCases";
4281
+ } else if (targetSubfolder === "services") {
4282
+ targetLayer = "services";
4283
+ } else if (targetSubfolder === "infrastructure") {
4284
+ targetLayer = "infrastructure";
4285
+ } else if (targetSubfolder === "pxExpress" || targetSubfolder === "presentation" || targetSubfolder === "handlers" || targetSubfolder.startsWith("px")) {
4286
+ targetLayer = "presentation";
4287
+ }
4288
+ const basePart = rest ? path2__default.default.basename(rest) : targetSubfolder;
4289
+ const targetFileName = basePart.replace(/\.(js|ts|mjs|cjs)$/, "");
4290
+ const isTargetPort = /^I[A-Z]/.test(targetFileName);
4291
+ const isTargetRepo = isTargetPort && targetFileName.includes("Repo");
4292
+ return {
4293
+ isExternal: false,
4294
+ isDiFramework: false,
4295
+ targetFeature,
4296
+ targetLayer,
4297
+ targetSubfolder,
4298
+ targetFileName,
4299
+ isTargetPort,
4300
+ isTargetRepo
4301
+ };
4302
+ }
4303
+ const sharedMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?shared\/([^/]+)(?:\/(.*))?$/);
4304
+ if (sharedMatch) {
4305
+ const targetSharedModule = sharedMatch[1];
4306
+ const sharedRest = sharedMatch[2] || "";
4307
+ const isSharedRoot = !sharedRest.includes("/");
4308
+ const isSharedPorts = sharedRest.startsWith("ports/") || sharedRest === "ports";
4309
+ const isSharedInfra = sharedRest.startsWith("infrastructure/") || sharedRest === "infrastructure";
4310
+ const isSharedDomain = sharedRest.startsWith("domain/") || sharedRest === "domain";
4311
+ return {
4312
+ isExternal: false,
4313
+ isDiFramework: false,
4314
+ targetSharedModule,
4315
+ isSharedRoot,
4316
+ isSharedPorts,
4317
+ isSharedInfra,
4318
+ isSharedDomain
4319
+ };
4320
+ }
4321
+ return { isExternal: true, isDiFramework: false };
4322
+ }
4323
+ function extractImportStatements(content) {
4324
+ const statements = [];
4325
+ const lines = content.split(/\r?\n/);
4326
+ const regex = /(?:import\s+(?:type\s+)?(?:[\s\w*$,{}]+\s+from\s+)?|export\s+(?:type\s+)?(?:[\s\w*$,{}]+\s+from\s+)?|import\s*\(|require\s*\()\s*["']([^"']+)["']/g;
4327
+ let match;
4328
+ while ((match = regex.exec(content)) !== null) {
4329
+ const specifier = match[1];
4330
+ const charIndex = match.index;
4331
+ const lineNum = content.substring(0, charIndex).split("\n").length;
4332
+ const rawCode = lines[lineNum - 1]?.trim() || match[0].trim();
4333
+ statements.push({
4334
+ specifier,
4335
+ line: lineNum,
4336
+ rawCode
4337
+ });
4338
+ }
4339
+ return statements;
4340
+ }
4341
+ var FeatureDependencyGraph = class {
4342
+ edges = /* @__PURE__ */ new Map();
4343
+ addEdge(evidence) {
4344
+ const { fromFeature, toFeature } = evidence;
4345
+ if (fromFeature === toFeature) {
4346
+ return { createsCycle: false };
4347
+ }
4348
+ if (!this.edges.has(fromFeature)) {
4349
+ this.edges.set(fromFeature, /* @__PURE__ */ new Map());
4350
+ }
4351
+ this.edges.get(fromFeature).set(toFeature, evidence);
4352
+ const reverse = this.edges.get(toFeature)?.get(fromFeature);
4353
+ if (reverse) {
4354
+ return { createsCycle: true, directPartner: reverse, cycleChain: [fromFeature, toFeature, fromFeature] };
4355
+ }
4356
+ const cycle = this.findCycleFrom(fromFeature);
4357
+ if (cycle && cycle.length > 0) {
4358
+ return { createsCycle: true, cycleChain: cycle };
4359
+ }
4360
+ return { createsCycle: false };
4361
+ }
4362
+ getEdges() {
4363
+ const list = [];
4364
+ for (const [from, toMap] of this.edges.entries()) {
4365
+ for (const [to, ev] of toMap.entries()) {
4366
+ list.push({ from, to, evidence: ev });
4367
+ }
4368
+ }
4369
+ return list;
4370
+ }
4371
+ findDirectCycles() {
4372
+ const cycles = [];
4373
+ const checkedPairs = /* @__PURE__ */ new Set();
4374
+ for (const [from, toMap] of this.edges.entries()) {
4375
+ for (const [to, evAtoB] of toMap.entries()) {
4376
+ const pairKey = [from, to].sort().join("<->");
4377
+ if (checkedPairs.has(pairKey)) continue;
4378
+ const evBtoA = this.edges.get(to)?.get(from);
4379
+ if (evBtoA) {
4380
+ checkedPairs.add(pairKey);
4381
+ cycles.push({
4382
+ featureA: from,
4383
+ featureB: to,
4384
+ cycle: [from, to, from],
4385
+ evidenceAtoB: evAtoB,
4386
+ evidenceBtoA: evBtoA
4387
+ });
4388
+ }
4389
+ }
4390
+ }
4391
+ return cycles;
4392
+ }
4393
+ findCycleFrom(start) {
4394
+ const visited = /* @__PURE__ */ new Set();
4395
+ const inStack = /* @__PURE__ */ new Set();
4396
+ const parent = /* @__PURE__ */ new Map();
4397
+ const dfs = (curr) => {
4398
+ visited.add(curr);
4399
+ inStack.add(curr);
4400
+ const neighbors = this.edges.get(curr);
4401
+ if (neighbors) {
4402
+ for (const next of neighbors.keys()) {
4403
+ if (!visited.has(next)) {
4404
+ parent.set(next, curr);
4405
+ const cycle = dfs(next);
4406
+ if (cycle) return cycle;
4407
+ } else if (inStack.has(next)) {
4408
+ const cycle = [next];
4409
+ let p16 = curr;
4410
+ while (p16 !== next) {
4411
+ cycle.push(p16);
4412
+ p16 = parent.get(p16) || next;
4413
+ }
4414
+ cycle.push(next);
4415
+ cycle.reverse();
4416
+ return cycle;
4417
+ }
4418
+ }
4419
+ }
4420
+ inStack.delete(curr);
4421
+ return null;
4422
+ };
4423
+ return dfs(start);
4424
+ }
4425
+ };
4426
+ function discoverAllFeatures(projectRoot) {
4427
+ const featuresBase = resolveFeaturesDir(projectRoot);
4428
+ if (!fs9__default.default.existsSync(featuresBase)) return [];
4429
+ const entries = fs9__default.default.readdirSync(featuresBase, { withFileTypes: true });
4430
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "__tests__" && e.name !== "tests").map((e) => ({
4431
+ name: e.name,
4432
+ dir: path2__default.default.join(featuresBase, e.name)
4433
+ })).sort((a, b) => a.name.localeCompare(b.name));
4434
+ }
4435
+ function lintCodebase(options = {}) {
4436
+ const startTime = Date.now();
4437
+ const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
4438
+ const failFast = !options.complete;
4439
+ const features = discoverAllFeatures(projectRoot);
4440
+ const graph = new FeatureDependencyGraph();
4441
+ const allViolations = [];
4442
+ const results = [];
4443
+ const detectedCycles = [];
4444
+ const addViolation = (v) => {
4445
+ allViolations.push(v);
4446
+ const featureResult = results.find((r) => r.featureName === v.feature);
4447
+ if (featureResult) {
4448
+ featureResult.violations.push(v);
4449
+ featureResult.passed = false;
4450
+ }
4451
+ if (failFast) {
4452
+ const errorMsg = `[FAIL FAST] Dependency rule violation detected:
4453
+ Feature: ${v.feature}
4454
+ Category: ${v.category}
4455
+ Rule: ${v.ruleId}
4456
+ Location: ${v.relativePath}:${v.line}
4457
+ Code: ${v.code}
4458
+ Explanation: ${v.advice}`;
4459
+ throw new DependencyRuleError(errorMsg, [v], true);
4460
+ }
4461
+ };
4462
+ for (const feat of features) {
4463
+ results.push({
4464
+ featureName: feat.name,
4465
+ featureDir: feat.dir,
4466
+ scannedFilesCount: 0,
4467
+ passed: true,
4468
+ violations: [],
4469
+ layersChecked: ["domain", "useCases", "services", "infrastructure", "presentation"]
4470
+ });
4471
+ }
4472
+ for (const feat of features) {
4473
+ const featResult = results.find((r) => r.featureName === feat.name);
4474
+ const allFiles = scanDirFiles(feat.dir, projectRoot);
4475
+ featResult.scannedFilesCount = allFiles.length;
4476
+ for (const file of allFiles) {
4477
+ const relPath = path2__default.default.relative(projectRoot, file).replace(/\\/g, "/");
4478
+ const { layer } = classifyFile(file, projectRoot);
4479
+ if (!layer) continue;
4480
+ const content = fs9__default.default.readFileSync(file, "utf-8");
4481
+ const statements = extractImportStatements(content);
4482
+ if (layer === "domain") {
4483
+ const lines = content.split(/\r?\n/);
4484
+ for (let i = 0; i < lines.length; i++) {
4485
+ const lineText = lines[i].trim();
4486
+ if (lineText.includes("@MakeInjectable") || lineText.includes("public static deps =")) {
4487
+ addViolation({
4488
+ feature: feat.name,
4489
+ layer,
4490
+ file,
4491
+ relativePath: relPath,
4492
+ line: i + 1,
4493
+ category: "Domain Dependence Violation",
4494
+ code: lineText,
4495
+ ruleId: "D2_DI_DEPENDENCE",
4496
+ severity: "error",
4497
+ advice: "Domain layer must have zero DI dependence. Remove @MakeInjectable decorators and static deps declarations.",
4498
+ fixExample: {
4499
+ bad: lineText,
4500
+ good: "// Domain interfaces and value objects must remain plain TypeScript classes/interfaces."
4501
+ }
4502
+ });
4503
+ }
4504
+ }
4505
+ }
4506
+ for (const stmt of statements) {
4507
+ const target = classifyTarget(stmt.specifier, file, projectRoot);
4508
+ if (target.targetFeature && target.targetFeature !== feat.name) {
4509
+ const evidence = {
4510
+ fromFeature: feat.name,
4511
+ toFeature: target.targetFeature,
4512
+ file,
4513
+ relativePath: relPath,
4514
+ line: stmt.line,
4515
+ code: stmt.rawCode
4516
+ };
4517
+ const cycleResult = graph.addEdge(evidence);
4518
+ if (cycleResult.createsCycle) {
4519
+ const partner = cycleResult.directPartner;
4520
+ detectedCycles.push({
4521
+ featureA: feat.name,
4522
+ featureB: target.targetFeature,
4523
+ cycle: cycleResult.cycleChain,
4524
+ evidenceAtoB: evidence,
4525
+ evidenceBtoA: partner || evidence
4526
+ });
4527
+ addViolation({
4528
+ feature: feat.name,
4529
+ layer,
4530
+ file,
4531
+ relativePath: relPath,
4532
+ line: stmt.line,
4533
+ category: "Circular Feature Dependency Violation",
4534
+ code: stmt.rawCode,
4535
+ ruleId: "C1_CIRCULAR_FEATURE_DEPENDENCY",
4536
+ severity: "error",
4537
+ advice: `Circular feature dependency detected between '${feat.name}' and '${target.targetFeature}'. Dependency flow must be strictly one-way: for any two features, only one can know about the other exists. If '${feat.name}' needs to impose a side effect on '${target.targetFeature}' (or vice versa), it must do so via an event, never via a direct dependency.`,
4538
+ fixExample: {
4539
+ bad: stmt.rawCode,
4540
+ good: `// Emit a Domain/Application Event (e.g. publish '${feat.name}Changed') instead of depending on '${target.targetFeature}'.`
4541
+ }
4542
+ });
4543
+ }
4544
+ }
4545
+ if (layer === "domain") {
4546
+ if (target.targetLayer && ["useCases", "services", "infrastructure", "presentation"].includes(target.targetLayer)) {
4547
+ addViolation({
4548
+ feature: feat.name,
4549
+ layer,
4550
+ file,
4551
+ relativePath: relPath,
4552
+ line: stmt.line,
4553
+ category: "Domain Purity Violation",
4554
+ code: stmt.rawCode,
4555
+ ruleId: "D1_OUTER_IMPORT",
4556
+ severity: "error",
4557
+ advice: `Domain layer must not import from outer layers (${target.targetLayer}). Keep the domain core pure.`,
4558
+ fixExample: {
4559
+ bad: stmt.rawCode,
4560
+ good: "// Define interfaces, types, or value objects directly within the domain layer."
4561
+ }
4562
+ });
4563
+ }
4564
+ if (target.isDiFramework) {
4565
+ addViolation({
4566
+ feature: feat.name,
4567
+ layer,
4568
+ file,
4569
+ relativePath: relPath,
4570
+ line: stmt.line,
4571
+ category: "Domain Dependence Violation",
4572
+ code: stmt.rawCode,
4573
+ ruleId: "D2_DI_DEPENDENCE",
4574
+ severity: "error",
4575
+ advice: "Domain layer must have zero DI dependence. Domain interfaces and value objects are plain classes/types.",
4576
+ fixExample: {
4577
+ bad: stmt.rawCode,
4578
+ good: "// Remove @solid-stack/di import. Domain entities are pure TypeScript."
4579
+ }
4580
+ });
4581
+ }
4582
+ if (target.targetSharedModule && !target.isSharedDomain) {
4583
+ addViolation({
4584
+ feature: feat.name,
4585
+ layer,
4586
+ file,
4587
+ relativePath: relPath,
4588
+ line: stmt.line,
4589
+ category: "Domain Purity Violation",
4590
+ code: stmt.rawCode,
4591
+ ruleId: "D3_SHARED_NON_DOMAIN_IMPORT",
4592
+ severity: "error",
4593
+ advice: "Domain layer can only import pure value objects from shared/<module>/domain/*. Ports, infrastructure, and root classes must not be imported.",
4594
+ fixExample: {
4595
+ bad: stmt.rawCode,
4596
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/domain/...";`
4597
+ }
4598
+ });
4599
+ }
4600
+ if (target.isExternal && !target.isDiFramework) {
4601
+ addViolation({
4602
+ feature: feat.name,
4603
+ layer,
4604
+ file,
4605
+ relativePath: relPath,
4606
+ line: stmt.line,
4607
+ category: "Domain Purity Violation",
4608
+ code: stmt.rawCode,
4609
+ ruleId: "D4_EXTERNAL_LIBRARY",
4610
+ severity: "error",
4611
+ advice: `Domain layer must be pure and must not depend on external runtime libraries ('${stmt.specifier}').`,
4612
+ fixExample: {
4613
+ bad: stmt.rawCode,
4614
+ good: "// Model pure business interfaces without external libraries."
4615
+ }
4616
+ });
4617
+ }
4618
+ } else if (layer === "useCases") {
4619
+ if (target.targetLayer === "useCases" && target.targetFeature === feat.name) {
4620
+ addViolation({
4621
+ feature: feat.name,
4622
+ layer,
4623
+ file,
4624
+ relativePath: relPath,
4625
+ line: stmt.line,
4626
+ category: "Use Case Intra-Feature Violation",
4627
+ code: stmt.rawCode,
4628
+ ruleId: "U1_INTRA_USE_CASE",
4629
+ severity: "error",
4630
+ advice: `Use cases cannot depend on other use cases of the same feature ('${feat.name}'). Extract shared business logic to a domain repository or domain service.`,
4631
+ fixExample: {
4632
+ bad: stmt.rawCode,
4633
+ good: "// Inject a domain interface or domain service instead of another use case."
4634
+ }
4635
+ });
4636
+ }
4637
+ if (target.targetLayer === "useCases" && target.targetFeature && target.targetFeature !== feat.name) {
4638
+ addViolation({
4639
+ feature: feat.name,
4640
+ layer,
4641
+ file,
4642
+ relativePath: relPath,
4643
+ line: stmt.line,
4644
+ category: "Use Case Cross-Feature Use Case Violation",
4645
+ code: stmt.rawCode,
4646
+ ruleId: "U2_CROSS_FEATURE_USE_CASE",
4647
+ severity: "error",
4648
+ advice: `Use cases cannot depend directly on use cases of foreign feature '${target.targetFeature}'. Treat features as separate microservices: define an interface in '${feat.name}/domain/' specifying what is needed, and implement it in '${feat.name}/infrastructure/'.`,
4649
+ fixExample: {
4650
+ bad: stmt.rawCode,
4651
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/ using '${target.targetFileName}'.`
4652
+ }
4653
+ });
4654
+ }
4655
+ if (target.targetLayer === "infrastructure") {
4656
+ addViolation({
4657
+ feature: feat.name,
4658
+ layer,
4659
+ file,
4660
+ relativePath: relPath,
4661
+ line: stmt.line,
4662
+ category: "Use Case Layer Violation",
4663
+ code: stmt.rawCode,
4664
+ ruleId: "U3_DIRECT_INFRASTRUCTURE",
4665
+ severity: "error",
4666
+ advice: "Use cases cannot import concrete infrastructure directly. Depend on Domain Ports of the same feature via DI.",
4667
+ fixExample: {
4668
+ bad: stmt.rawCode,
4669
+ good: "// Depend on the domain interface port declared in static deps."
4670
+ }
4671
+ });
4672
+ }
4673
+ if (target.targetLayer === "domain" && target.targetFeature && target.targetFeature !== feat.name && target.isTargetPort) {
4674
+ addViolation({
4675
+ feature: feat.name,
4676
+ layer,
4677
+ file,
4678
+ relativePath: relPath,
4679
+ line: stmt.line,
4680
+ category: "Use Case Cross-Feature Port Violation",
4681
+ code: stmt.rawCode,
4682
+ ruleId: "U4_CROSS_FEATURE_PORT",
4683
+ severity: "error",
4684
+ advice: `Use cases cannot depend on domain interfaces/ports of foreign feature '${target.targetFeature}'. Define an interface in '${feat.name}/domain/' (consumer-driven port) and implement it in '${feat.name}/infrastructure/'.`,
4685
+ fixExample: {
4686
+ bad: stmt.rawCode,
4687
+ good: `// Define a local interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4688
+ }
4689
+ });
4690
+ }
4691
+ if (target.targetLayer === "services" && target.targetFeature && target.targetFeature !== feat.name) {
4692
+ addViolation({
4693
+ feature: feat.name,
4694
+ layer,
4695
+ file,
4696
+ relativePath: relPath,
4697
+ line: stmt.line,
4698
+ category: "Use Case Layer Violation",
4699
+ code: stmt.rawCode,
4700
+ ruleId: "U5_CROSS_FEATURE_SERVICE",
4701
+ severity: "error",
4702
+ advice: `Use cases cannot depend on services of foreign feature '${target.targetFeature}'.`,
4703
+ fixExample: {
4704
+ bad: stmt.rawCode,
4705
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4706
+ }
4707
+ });
4708
+ }
4709
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4710
+ addViolation({
4711
+ feature: feat.name,
4712
+ layer,
4713
+ file,
4714
+ relativePath: relPath,
4715
+ line: stmt.line,
4716
+ category: "Shared Module Root Class Violation",
4717
+ code: stmt.rawCode,
4718
+ ruleId: "U6_SHARED_NON_ROOT",
4719
+ severity: "error",
4720
+ advice: `Use cases can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.`,
4721
+ fixExample: {
4722
+ bad: stmt.rawCode,
4723
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4724
+ }
4725
+ });
4726
+ }
4727
+ } else if (layer === "services") {
4728
+ if (target.targetLayer === "useCases") {
4729
+ addViolation({
4730
+ feature: feat.name,
4731
+ layer,
4732
+ file,
4733
+ relativePath: relPath,
4734
+ line: stmt.line,
4735
+ category: "Service Intra-Feature Violation",
4736
+ code: stmt.rawCode,
4737
+ ruleId: "S1_USE_CASE_DEPENDENCY",
4738
+ severity: "error",
4739
+ advice: "Services cannot depend on use cases of any feature.",
4740
+ fixExample: {
4741
+ bad: stmt.rawCode,
4742
+ good: "// Inject domain repository ports directly instead of use cases."
4743
+ }
4744
+ });
4745
+ }
4746
+ if (target.targetLayer === "domain" && target.targetFeature && target.targetFeature !== feat.name && target.isTargetPort) {
4747
+ addViolation({
4748
+ feature: feat.name,
4749
+ layer,
4750
+ file,
4751
+ relativePath: relPath,
4752
+ line: stmt.line,
4753
+ category: "Service Cross-Feature Port Violation",
4754
+ code: stmt.rawCode,
4755
+ ruleId: "S2_CROSS_FEATURE_PORT",
4756
+ severity: "error",
4757
+ advice: `Services cannot depend on domain ports of foreign feature '${target.targetFeature}'.`,
4758
+ fixExample: {
4759
+ bad: stmt.rawCode,
4760
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4761
+ }
4762
+ });
4763
+ }
4764
+ if (target.targetLayer === "infrastructure") {
4765
+ addViolation({
4766
+ feature: feat.name,
4767
+ layer,
4768
+ file,
4769
+ relativePath: relPath,
4770
+ line: stmt.line,
4771
+ category: "Service Layer Violation",
4772
+ code: stmt.rawCode,
4773
+ ruleId: "S3_DIRECT_INFRASTRUCTURE",
4774
+ severity: "error",
4775
+ advice: "Services cannot import concrete infrastructure directly. Depend on Domain Ports via DI.",
4776
+ fixExample: {
4777
+ bad: stmt.rawCode,
4778
+ good: "// Depend on the domain interface port declared in static deps."
4779
+ }
4780
+ });
4781
+ }
4782
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4783
+ addViolation({
4784
+ feature: feat.name,
4785
+ layer,
4786
+ file,
4787
+ relativePath: relPath,
4788
+ line: stmt.line,
4789
+ category: "Shared Module Root Class Violation",
4790
+ code: stmt.rawCode,
4791
+ ruleId: "S4_SHARED_NON_ROOT",
4792
+ severity: "error",
4793
+ advice: "Services can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4794
+ fixExample: {
4795
+ bad: stmt.rawCode,
4796
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4797
+ }
4798
+ });
4799
+ }
4800
+ } else if (layer === "infrastructure") {
4801
+ if (target.targetLayer === "useCases" && target.targetFeature === feat.name) {
4802
+ addViolation({
4803
+ feature: feat.name,
4804
+ layer,
4805
+ file,
4806
+ relativePath: relPath,
4807
+ line: stmt.line,
4808
+ category: "Adapter Layer Violation",
4809
+ code: stmt.rawCode,
4810
+ ruleId: "I1_INTRA_USE_CASE",
4811
+ severity: "error",
4812
+ advice: `Infrastructure adapters cannot depend on use cases of the same feature ('${feat.name}'). Adapters implement domain ports.`,
4813
+ fixExample: {
4814
+ bad: stmt.rawCode,
4815
+ good: "// Adapters implement domain ports and should not invoke local application use cases."
4816
+ }
4817
+ });
4818
+ }
4819
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4820
+ addViolation({
4821
+ feature: feat.name,
4822
+ layer,
4823
+ file,
4824
+ relativePath: relPath,
4825
+ line: stmt.line,
4826
+ category: "Shared Module Root Class Violation",
4827
+ code: stmt.rawCode,
4828
+ ruleId: "I2_SHARED_NON_ROOT",
4829
+ severity: "error",
4830
+ advice: "Infrastructure adapters can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4831
+ fixExample: {
4832
+ bad: stmt.rawCode,
4833
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4834
+ }
4835
+ });
4836
+ }
4837
+ } else if (layer === "presentation") {
4838
+ if (target.targetLayer === "infrastructure") {
4839
+ addViolation({
4840
+ feature: feat.name,
4841
+ layer,
4842
+ file,
4843
+ relativePath: relPath,
4844
+ line: stmt.line,
4845
+ category: "Presentation Layer Violation",
4846
+ code: stmt.rawCode,
4847
+ ruleId: "P1_DIRECT_INFRASTRUCTURE",
4848
+ severity: "error",
4849
+ advice: "Presentation handlers must not import infrastructure directly. Inject Use Cases via DI.",
4850
+ fixExample: {
4851
+ bad: stmt.rawCode,
4852
+ good: "// Inject the corresponding Use Case via static deps and invoke its execute() method."
4853
+ }
4854
+ });
4855
+ }
4856
+ if (target.targetLayer === "domain" && (target.isTargetRepo || target.isTargetPort)) {
4857
+ addViolation({
4858
+ feature: feat.name,
4859
+ layer,
4860
+ file,
4861
+ relativePath: relPath,
4862
+ line: stmt.line,
4863
+ category: "Presentation Layer Violation",
4864
+ code: stmt.rawCode,
4865
+ ruleId: "P2_DIRECT_REPOSITORY",
4866
+ severity: "error",
4867
+ advice: "Presentation handlers must not depend on domain repositories or ports directly. Inject Use Cases via DI.",
4868
+ fixExample: {
4869
+ bad: stmt.rawCode,
4870
+ good: "// Presentation layer must only orchestrate Use Cases."
4871
+ }
4872
+ });
4873
+ }
4874
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4875
+ addViolation({
4876
+ feature: feat.name,
4877
+ layer,
4878
+ file,
4879
+ relativePath: relPath,
4880
+ line: stmt.line,
4881
+ category: "Shared Module Root Class Violation",
4882
+ code: stmt.rawCode,
4883
+ ruleId: "P3_SHARED_NON_ROOT",
4884
+ severity: "error",
4885
+ advice: "Presentation handlers can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4886
+ fixExample: {
4887
+ bad: stmt.rawCode,
4888
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4889
+ }
4890
+ });
4891
+ }
4892
+ }
4893
+ }
4894
+ }
4895
+ }
4896
+ const directCycles = graph.findDirectCycles();
4897
+ for (const c of directCycles) {
4898
+ const existing = detectedCycles.find(
4899
+ (dc) => dc.featureA === c.featureA && dc.featureB === c.featureB || dc.featureA === c.featureB && dc.featureB === c.featureA
4900
+ );
4901
+ if (!existing) {
4902
+ detectedCycles.push(c);
4903
+ addViolation({
4904
+ feature: c.featureA,
4905
+ file: c.evidenceAtoB.file,
4906
+ relativePath: c.evidenceAtoB.relativePath,
4907
+ line: c.evidenceAtoB.line,
4908
+ category: "Circular Feature Dependency Violation",
4909
+ code: c.evidenceAtoB.code,
4910
+ ruleId: "C1_CIRCULAR_FEATURE_DEPENDENCY",
4911
+ severity: "error",
4912
+ advice: `Circular feature dependency detected between '${c.featureA}' and '${c.featureB}'. Dependency flow must be strictly one-way: for any two features, only one can know about the other exists. If the unaware feature needs to impose a side effect on the other, it must do so via an event, never via a direct dependency.`
4913
+ });
4914
+ }
4915
+ }
4916
+ const durationMs = Date.now() - startTime;
4917
+ const cleanFeatures = results.filter((r) => r.passed).length;
4918
+ const violatedFeatures = results.filter((r) => !r.passed).length;
4919
+ const summary = {
4920
+ totalFeatures: results.length,
4921
+ cleanFeatures,
4922
+ violatedFeatures,
4923
+ totalViolations: allViolations.length,
4924
+ durationMs,
4925
+ results,
4926
+ allViolations,
4927
+ circularDependencies: detectedCycles
4928
+ };
4929
+ if (allViolations.length > 0 && options.throwOnError !== false) {
4930
+ const errorHeader = `Dependency rule audit failed with ${allViolations.length} violation(s) across ${violatedFeatures} feature(s):`;
4931
+ const compiledDetails = allViolations.map(
4932
+ (v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
4933
+ Rule: ${v.ruleId}
4934
+ Code: ${v.code}
4935
+ Advice: ${v.advice}`
4936
+ ).join("\n\n");
4937
+ throw new DependencyRuleError(`${errorHeader}
4938
+
4939
+ ${compiledDetails}
4940
+ `, allViolations, false);
4941
+ }
4942
+ return summary;
4943
+ }
4944
+
4945
+ // src/commands/lint.ts
4946
+ function printLintBanner(mode) {
4947
+ console.log("");
4948
+ const title = " Mason Clean Architecture Dependency Linter ";
4949
+ const line = "\u2501".repeat(title.length + 2);
4950
+ console.log(pc15__default.default.cyan(` \u250F${line}\u2513`));
4951
+ console.log(` ${pc15__default.default.cyan("\u2503")} ${pc15__default.default.bgCyan(pc15__default.default.black(title))} ${pc15__default.default.cyan("\u2503")}`);
4952
+ console.log(pc15__default.default.cyan(` \u2517${line}\u251B`));
4953
+ const modeText = mode === "failFast" ? `${pc15__default.default.yellow("Fail-Fast")} ${pc15__default.default.dim("(default: stops at first error)")}` : `${pc15__default.default.magenta("Complete")} ${pc15__default.default.dim("(--complete: compiles all errors)")}`;
4954
+ console.log(` Mode: ${modeText}`);
4955
+ console.log("");
4956
+ }
4957
+ function formatViolation(v, showFixHint = true) {
4958
+ const lines = [];
4959
+ const categoryBadge = pc15__default.default.red(pc15__default.default.bold(`[${v.category}]`));
4960
+ const location = pc15__default.default.white(`${v.relativePath}:${pc15__default.default.yellow(String(v.line))}`);
4961
+ lines.push(` ${categoryBadge} ${location}`);
4962
+ lines.push(` ${pc15__default.default.dim("Rule ID:")} ${pc15__default.default.yellow(v.ruleId)}`);
4963
+ lines.push(` ${pc15__default.default.dim("Code:")} ${pc15__default.default.magenta(v.code)}`);
4964
+ lines.push(` ${pc15__default.default.dim("Advice:")} ${pc15__default.default.cyan(v.advice)}`);
4965
+ if (showFixHint && v.fixExample) {
4966
+ lines.push(` ${pc15__default.default.dim("Example:")}`);
4967
+ lines.push(` ${pc15__default.default.red(`- ${v.fixExample.bad}`)}`);
4968
+ lines.push(` ${pc15__default.default.green(`+ ${v.fixExample.good}`)}`);
4969
+ }
4970
+ return lines.join("\n");
4971
+ }
4972
+ function formatFeatureResult(res, options = {}) {
4973
+ const lines = [];
4974
+ if (res.passed) {
4975
+ lines.push(
4976
+ ` ${pc15__default.default.green("\u2714")} ${pc15__default.default.bold(res.featureName)} ${pc15__default.default.dim(
4977
+ `(${res.scannedFilesCount} files checked - clean)`
4978
+ )}`
4979
+ );
4980
+ return lines.join("\n");
4981
+ }
4982
+ const countBadge = pc15__default.default.red(
4983
+ `(${res.violations.length} violation${res.violations.length > 1 ? "s" : ""})`
4984
+ );
4985
+ lines.push(`
4986
+ ${pc15__default.default.red("\u2716")} ${pc15__default.default.bold(pc15__default.default.white(res.featureName))} ${countBadge}`);
4987
+ lines.push(` ${pc15__default.default.dim("Path:")} ${res.featureDir}`);
4988
+ lines.push("");
4989
+ for (const v of res.violations) {
4990
+ lines.push(formatViolation(v, options.fixHint !== false));
4991
+ lines.push("");
4992
+ }
4993
+ return lines.join("\n");
4994
+ }
4995
+ function printTerminalReport(summary, options = {}) {
4996
+ for (const res of summary.results) {
4997
+ const formatted = formatFeatureResult(res, options);
4998
+ if (formatted.trim()) {
4999
+ console.log(formatted);
5000
+ }
5001
+ }
5002
+ console.log("\u2501".repeat(70));
5003
+ const statusIcon = summary.totalViolations === 0 ? pc15__default.default.green("\u2714 PASS") : pc15__default.default.red("\u2716 FAIL");
5004
+ console.log(
5005
+ ` ${statusIcon} ${pc15__default.default.bold("Dependency Rules Audit Result")} ${pc15__default.default.dim(
5006
+ `(${summary.durationMs}ms)`
5007
+ )}`
5008
+ );
5009
+ console.log(
5010
+ ` ${pc15__default.default.bold("Features:")} ${pc15__default.default.cyan(String(summary.totalFeatures))} total | ${pc15__default.default.green(
5011
+ String(summary.cleanFeatures)
5012
+ )} clean | ${summary.violatedFeatures > 0 ? pc15__default.default.red(String(summary.violatedFeatures)) : pc15__default.default.green("0")} with violations`
5013
+ );
5014
+ console.log(
5015
+ ` ${pc15__default.default.bold("Violations:")} ${summary.totalViolations > 0 ? pc15__default.default.red(pc15__default.default.bold(String(summary.totalViolations))) : pc15__default.default.green("0")} rule violations found`
5016
+ );
5017
+ console.log("\u2501".repeat(70));
5018
+ console.log("");
5019
+ }
5020
+ async function handleLint(flags = {}) {
5021
+ const complete = Boolean(flags.complete);
5022
+ const mode = complete ? "complete" : "failFast";
5023
+ const quiet = Boolean(flags.quiet);
5024
+ if (!quiet) {
5025
+ printLintBanner(mode);
5026
+ }
5027
+ const options = {
5028
+ complete,
5029
+ quiet,
5030
+ targetDir: flags.targetDir || flags.target,
5031
+ projectRoot: flags.projectRoot,
5032
+ fixHint: flags.fixHint !== false,
5033
+ throwOnError: false
5034
+ };
5035
+ try {
5036
+ const summary = lintCodebase(options);
5037
+ if (!quiet) {
5038
+ printTerminalReport(summary, options);
5039
+ }
5040
+ if (summary.totalViolations > 0) {
5041
+ const errorHeader = `Dependency rule audit failed with ${summary.totalViolations} violation(s) across ${summary.violatedFeatures} feature(s):`;
5042
+ const compiledDetails = summary.allViolations.map(
5043
+ (v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
5044
+ Rule: ${v.ruleId}
5045
+ Code: ${v.code}
5046
+ Advice: ${v.advice}`
5047
+ ).join("\n\n");
5048
+ throw new DependencyRuleError(`${errorHeader}
5049
+
5050
+ ${compiledDetails}
5051
+ `, summary.allViolations, false);
5052
+ }
5053
+ if (!quiet) {
5054
+ console.log(
5055
+ pc15__default.default.green(`\u2714 All dependency rules are being followed! (${summary.totalFeatures} features checked)`)
5056
+ );
5057
+ console.log("");
5058
+ }
5059
+ return summary;
5060
+ } catch (err) {
5061
+ if (err instanceof DependencyRuleError && err.isFailFast) {
5062
+ if (!quiet) {
5063
+ console.log(pc15__default.default.red(`
5064
+ \u2716 [FAIL FAST] Terminating on first detected dependency rule violation:`));
5065
+ const v = err.violations[0];
5066
+ if (v) {
5067
+ console.log(formatViolation(v, options.fixHint !== false));
5068
+ }
5069
+ console.log("");
5070
+ }
5071
+ }
5072
+ throw err;
5073
+ }
5074
+ }
4192
5075
 
4193
5076
  // src/cli.ts
4194
5077
  function createCli() {
@@ -4196,7 +5079,7 @@ function createCli() {
4196
5079
  cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").option("--point-to <mapping>", "Point a dependency to an existing local module (e.g. --point-to time:mytime)").option("--alias <mapping>", "Import a dependency fresh under an alias name (e.g. --alias time:time-mason)").option("--tests", "Include test files (use --no-tests to exclude)").action(async (args, flags) => {
4197
5080
  if (args && args.length > 0) {
4198
5081
  p14__namespace.log.error(
4199
- pc14__default.default.red(
5082
+ pc15__default.default.red(
4200
5083
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
4201
5084
  )
4202
5085
  );
@@ -4262,14 +5145,21 @@ function createCli() {
4262
5145
  value: "import",
4263
5146
  label: "Import from registry",
4264
5147
  hint: "Download and link pre-built shared modules or features from the registry"
5148
+ },
5149
+ {
5150
+ value: "lint",
5151
+ label: "Lint dependency rules",
5152
+ hint: "Scan codebase and verify Clean Architecture dependency rules"
4265
5153
  }
4266
5154
  ]
4267
5155
  })
4268
5156
  );
4269
5157
  if (choice === "create") {
4270
5158
  await handleCreate(void 0, void 0, flags);
4271
- } else {
5159
+ } else if (choice === "import") {
4272
5160
  await handleImport(void 0, void 0, flags);
5161
+ } else if (choice === "lint") {
5162
+ await handleLint(flags);
4273
5163
  }
4274
5164
  } catch (err) {
4275
5165
  handleCliError(err);
@@ -4295,6 +5185,22 @@ function createCli() {
4295
5185
  handleCliError(err);
4296
5186
  }
4297
5187
  });
5188
+ cli.command(
5189
+ "lint [target]",
5190
+ "Scan codebase and verify Clean Architecture dependency rules"
5191
+ ).option(
5192
+ "-c, --complete",
5193
+ "Scan entire codebase without failing fast, compiling all errors at the end"
5194
+ ).option("-q, --quiet", "Suppress non-error output").example("mason lint").example("mason lint --complete").example("mason lint ./src").action(async (target, flags = {}) => {
5195
+ try {
5196
+ await handleLint({
5197
+ ...flags,
5198
+ targetDir: target || flags.target || flags.dest
5199
+ });
5200
+ } catch (err) {
5201
+ handleCliError(err);
5202
+ }
5203
+ });
4298
5204
  const createSubtypes = [
4299
5205
  "feature",
4300
5206
  "shared",
@@ -4326,7 +5232,10 @@ function handleCliError(err) {
4326
5232
  process.exit(0);
4327
5233
  }
4328
5234
  const message = err instanceof Error ? err.message : String(err);
4329
- p14__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
5235
+ p14__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
5236
+ if (process.env.VITEST) {
5237
+ throw err;
5238
+ }
4330
5239
  process.exit(1);
4331
5240
  }
4332
5241
  async function runCli(argv = process.argv) {