@solid-stack/mason 1.0.21 → 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/index.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  var fs4 = require('fs');
4
4
  var path2 = require('path');
5
5
  var p14 = require('@clack/prompts');
6
- var pc14 = require('picocolors');
6
+ var pc15 = require('picocolors');
7
7
  var url = require('url');
8
8
  var child_process = require('child_process');
9
9
  var crypto = require('crypto');
@@ -34,7 +34,7 @@ function _interopNamespace(e) {
34
34
  var fs4__default = /*#__PURE__*/_interopDefault(fs4);
35
35
  var path2__default = /*#__PURE__*/_interopDefault(path2);
36
36
  var p14__namespace = /*#__PURE__*/_interopNamespace(p14);
37
- var pc14__default = /*#__PURE__*/_interopDefault(pc14);
37
+ var pc15__default = /*#__PURE__*/_interopDefault(pc15);
38
38
  var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
39
39
 
40
40
  // src/utils/formatting.ts
@@ -141,17 +141,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
141
141
  function printGeneratedFiles(files, isDryRun = false) {
142
142
  console.log("");
143
143
  if (isDryRun) {
144
- p14__namespace.log.warn(pc14__default.default.yellow(pc14__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
144
+ p14__namespace.log.warn(pc15__default.default.yellow(pc15__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
145
145
  }
146
146
  for (const f of files) {
147
147
  if (f.action === "created") {
148
- p14__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
148
+ p14__namespace.log.success(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(created)")}`);
149
149
  } else if (f.action === "overwritten") {
150
- p14__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
150
+ p14__namespace.log.warn(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(overwritten)")}`);
151
151
  } else if (f.action === "skipped") {
152
- p14__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
152
+ p14__namespace.log.info(`${pc15__default.default.dim(f.relativePath)} ${pc15__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
153
153
  } else {
154
- p14__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(preview)")}`);
154
+ p14__namespace.log.info(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(preview)")}`);
155
155
  }
156
156
  }
157
157
  }
@@ -361,13 +361,13 @@ function handleCancel(result) {
361
361
  }
362
362
  function printBanner() {
363
363
  console.log("");
364
- p14__namespace.intro(pc14__default.default.bgCyan(pc14__default.default.black(" @solid-stack/mason ")));
364
+ p14__namespace.intro(pc15__default.default.bgCyan(pc15__default.default.black(" @solid-stack/mason ")));
365
365
  }
366
366
  async function promptProperties(options = {}) {
367
367
  const properties = options.defaults ? [...options.defaults] : [];
368
368
  if (properties.length > 0) {
369
369
  const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
370
- p14__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
370
+ p14__namespace.log.info(pc15__default.default.dim(`Pre-configured fields: ${summary}`));
371
371
  const addMore = handleCancel(
372
372
  await p14__namespace.confirm({
373
373
  message: "Would you like to add custom fields/properties?",
@@ -378,7 +378,7 @@ async function promptProperties(options = {}) {
378
378
  return properties;
379
379
  }
380
380
  }
381
- p14__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
381
+ p14__namespace.log.info(pc15__default.default.cyan("Enter properties (leave name blank when finished):"));
382
382
  while (true) {
383
383
  const name = handleCancel(
384
384
  await p14__namespace.text({
@@ -426,7 +426,7 @@ async function promptProperties(options = {}) {
426
426
  optional
427
427
  });
428
428
  p14__namespace.log.success(
429
- pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
429
+ pc15__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
430
430
  );
431
431
  }
432
432
  return properties;
@@ -660,7 +660,7 @@ function installNpmPackages(projectRoot, packages, pm) {
660
660
  installCmd = `npm install ${packages.join(" ")}`;
661
661
  break;
662
662
  }
663
- p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
663
+ p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
664
664
  child_process.execSync(installCmd, {
665
665
  cwd: projectRoot,
666
666
  stdio: "inherit"
@@ -2377,7 +2377,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2377
2377
  }
2378
2378
  }
2379
2379
  const s = p14__namespace.spinner();
2380
- s.start(`Generating feature '${pc14__default.default.cyan(normalizedName)}'...`);
2380
+ s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
2381
2381
  const options = {
2382
2382
  name: normalizedName,
2383
2383
  projectRoot,
@@ -2424,7 +2424,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2424
2424
  s.stop("Feature scaffolded successfully!");
2425
2425
  printGeneratedFiles(files, options.dryRun);
2426
2426
  p14__namespace.outro(
2427
- pc14__default.default.green(
2427
+ pc15__default.default.green(
2428
2428
  `\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2429
2429
  )
2430
2430
  );
@@ -2580,7 +2580,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2580
2580
  );
2581
2581
  }
2582
2582
  const s = p14__namespace.spinner();
2583
- s.start(`Generating shared module '${pc14__default.default.cyan(normalizedName)}'...`);
2583
+ s.start(`Generating shared module '${pc15__default.default.cyan(normalizedName)}'...`);
2584
2584
  const files = generateShared({
2585
2585
  name: normalizedName,
2586
2586
  description,
@@ -2594,7 +2594,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2594
2594
  s.stop("Shared module generated successfully!");
2595
2595
  printGeneratedFiles(files, Boolean(flags.dryRun));
2596
2596
  p14__namespace.outro(
2597
- pc14__default.default.green(
2597
+ pc15__default.default.green(
2598
2598
  `\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2599
2599
  )
2600
2600
  );
@@ -2722,7 +2722,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2722
2722
  }
2723
2723
  }
2724
2724
  const s = p14__namespace.spinner();
2725
- s.start(`Generating use case '${pc14__default.default.cyan(ucName)}'...`);
2725
+ s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
2726
2726
  const options = {
2727
2727
  name: ucName,
2728
2728
  feature,
@@ -2754,7 +2754,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2754
2754
  }
2755
2755
  s.stop("Use case generated successfully!");
2756
2756
  printGeneratedFiles(files, options.dryRun);
2757
- p14__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2757
+ p14__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2758
2758
  }
2759
2759
  async function runDomainCommand(domainNameArg, flags = {}) {
2760
2760
  const projectRoot = findProjectRoot();
@@ -2825,7 +2825,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2825
2825
  });
2826
2826
  }
2827
2827
  const s = p14__namespace.spinner();
2828
- s.start(`Generating domain model '${pc14__default.default.cyan(entityName)}'...`);
2828
+ s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
2829
2829
  const options = {
2830
2830
  name: entityName,
2831
2831
  feature,
@@ -2838,7 +2838,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2838
2838
  const file = generateDomain(options);
2839
2839
  s.stop("Domain model generated successfully!");
2840
2840
  printGeneratedFiles([file], options.dryRun);
2841
- p14__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2841
+ p14__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2842
2842
  }
2843
2843
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2844
2844
  const projectRoot = findProjectRoot();
@@ -3007,7 +3007,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
3007
3007
  registerInProvider = !flags.noProvider;
3008
3008
  }
3009
3009
  const s = p14__namespace.spinner();
3010
- s.start(`Generating infrastructure class '${pc14__default.default.cyan(className)}'...`);
3010
+ s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
3011
3011
  const files = [];
3012
3012
  const options = {
3013
3013
  name: className,
@@ -3042,7 +3042,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
3042
3042
  }
3043
3043
  s.stop("Infrastructure class generated successfully!");
3044
3044
  printGeneratedFiles(files, options.dryRun);
3045
- p14__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
3045
+ p14__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
3046
3046
  }
3047
3047
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
3048
3048
  const projectRoot = findProjectRoot();
@@ -3155,7 +3155,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
3155
3155
  }
3156
3156
  }
3157
3157
  const s = p14__namespace.spinner();
3158
- s.start(`Generating interface '${pc14__default.default.cyan(ifaceName)}'...`);
3158
+ s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
3159
3159
  const files = [];
3160
3160
  const options = {
3161
3161
  name: ifaceName,
@@ -3204,7 +3204,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
3204
3204
  }
3205
3205
  s.stop("Interface generated successfully!");
3206
3206
  printGeneratedFiles(files, options.dryRun);
3207
- p14__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
3207
+ p14__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
3208
3208
  }
3209
3209
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3210
3210
  const projectRoot = findProjectRoot();
@@ -3321,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3321
3321
  }
3322
3322
  }
3323
3323
  const s = p14__namespace.spinner();
3324
- s.start(`Generating HTTP handler '${pc14__default.default.cyan(className)}'...`);
3324
+ s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
3325
3325
  const options = {
3326
3326
  name: className,
3327
3327
  feature,
@@ -3339,7 +3339,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3339
3339
  const files = generateHttpHandler(options);
3340
3340
  s.stop("HTTP handler generated successfully!");
3341
3341
  printGeneratedFiles(files, options.dryRun);
3342
- p14__namespace.outro(pc14__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3342
+ p14__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3343
3343
  }
3344
3344
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
3345
3345
  const formattedDeps = dependencies.map((dep) => ({
@@ -3495,7 +3495,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3495
3495
  );
3496
3496
  }
3497
3497
  const s = p14__namespace.spinner();
3498
- s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
3498
+ s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
3499
3499
  const options = {
3500
3500
  name: className,
3501
3501
  feature,
@@ -3509,7 +3509,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3509
3509
  const files = generateInjectable(options);
3510
3510
  s.stop("Injectable class generated successfully!");
3511
3511
  printGeneratedFiles(files, options.dryRun);
3512
- p14__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
3512
+ p14__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
3513
3513
  }
3514
3514
  async function runInteractiveMenu(flags = {}) {
3515
3515
  printBanner();
@@ -3623,7 +3623,7 @@ async function handleCreate(type, name, flags = {}) {
3623
3623
  break;
3624
3624
  default:
3625
3625
  p14__namespace.log.error(
3626
- pc14__default.default.red(
3626
+ pc15__default.default.red(
3627
3627
  `Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3628
3628
  )
3629
3629
  );
@@ -3743,19 +3743,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3743
3743
  );
3744
3744
  }
3745
3745
  if (type !== "shared" && type !== "feature") {
3746
- p14__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3746
+ p14__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3747
3747
  process.exit(1);
3748
3748
  }
3749
3749
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3750
3750
  const s = p14__namespace.spinner();
3751
- s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
3751
+ s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
3752
3752
  let index;
3753
3753
  try {
3754
3754
  index = await fetchRegistryIndex(registryBase);
3755
3755
  s.stop("Registry index loaded.");
3756
3756
  } catch (err) {
3757
- s.stop(pc14__default.default.red("Failed to load registry index."));
3758
- p14__namespace.log.error(pc14__default.default.red(err.message || String(err)));
3757
+ s.stop(pc15__default.default.red("Failed to load registry index."));
3758
+ p14__namespace.log.error(pc15__default.default.red(err.message || String(err)));
3759
3759
  process.exit(1);
3760
3760
  }
3761
3761
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3779,9 +3779,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3779
3779
  }
3780
3780
  if (!candidateNames.includes(name)) {
3781
3781
  const closest = findClosestMatch(name, candidateNames);
3782
- p14__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3782
+ p14__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3783
3783
  if (closest) {
3784
- p14__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
3784
+ p14__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
3785
3785
  }
3786
3786
  process.exit(1);
3787
3787
  }
@@ -3793,7 +3793,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3793
3793
  try {
3794
3794
  manifest = await fetchModuleManifest(registryBase, modType, modName);
3795
3795
  } catch (err) {
3796
- p14__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3796
+ p14__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3797
3797
  process.exit(1);
3798
3798
  }
3799
3799
  nodes.set(key, {
@@ -3833,7 +3833,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3833
3833
  buildTopologicalOrder(rootKey);
3834
3834
  const resolutions = /* @__PURE__ */ new Map();
3835
3835
  const globalAliasMap = /* @__PURE__ */ new Map();
3836
- p14__namespace.log.step(`Importing ${type}: ${pc14__default.default.cyan(name)}...`);
3836
+ p14__namespace.log.step(`Importing ${type}: ${pc15__default.default.cyan(name)}...`);
3837
3837
  for (const key of orderedKeys) {
3838
3838
  const node = nodes.get(key);
3839
3839
  const { type: modType, name: registryName, manifest } = node;
@@ -3866,7 +3866,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3866
3866
  if (programmaticPointTo) {
3867
3867
  if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
3868
3868
  p14__namespace.log.error(
3869
- pc14__default.default.red(
3869
+ pc15__default.default.red(
3870
3870
  `Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
3871
3871
  )
3872
3872
  );
@@ -3902,7 +3902,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3902
3902
  }
3903
3903
  const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
3904
3904
  if (!existingDir) {
3905
- p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14__default.default.cyan(registryName)}`);
3905
+ p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc15__default.default.cyan(registryName)}`);
3906
3906
  resolutions.set(key, {
3907
3907
  type: modType,
3908
3908
  registryName,
@@ -4133,7 +4133,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4133
4133
  const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
4134
4134
  if (key !== rootKey) {
4135
4135
  p14__namespace.log.step(
4136
- `Importing ${res.type}: ${pc14__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14__default.default.dim(` (from ${res.registryName})`) : ""}...`
4136
+ `Importing ${res.type}: ${pc15__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc15__default.default.dim(` (from ${res.registryName})`) : ""}...`
4137
4137
  );
4138
4138
  }
4139
4139
  if (res.action === "overwrite" && !flags.dryRun) {
@@ -4152,7 +4152,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4152
4152
  const localFilePath = path2__default.default.join(targetDir, file);
4153
4153
  if (fs4__default.default.existsSync(localFilePath) && !isOverwrite) {
4154
4154
  p14__namespace.log.warn(
4155
- ` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4155
+ ` \u21B3 Skipping existing file: ${pc15__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4156
4156
  );
4157
4157
  continue;
4158
4158
  }
@@ -4164,13 +4164,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4164
4164
  }
4165
4165
  const displayPath = path2__default.default.relative(projectRoot, localFilePath);
4166
4166
  totalWrittenFiles.push(displayPath);
4167
- p14__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
4167
+ p14__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
4168
4168
  }
4169
4169
  }
4170
4170
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
4171
4171
  if (missingNpm.length > 0) {
4172
4172
  if (flags.skipInstall || flags.dryRun) {
4173
- p14__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
4173
+ p14__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
4174
4174
  } else {
4175
4175
  try {
4176
4176
  installNpmPackages(projectRoot, missingNpm);
@@ -4184,18 +4184,901 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4184
4184
  }
4185
4185
  }
4186
4186
  if (flags.dryRun) {
4187
- p14__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
4187
+ p14__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
4188
4188
  } else {
4189
4189
  const rootRes = resolutions.get(rootKey);
4190
4190
  const baseDir = getModuleBaseDir(projectRoot, type);
4191
4191
  const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, rootRes.targetName));
4192
4192
  p14__namespace.log.success(
4193
- pc14__default.default.green(
4193
+ pc15__default.default.green(
4194
4194
  `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
4195
4195
  )
4196
4196
  );
4197
4197
  }
4198
4198
  }
4199
+ var DependencyRuleError = class extends Error {
4200
+ violations;
4201
+ isFailFast;
4202
+ constructor(message, violations, isFailFast = false) {
4203
+ super(message);
4204
+ this.name = "DependencyRuleError";
4205
+ this.violations = violations;
4206
+ this.isFailFast = isFailFast;
4207
+ }
4208
+ };
4209
+ function isTestFile2(filePath, projectRoot) {
4210
+ const norm = (projectRoot ? path2__default.default.relative(projectRoot, filePath) : filePath).replace(/\\/g, "/");
4211
+ const base = path2__default.default.basename(norm);
4212
+ 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);
4213
+ }
4214
+ function scanDirFiles(dir, projectRoot) {
4215
+ if (!fs4__default.default.existsSync(dir)) return [];
4216
+ const entries = fs4__default.default.readdirSync(dir, { withFileTypes: true });
4217
+ const files = [];
4218
+ for (const entry of entries) {
4219
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "__tests__" || entry.name.startsWith(".")) {
4220
+ continue;
4221
+ }
4222
+ const fullPath = path2__default.default.join(dir, entry.name);
4223
+ if (entry.isDirectory()) {
4224
+ files.push(...scanDirFiles(fullPath, projectRoot));
4225
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) && !isTestFile2(fullPath, projectRoot)) {
4226
+ files.push(fullPath);
4227
+ }
4228
+ }
4229
+ return files;
4230
+ }
4231
+ function classifyFile(fullPath, projectRoot) {
4232
+ const rel = path2__default.default.relative(projectRoot, fullPath).replace(/\\/g, "/");
4233
+ const featureMatch = rel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/.*)?$/);
4234
+ if (!featureMatch || !featureMatch[1] || !featureMatch[2]) return {};
4235
+ const feature = featureMatch[1];
4236
+ const subfolder = featureMatch[2];
4237
+ let layer;
4238
+ if (subfolder === "domain" || subfolder === "errors") {
4239
+ layer = "domain";
4240
+ } else if (subfolder === "useCases" || subfolder === "usecases") {
4241
+ layer = "useCases";
4242
+ } else if (subfolder === "services") {
4243
+ layer = "services";
4244
+ } else if (subfolder === "infrastructure") {
4245
+ layer = "infrastructure";
4246
+ } else if (subfolder === "pxExpress" || subfolder === "presentation" || subfolder === "handlers" || subfolder.startsWith("px")) {
4247
+ layer = "presentation";
4248
+ }
4249
+ return { feature, layer };
4250
+ }
4251
+ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4252
+ if (specifier === "@solid-stack/di" || specifier.startsWith("@solid-stack/di/")) {
4253
+ return { isExternal: true, isDiFramework: true };
4254
+ }
4255
+ if (specifier.startsWith("node:") || ["crypto", "fs", "path", "http", "https", "url", "os", "child_process", "stream", "express", "cors", "dotenv"].includes(specifier)) {
4256
+ return { isExternal: true, isDiFramework: false };
4257
+ }
4258
+ let resolvedRel = null;
4259
+ if (specifier.startsWith(".")) {
4260
+ const abs = path2__default.default.resolve(path2__default.default.dirname(sourceFileAbs), specifier);
4261
+ resolvedRel = path2__default.default.relative(projectRoot, abs).replace(/\\/g, "/");
4262
+ } else if (specifier.startsWith("@/features/") || specifier.startsWith("~/features/")) {
4263
+ resolvedRel = specifier.replace(/^[@~]\//, "src/");
4264
+ } else if (specifier.startsWith("@features/")) {
4265
+ resolvedRel = "src/" + specifier.slice(1);
4266
+ } else if (specifier.startsWith("@/shared/") || specifier.startsWith("~/shared/")) {
4267
+ resolvedRel = specifier.replace(/^[@~]\//, "src/");
4268
+ } else if (specifier.startsWith("@shared/")) {
4269
+ resolvedRel = "src/" + specifier.slice(1);
4270
+ } else if (specifier.startsWith("@/") || specifier.startsWith("~/")) {
4271
+ resolvedRel = "src/" + specifier.slice(2);
4272
+ } else if (specifier.startsWith("features/") || specifier.startsWith("shared/")) {
4273
+ resolvedRel = specifier;
4274
+ }
4275
+ if (!resolvedRel) {
4276
+ return { isExternal: true, isDiFramework: false };
4277
+ }
4278
+ const featMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/(.*))?$/);
4279
+ if (featMatch && featMatch[1] && featMatch[2]) {
4280
+ const targetFeature = featMatch[1];
4281
+ const targetSubfolder = featMatch[2];
4282
+ const rest = featMatch[3] || "";
4283
+ let targetLayer;
4284
+ if (targetSubfolder === "domain" || targetSubfolder === "errors") {
4285
+ targetLayer = "domain";
4286
+ } else if (targetSubfolder === "useCases" || targetSubfolder === "usecases") {
4287
+ targetLayer = "useCases";
4288
+ } else if (targetSubfolder === "services") {
4289
+ targetLayer = "services";
4290
+ } else if (targetSubfolder === "infrastructure") {
4291
+ targetLayer = "infrastructure";
4292
+ } else if (targetSubfolder === "pxExpress" || targetSubfolder === "presentation" || targetSubfolder === "handlers" || targetSubfolder.startsWith("px")) {
4293
+ targetLayer = "presentation";
4294
+ }
4295
+ const basePart = rest ? path2__default.default.basename(rest) : targetSubfolder;
4296
+ const targetFileName = basePart.replace(/\.(js|ts|mjs|cjs)$/, "");
4297
+ const isTargetPort = /^I[A-Z]/.test(targetFileName);
4298
+ const isTargetRepo = isTargetPort && targetFileName.includes("Repo");
4299
+ return {
4300
+ isExternal: false,
4301
+ isDiFramework: false,
4302
+ targetFeature,
4303
+ targetLayer,
4304
+ targetSubfolder,
4305
+ targetFileName,
4306
+ isTargetPort,
4307
+ isTargetRepo
4308
+ };
4309
+ }
4310
+ const sharedMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?shared\/([^/]+)(?:\/(.*))?$/);
4311
+ if (sharedMatch) {
4312
+ const targetSharedModule = sharedMatch[1];
4313
+ const sharedRest = sharedMatch[2] || "";
4314
+ const isSharedRoot = !sharedRest.includes("/");
4315
+ const isSharedPorts = sharedRest.startsWith("ports/") || sharedRest === "ports";
4316
+ const isSharedInfra = sharedRest.startsWith("infrastructure/") || sharedRest === "infrastructure";
4317
+ const isSharedDomain = sharedRest.startsWith("domain/") || sharedRest === "domain";
4318
+ return {
4319
+ isExternal: false,
4320
+ isDiFramework: false,
4321
+ targetSharedModule,
4322
+ isSharedRoot,
4323
+ isSharedPorts,
4324
+ isSharedInfra,
4325
+ isSharedDomain
4326
+ };
4327
+ }
4328
+ return { isExternal: true, isDiFramework: false };
4329
+ }
4330
+ function extractImportStatements(content) {
4331
+ const statements = [];
4332
+ const lines = content.split(/\r?\n/);
4333
+ 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;
4334
+ let match;
4335
+ while ((match = regex.exec(content)) !== null) {
4336
+ const specifier = match[1];
4337
+ const charIndex = match.index;
4338
+ const lineNum = content.substring(0, charIndex).split("\n").length;
4339
+ const rawCode = lines[lineNum - 1]?.trim() || match[0].trim();
4340
+ statements.push({
4341
+ specifier,
4342
+ line: lineNum,
4343
+ rawCode
4344
+ });
4345
+ }
4346
+ return statements;
4347
+ }
4348
+ var FeatureDependencyGraph = class {
4349
+ edges = /* @__PURE__ */ new Map();
4350
+ addEdge(evidence) {
4351
+ const { fromFeature, toFeature } = evidence;
4352
+ if (fromFeature === toFeature) {
4353
+ return { createsCycle: false };
4354
+ }
4355
+ if (!this.edges.has(fromFeature)) {
4356
+ this.edges.set(fromFeature, /* @__PURE__ */ new Map());
4357
+ }
4358
+ this.edges.get(fromFeature).set(toFeature, evidence);
4359
+ const reverse = this.edges.get(toFeature)?.get(fromFeature);
4360
+ if (reverse) {
4361
+ return { createsCycle: true, directPartner: reverse, cycleChain: [fromFeature, toFeature, fromFeature] };
4362
+ }
4363
+ const cycle = this.findCycleFrom(fromFeature);
4364
+ if (cycle && cycle.length > 0) {
4365
+ return { createsCycle: true, cycleChain: cycle };
4366
+ }
4367
+ return { createsCycle: false };
4368
+ }
4369
+ getEdges() {
4370
+ const list = [];
4371
+ for (const [from, toMap] of this.edges.entries()) {
4372
+ for (const [to, ev] of toMap.entries()) {
4373
+ list.push({ from, to, evidence: ev });
4374
+ }
4375
+ }
4376
+ return list;
4377
+ }
4378
+ findDirectCycles() {
4379
+ const cycles = [];
4380
+ const checkedPairs = /* @__PURE__ */ new Set();
4381
+ for (const [from, toMap] of this.edges.entries()) {
4382
+ for (const [to, evAtoB] of toMap.entries()) {
4383
+ const pairKey = [from, to].sort().join("<->");
4384
+ if (checkedPairs.has(pairKey)) continue;
4385
+ const evBtoA = this.edges.get(to)?.get(from);
4386
+ if (evBtoA) {
4387
+ checkedPairs.add(pairKey);
4388
+ cycles.push({
4389
+ featureA: from,
4390
+ featureB: to,
4391
+ cycle: [from, to, from],
4392
+ evidenceAtoB: evAtoB,
4393
+ evidenceBtoA: evBtoA
4394
+ });
4395
+ }
4396
+ }
4397
+ }
4398
+ return cycles;
4399
+ }
4400
+ findCycleFrom(start) {
4401
+ const visited = /* @__PURE__ */ new Set();
4402
+ const inStack = /* @__PURE__ */ new Set();
4403
+ const parent = /* @__PURE__ */ new Map();
4404
+ const dfs = (curr) => {
4405
+ visited.add(curr);
4406
+ inStack.add(curr);
4407
+ const neighbors = this.edges.get(curr);
4408
+ if (neighbors) {
4409
+ for (const next of neighbors.keys()) {
4410
+ if (!visited.has(next)) {
4411
+ parent.set(next, curr);
4412
+ const cycle = dfs(next);
4413
+ if (cycle) return cycle;
4414
+ } else if (inStack.has(next)) {
4415
+ const cycle = [next];
4416
+ let p16 = curr;
4417
+ while (p16 !== next) {
4418
+ cycle.push(p16);
4419
+ p16 = parent.get(p16) || next;
4420
+ }
4421
+ cycle.push(next);
4422
+ cycle.reverse();
4423
+ return cycle;
4424
+ }
4425
+ }
4426
+ }
4427
+ inStack.delete(curr);
4428
+ return null;
4429
+ };
4430
+ return dfs(start);
4431
+ }
4432
+ };
4433
+ function discoverAllFeatures(projectRoot) {
4434
+ const featuresBase = resolveFeaturesDir(projectRoot);
4435
+ if (!fs4__default.default.existsSync(featuresBase)) return [];
4436
+ const entries = fs4__default.default.readdirSync(featuresBase, { withFileTypes: true });
4437
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "__tests__" && e.name !== "tests").map((e) => ({
4438
+ name: e.name,
4439
+ dir: path2__default.default.join(featuresBase, e.name)
4440
+ })).sort((a, b) => a.name.localeCompare(b.name));
4441
+ }
4442
+ function lintCodebase(options = {}) {
4443
+ const startTime = Date.now();
4444
+ const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
4445
+ const failFast = !options.complete;
4446
+ const features = discoverAllFeatures(projectRoot);
4447
+ const graph = new FeatureDependencyGraph();
4448
+ const allViolations = [];
4449
+ const results = [];
4450
+ const detectedCycles = [];
4451
+ const addViolation = (v) => {
4452
+ allViolations.push(v);
4453
+ const featureResult = results.find((r) => r.featureName === v.feature);
4454
+ if (featureResult) {
4455
+ featureResult.violations.push(v);
4456
+ featureResult.passed = false;
4457
+ }
4458
+ if (failFast) {
4459
+ const errorMsg = `[FAIL FAST] Dependency rule violation detected:
4460
+ Feature: ${v.feature}
4461
+ Category: ${v.category}
4462
+ Rule: ${v.ruleId}
4463
+ Location: ${v.relativePath}:${v.line}
4464
+ Code: ${v.code}
4465
+ Explanation: ${v.advice}`;
4466
+ throw new DependencyRuleError(errorMsg, [v], true);
4467
+ }
4468
+ };
4469
+ for (const feat of features) {
4470
+ results.push({
4471
+ featureName: feat.name,
4472
+ featureDir: feat.dir,
4473
+ scannedFilesCount: 0,
4474
+ passed: true,
4475
+ violations: [],
4476
+ layersChecked: ["domain", "useCases", "services", "infrastructure", "presentation"]
4477
+ });
4478
+ }
4479
+ for (const feat of features) {
4480
+ const featResult = results.find((r) => r.featureName === feat.name);
4481
+ const allFiles = scanDirFiles(feat.dir, projectRoot);
4482
+ featResult.scannedFilesCount = allFiles.length;
4483
+ for (const file of allFiles) {
4484
+ const relPath = path2__default.default.relative(projectRoot, file).replace(/\\/g, "/");
4485
+ const { layer } = classifyFile(file, projectRoot);
4486
+ if (!layer) continue;
4487
+ const content = fs4__default.default.readFileSync(file, "utf-8");
4488
+ const statements = extractImportStatements(content);
4489
+ if (layer === "domain") {
4490
+ const lines = content.split(/\r?\n/);
4491
+ for (let i = 0; i < lines.length; i++) {
4492
+ const lineText = lines[i].trim();
4493
+ if (lineText.includes("@MakeInjectable") || lineText.includes("public static deps =")) {
4494
+ addViolation({
4495
+ feature: feat.name,
4496
+ layer,
4497
+ file,
4498
+ relativePath: relPath,
4499
+ line: i + 1,
4500
+ category: "Domain Dependence Violation",
4501
+ code: lineText,
4502
+ ruleId: "D2_DI_DEPENDENCE",
4503
+ severity: "error",
4504
+ advice: "Domain layer must have zero DI dependence. Remove @MakeInjectable decorators and static deps declarations.",
4505
+ fixExample: {
4506
+ bad: lineText,
4507
+ good: "// Domain interfaces and value objects must remain plain TypeScript classes/interfaces."
4508
+ }
4509
+ });
4510
+ }
4511
+ }
4512
+ }
4513
+ for (const stmt of statements) {
4514
+ const target = classifyTarget(stmt.specifier, file, projectRoot);
4515
+ if (target.targetFeature && target.targetFeature !== feat.name) {
4516
+ const evidence = {
4517
+ fromFeature: feat.name,
4518
+ toFeature: target.targetFeature,
4519
+ file,
4520
+ relativePath: relPath,
4521
+ line: stmt.line,
4522
+ code: stmt.rawCode
4523
+ };
4524
+ const cycleResult = graph.addEdge(evidence);
4525
+ if (cycleResult.createsCycle) {
4526
+ const partner = cycleResult.directPartner;
4527
+ detectedCycles.push({
4528
+ featureA: feat.name,
4529
+ featureB: target.targetFeature,
4530
+ cycle: cycleResult.cycleChain,
4531
+ evidenceAtoB: evidence,
4532
+ evidenceBtoA: partner || evidence
4533
+ });
4534
+ addViolation({
4535
+ feature: feat.name,
4536
+ layer,
4537
+ file,
4538
+ relativePath: relPath,
4539
+ line: stmt.line,
4540
+ category: "Circular Feature Dependency Violation",
4541
+ code: stmt.rawCode,
4542
+ ruleId: "C1_CIRCULAR_FEATURE_DEPENDENCY",
4543
+ severity: "error",
4544
+ 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.`,
4545
+ fixExample: {
4546
+ bad: stmt.rawCode,
4547
+ good: `// Emit a Domain/Application Event (e.g. publish '${feat.name}Changed') instead of depending on '${target.targetFeature}'.`
4548
+ }
4549
+ });
4550
+ }
4551
+ }
4552
+ if (layer === "domain") {
4553
+ if (target.targetLayer && ["useCases", "services", "infrastructure", "presentation"].includes(target.targetLayer)) {
4554
+ addViolation({
4555
+ feature: feat.name,
4556
+ layer,
4557
+ file,
4558
+ relativePath: relPath,
4559
+ line: stmt.line,
4560
+ category: "Domain Purity Violation",
4561
+ code: stmt.rawCode,
4562
+ ruleId: "D1_OUTER_IMPORT",
4563
+ severity: "error",
4564
+ advice: `Domain layer must not import from outer layers (${target.targetLayer}). Keep the domain core pure.`,
4565
+ fixExample: {
4566
+ bad: stmt.rawCode,
4567
+ good: "// Define interfaces, types, or value objects directly within the domain layer."
4568
+ }
4569
+ });
4570
+ }
4571
+ if (target.isDiFramework) {
4572
+ addViolation({
4573
+ feature: feat.name,
4574
+ layer,
4575
+ file,
4576
+ relativePath: relPath,
4577
+ line: stmt.line,
4578
+ category: "Domain Dependence Violation",
4579
+ code: stmt.rawCode,
4580
+ ruleId: "D2_DI_DEPENDENCE",
4581
+ severity: "error",
4582
+ advice: "Domain layer must have zero DI dependence. Domain interfaces and value objects are plain classes/types.",
4583
+ fixExample: {
4584
+ bad: stmt.rawCode,
4585
+ good: "// Remove @solid-stack/di import. Domain entities are pure TypeScript."
4586
+ }
4587
+ });
4588
+ }
4589
+ if (target.targetSharedModule && !target.isSharedDomain) {
4590
+ addViolation({
4591
+ feature: feat.name,
4592
+ layer,
4593
+ file,
4594
+ relativePath: relPath,
4595
+ line: stmt.line,
4596
+ category: "Domain Purity Violation",
4597
+ code: stmt.rawCode,
4598
+ ruleId: "D3_SHARED_NON_DOMAIN_IMPORT",
4599
+ severity: "error",
4600
+ advice: "Domain layer can only import pure value objects from shared/<module>/domain/*. Ports, infrastructure, and root classes must not be imported.",
4601
+ fixExample: {
4602
+ bad: stmt.rawCode,
4603
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/domain/...";`
4604
+ }
4605
+ });
4606
+ }
4607
+ if (target.isExternal && !target.isDiFramework) {
4608
+ addViolation({
4609
+ feature: feat.name,
4610
+ layer,
4611
+ file,
4612
+ relativePath: relPath,
4613
+ line: stmt.line,
4614
+ category: "Domain Purity Violation",
4615
+ code: stmt.rawCode,
4616
+ ruleId: "D4_EXTERNAL_LIBRARY",
4617
+ severity: "error",
4618
+ advice: `Domain layer must be pure and must not depend on external runtime libraries ('${stmt.specifier}').`,
4619
+ fixExample: {
4620
+ bad: stmt.rawCode,
4621
+ good: "// Model pure business interfaces without external libraries."
4622
+ }
4623
+ });
4624
+ }
4625
+ } else if (layer === "useCases") {
4626
+ if (target.targetLayer === "useCases" && target.targetFeature === feat.name) {
4627
+ addViolation({
4628
+ feature: feat.name,
4629
+ layer,
4630
+ file,
4631
+ relativePath: relPath,
4632
+ line: stmt.line,
4633
+ category: "Use Case Intra-Feature Violation",
4634
+ code: stmt.rawCode,
4635
+ ruleId: "U1_INTRA_USE_CASE",
4636
+ severity: "error",
4637
+ 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.`,
4638
+ fixExample: {
4639
+ bad: stmt.rawCode,
4640
+ good: "// Inject a domain interface or domain service instead of another use case."
4641
+ }
4642
+ });
4643
+ }
4644
+ if (target.targetLayer === "useCases" && target.targetFeature && target.targetFeature !== feat.name) {
4645
+ addViolation({
4646
+ feature: feat.name,
4647
+ layer,
4648
+ file,
4649
+ relativePath: relPath,
4650
+ line: stmt.line,
4651
+ category: "Use Case Cross-Feature Use Case Violation",
4652
+ code: stmt.rawCode,
4653
+ ruleId: "U2_CROSS_FEATURE_USE_CASE",
4654
+ severity: "error",
4655
+ 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/'.`,
4656
+ fixExample: {
4657
+ bad: stmt.rawCode,
4658
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/ using '${target.targetFileName}'.`
4659
+ }
4660
+ });
4661
+ }
4662
+ if (target.targetLayer === "infrastructure") {
4663
+ addViolation({
4664
+ feature: feat.name,
4665
+ layer,
4666
+ file,
4667
+ relativePath: relPath,
4668
+ line: stmt.line,
4669
+ category: "Use Case Layer Violation",
4670
+ code: stmt.rawCode,
4671
+ ruleId: "U3_DIRECT_INFRASTRUCTURE",
4672
+ severity: "error",
4673
+ advice: "Use cases cannot import concrete infrastructure directly. Depend on Domain Ports of the same feature via DI.",
4674
+ fixExample: {
4675
+ bad: stmt.rawCode,
4676
+ good: "// Depend on the domain interface port declared in static deps."
4677
+ }
4678
+ });
4679
+ }
4680
+ if (target.targetLayer === "domain" && target.targetFeature && target.targetFeature !== feat.name && target.isTargetPort) {
4681
+ addViolation({
4682
+ feature: feat.name,
4683
+ layer,
4684
+ file,
4685
+ relativePath: relPath,
4686
+ line: stmt.line,
4687
+ category: "Use Case Cross-Feature Port Violation",
4688
+ code: stmt.rawCode,
4689
+ ruleId: "U4_CROSS_FEATURE_PORT",
4690
+ severity: "error",
4691
+ 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/'.`,
4692
+ fixExample: {
4693
+ bad: stmt.rawCode,
4694
+ good: `// Define a local interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4695
+ }
4696
+ });
4697
+ }
4698
+ if (target.targetLayer === "services" && target.targetFeature && target.targetFeature !== feat.name) {
4699
+ addViolation({
4700
+ feature: feat.name,
4701
+ layer,
4702
+ file,
4703
+ relativePath: relPath,
4704
+ line: stmt.line,
4705
+ category: "Use Case Layer Violation",
4706
+ code: stmt.rawCode,
4707
+ ruleId: "U5_CROSS_FEATURE_SERVICE",
4708
+ severity: "error",
4709
+ advice: `Use cases cannot depend on services of foreign feature '${target.targetFeature}'.`,
4710
+ fixExample: {
4711
+ bad: stmt.rawCode,
4712
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4713
+ }
4714
+ });
4715
+ }
4716
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4717
+ addViolation({
4718
+ feature: feat.name,
4719
+ layer,
4720
+ file,
4721
+ relativePath: relPath,
4722
+ line: stmt.line,
4723
+ category: "Shared Module Root Class Violation",
4724
+ code: stmt.rawCode,
4725
+ ruleId: "U6_SHARED_NON_ROOT",
4726
+ severity: "error",
4727
+ advice: `Use cases can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.`,
4728
+ fixExample: {
4729
+ bad: stmt.rawCode,
4730
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4731
+ }
4732
+ });
4733
+ }
4734
+ } else if (layer === "services") {
4735
+ if (target.targetLayer === "useCases") {
4736
+ addViolation({
4737
+ feature: feat.name,
4738
+ layer,
4739
+ file,
4740
+ relativePath: relPath,
4741
+ line: stmt.line,
4742
+ category: "Service Intra-Feature Violation",
4743
+ code: stmt.rawCode,
4744
+ ruleId: "S1_USE_CASE_DEPENDENCY",
4745
+ severity: "error",
4746
+ advice: "Services cannot depend on use cases of any feature.",
4747
+ fixExample: {
4748
+ bad: stmt.rawCode,
4749
+ good: "// Inject domain repository ports directly instead of use cases."
4750
+ }
4751
+ });
4752
+ }
4753
+ if (target.targetLayer === "domain" && target.targetFeature && target.targetFeature !== feat.name && target.isTargetPort) {
4754
+ addViolation({
4755
+ feature: feat.name,
4756
+ layer,
4757
+ file,
4758
+ relativePath: relPath,
4759
+ line: stmt.line,
4760
+ category: "Service Cross-Feature Port Violation",
4761
+ code: stmt.rawCode,
4762
+ ruleId: "S2_CROSS_FEATURE_PORT",
4763
+ severity: "error",
4764
+ advice: `Services cannot depend on domain ports of foreign feature '${target.targetFeature}'.`,
4765
+ fixExample: {
4766
+ bad: stmt.rawCode,
4767
+ good: `// Define an interface in ${feat.name}/domain/ and implement it in ${feat.name}/infrastructure/.`
4768
+ }
4769
+ });
4770
+ }
4771
+ if (target.targetLayer === "infrastructure") {
4772
+ addViolation({
4773
+ feature: feat.name,
4774
+ layer,
4775
+ file,
4776
+ relativePath: relPath,
4777
+ line: stmt.line,
4778
+ category: "Service Layer Violation",
4779
+ code: stmt.rawCode,
4780
+ ruleId: "S3_DIRECT_INFRASTRUCTURE",
4781
+ severity: "error",
4782
+ advice: "Services cannot import concrete infrastructure directly. Depend on Domain Ports via DI.",
4783
+ fixExample: {
4784
+ bad: stmt.rawCode,
4785
+ good: "// Depend on the domain interface port declared in static deps."
4786
+ }
4787
+ });
4788
+ }
4789
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4790
+ addViolation({
4791
+ feature: feat.name,
4792
+ layer,
4793
+ file,
4794
+ relativePath: relPath,
4795
+ line: stmt.line,
4796
+ category: "Shared Module Root Class Violation",
4797
+ code: stmt.rawCode,
4798
+ ruleId: "S4_SHARED_NON_ROOT",
4799
+ severity: "error",
4800
+ advice: "Services can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4801
+ fixExample: {
4802
+ bad: stmt.rawCode,
4803
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4804
+ }
4805
+ });
4806
+ }
4807
+ } else if (layer === "infrastructure") {
4808
+ if (target.targetLayer === "useCases" && target.targetFeature === feat.name) {
4809
+ addViolation({
4810
+ feature: feat.name,
4811
+ layer,
4812
+ file,
4813
+ relativePath: relPath,
4814
+ line: stmt.line,
4815
+ category: "Adapter Layer Violation",
4816
+ code: stmt.rawCode,
4817
+ ruleId: "I1_INTRA_USE_CASE",
4818
+ severity: "error",
4819
+ advice: `Infrastructure adapters cannot depend on use cases of the same feature ('${feat.name}'). Adapters implement domain ports.`,
4820
+ fixExample: {
4821
+ bad: stmt.rawCode,
4822
+ good: "// Adapters implement domain ports and should not invoke local application use cases."
4823
+ }
4824
+ });
4825
+ }
4826
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4827
+ addViolation({
4828
+ feature: feat.name,
4829
+ layer,
4830
+ file,
4831
+ relativePath: relPath,
4832
+ line: stmt.line,
4833
+ category: "Shared Module Root Class Violation",
4834
+ code: stmt.rawCode,
4835
+ ruleId: "I2_SHARED_NON_ROOT",
4836
+ severity: "error",
4837
+ advice: "Infrastructure adapters can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4838
+ fixExample: {
4839
+ bad: stmt.rawCode,
4840
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4841
+ }
4842
+ });
4843
+ }
4844
+ } else if (layer === "presentation") {
4845
+ if (target.targetLayer === "infrastructure") {
4846
+ addViolation({
4847
+ feature: feat.name,
4848
+ layer,
4849
+ file,
4850
+ relativePath: relPath,
4851
+ line: stmt.line,
4852
+ category: "Presentation Layer Violation",
4853
+ code: stmt.rawCode,
4854
+ ruleId: "P1_DIRECT_INFRASTRUCTURE",
4855
+ severity: "error",
4856
+ advice: "Presentation handlers must not import infrastructure directly. Inject Use Cases via DI.",
4857
+ fixExample: {
4858
+ bad: stmt.rawCode,
4859
+ good: "// Inject the corresponding Use Case via static deps and invoke its execute() method."
4860
+ }
4861
+ });
4862
+ }
4863
+ if (target.targetLayer === "domain" && (target.isTargetRepo || target.isTargetPort)) {
4864
+ addViolation({
4865
+ feature: feat.name,
4866
+ layer,
4867
+ file,
4868
+ relativePath: relPath,
4869
+ line: stmt.line,
4870
+ category: "Presentation Layer Violation",
4871
+ code: stmt.rawCode,
4872
+ ruleId: "P2_DIRECT_REPOSITORY",
4873
+ severity: "error",
4874
+ advice: "Presentation handlers must not depend on domain repositories or ports directly. Inject Use Cases via DI.",
4875
+ fixExample: {
4876
+ bad: stmt.rawCode,
4877
+ good: "// Presentation layer must only orchestrate Use Cases."
4878
+ }
4879
+ });
4880
+ }
4881
+ if (target.targetSharedModule && (target.isSharedPorts || target.isSharedInfra)) {
4882
+ addViolation({
4883
+ feature: feat.name,
4884
+ layer,
4885
+ file,
4886
+ relativePath: relPath,
4887
+ line: stmt.line,
4888
+ category: "Shared Module Root Class Violation",
4889
+ code: stmt.rawCode,
4890
+ ruleId: "P3_SHARED_NON_ROOT",
4891
+ severity: "error",
4892
+ advice: "Presentation handlers can only depend on root classes of shared modules. Never import from shared ports or infrastructure directly.",
4893
+ fixExample: {
4894
+ bad: stmt.rawCode,
4895
+ good: `import { ... } from "@/shared/${target.targetSharedModule}/<RootClass>.js";`
4896
+ }
4897
+ });
4898
+ }
4899
+ }
4900
+ }
4901
+ }
4902
+ }
4903
+ const directCycles = graph.findDirectCycles();
4904
+ for (const c of directCycles) {
4905
+ const existing = detectedCycles.find(
4906
+ (dc) => dc.featureA === c.featureA && dc.featureB === c.featureB || dc.featureA === c.featureB && dc.featureB === c.featureA
4907
+ );
4908
+ if (!existing) {
4909
+ detectedCycles.push(c);
4910
+ addViolation({
4911
+ feature: c.featureA,
4912
+ file: c.evidenceAtoB.file,
4913
+ relativePath: c.evidenceAtoB.relativePath,
4914
+ line: c.evidenceAtoB.line,
4915
+ category: "Circular Feature Dependency Violation",
4916
+ code: c.evidenceAtoB.code,
4917
+ ruleId: "C1_CIRCULAR_FEATURE_DEPENDENCY",
4918
+ severity: "error",
4919
+ 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.`
4920
+ });
4921
+ }
4922
+ }
4923
+ const durationMs = Date.now() - startTime;
4924
+ const cleanFeatures = results.filter((r) => r.passed).length;
4925
+ const violatedFeatures = results.filter((r) => !r.passed).length;
4926
+ const summary = {
4927
+ totalFeatures: results.length,
4928
+ cleanFeatures,
4929
+ violatedFeatures,
4930
+ totalViolations: allViolations.length,
4931
+ durationMs,
4932
+ results,
4933
+ allViolations,
4934
+ circularDependencies: detectedCycles
4935
+ };
4936
+ if (allViolations.length > 0 && options.throwOnError !== false) {
4937
+ const errorHeader = `Dependency rule audit failed with ${allViolations.length} violation(s) across ${violatedFeatures} feature(s):`;
4938
+ const compiledDetails = allViolations.map(
4939
+ (v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
4940
+ Rule: ${v.ruleId}
4941
+ Code: ${v.code}
4942
+ Advice: ${v.advice}`
4943
+ ).join("\n\n");
4944
+ throw new DependencyRuleError(`${errorHeader}
4945
+
4946
+ ${compiledDetails}
4947
+ `, allViolations, false);
4948
+ }
4949
+ return summary;
4950
+ }
4951
+
4952
+ // src/commands/lint.ts
4953
+ function printLintBanner(mode) {
4954
+ console.log("");
4955
+ const title = " Mason Clean Architecture Dependency Linter ";
4956
+ const line = "\u2501".repeat(title.length + 2);
4957
+ console.log(pc15__default.default.cyan(` \u250F${line}\u2513`));
4958
+ console.log(` ${pc15__default.default.cyan("\u2503")} ${pc15__default.default.bgCyan(pc15__default.default.black(title))} ${pc15__default.default.cyan("\u2503")}`);
4959
+ console.log(pc15__default.default.cyan(` \u2517${line}\u251B`));
4960
+ 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)")}`;
4961
+ console.log(` Mode: ${modeText}`);
4962
+ console.log("");
4963
+ }
4964
+ function formatViolation(v, showFixHint = true) {
4965
+ const lines = [];
4966
+ const categoryBadge = pc15__default.default.red(pc15__default.default.bold(`[${v.category}]`));
4967
+ const location = pc15__default.default.white(`${v.relativePath}:${pc15__default.default.yellow(String(v.line))}`);
4968
+ lines.push(` ${categoryBadge} ${location}`);
4969
+ lines.push(` ${pc15__default.default.dim("Rule ID:")} ${pc15__default.default.yellow(v.ruleId)}`);
4970
+ lines.push(` ${pc15__default.default.dim("Code:")} ${pc15__default.default.magenta(v.code)}`);
4971
+ lines.push(` ${pc15__default.default.dim("Advice:")} ${pc15__default.default.cyan(v.advice)}`);
4972
+ if (showFixHint && v.fixExample) {
4973
+ lines.push(` ${pc15__default.default.dim("Example:")}`);
4974
+ lines.push(` ${pc15__default.default.red(`- ${v.fixExample.bad}`)}`);
4975
+ lines.push(` ${pc15__default.default.green(`+ ${v.fixExample.good}`)}`);
4976
+ }
4977
+ return lines.join("\n");
4978
+ }
4979
+ function formatFeatureResult(res, options = {}) {
4980
+ const lines = [];
4981
+ if (res.passed) {
4982
+ lines.push(
4983
+ ` ${pc15__default.default.green("\u2714")} ${pc15__default.default.bold(res.featureName)} ${pc15__default.default.dim(
4984
+ `(${res.scannedFilesCount} files checked - clean)`
4985
+ )}`
4986
+ );
4987
+ return lines.join("\n");
4988
+ }
4989
+ const countBadge = pc15__default.default.red(
4990
+ `(${res.violations.length} violation${res.violations.length > 1 ? "s" : ""})`
4991
+ );
4992
+ lines.push(`
4993
+ ${pc15__default.default.red("\u2716")} ${pc15__default.default.bold(pc15__default.default.white(res.featureName))} ${countBadge}`);
4994
+ lines.push(` ${pc15__default.default.dim("Path:")} ${res.featureDir}`);
4995
+ lines.push("");
4996
+ for (const v of res.violations) {
4997
+ lines.push(formatViolation(v, options.fixHint !== false));
4998
+ lines.push("");
4999
+ }
5000
+ return lines.join("\n");
5001
+ }
5002
+ function printTerminalReport(summary, options = {}) {
5003
+ for (const res of summary.results) {
5004
+ const formatted = formatFeatureResult(res, options);
5005
+ if (formatted.trim()) {
5006
+ console.log(formatted);
5007
+ }
5008
+ }
5009
+ console.log("\u2501".repeat(70));
5010
+ const statusIcon = summary.totalViolations === 0 ? pc15__default.default.green("\u2714 PASS") : pc15__default.default.red("\u2716 FAIL");
5011
+ console.log(
5012
+ ` ${statusIcon} ${pc15__default.default.bold("Dependency Rules Audit Result")} ${pc15__default.default.dim(
5013
+ `(${summary.durationMs}ms)`
5014
+ )}`
5015
+ );
5016
+ console.log(
5017
+ ` ${pc15__default.default.bold("Features:")} ${pc15__default.default.cyan(String(summary.totalFeatures))} total | ${pc15__default.default.green(
5018
+ String(summary.cleanFeatures)
5019
+ )} clean | ${summary.violatedFeatures > 0 ? pc15__default.default.red(String(summary.violatedFeatures)) : pc15__default.default.green("0")} with violations`
5020
+ );
5021
+ console.log(
5022
+ ` ${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`
5023
+ );
5024
+ console.log("\u2501".repeat(70));
5025
+ console.log("");
5026
+ }
5027
+ async function handleLint(flags = {}) {
5028
+ const complete = Boolean(flags.complete);
5029
+ const mode = complete ? "complete" : "failFast";
5030
+ const quiet = Boolean(flags.quiet);
5031
+ if (!quiet) {
5032
+ printLintBanner(mode);
5033
+ }
5034
+ const options = {
5035
+ complete,
5036
+ quiet,
5037
+ targetDir: flags.targetDir || flags.target,
5038
+ projectRoot: flags.projectRoot,
5039
+ fixHint: flags.fixHint !== false,
5040
+ throwOnError: false
5041
+ };
5042
+ try {
5043
+ const summary = lintCodebase(options);
5044
+ if (!quiet) {
5045
+ printTerminalReport(summary, options);
5046
+ }
5047
+ if (summary.totalViolations > 0) {
5048
+ const errorHeader = `Dependency rule audit failed with ${summary.totalViolations} violation(s) across ${summary.violatedFeatures} feature(s):`;
5049
+ const compiledDetails = summary.allViolations.map(
5050
+ (v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
5051
+ Rule: ${v.ruleId}
5052
+ Code: ${v.code}
5053
+ Advice: ${v.advice}`
5054
+ ).join("\n\n");
5055
+ throw new DependencyRuleError(`${errorHeader}
5056
+
5057
+ ${compiledDetails}
5058
+ `, summary.allViolations, false);
5059
+ }
5060
+ if (!quiet) {
5061
+ console.log(
5062
+ pc15__default.default.green(`\u2714 All dependency rules are being followed! (${summary.totalFeatures} features checked)`)
5063
+ );
5064
+ console.log("");
5065
+ }
5066
+ return summary;
5067
+ } catch (err) {
5068
+ if (err instanceof DependencyRuleError && err.isFailFast) {
5069
+ if (!quiet) {
5070
+ console.log(pc15__default.default.red(`
5071
+ \u2716 [FAIL FAST] Terminating on first detected dependency rule violation:`));
5072
+ const v = err.violations[0];
5073
+ if (v) {
5074
+ console.log(formatViolation(v, options.fixHint !== false));
5075
+ }
5076
+ console.log("");
5077
+ }
5078
+ }
5079
+ throw err;
5080
+ }
5081
+ }
4199
5082
 
4200
5083
  // src/cli.ts
4201
5084
  function createCli() {
@@ -4203,7 +5086,7 @@ function createCli() {
4203
5086
  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) => {
4204
5087
  if (args && args.length > 0) {
4205
5088
  p14__namespace.log.error(
4206
- pc14__default.default.red(
5089
+ pc15__default.default.red(
4207
5090
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
4208
5091
  )
4209
5092
  );
@@ -4269,14 +5152,21 @@ function createCli() {
4269
5152
  value: "import",
4270
5153
  label: "Import from registry",
4271
5154
  hint: "Download and link pre-built shared modules or features from the registry"
5155
+ },
5156
+ {
5157
+ value: "lint",
5158
+ label: "Lint dependency rules",
5159
+ hint: "Scan codebase and verify Clean Architecture dependency rules"
4272
5160
  }
4273
5161
  ]
4274
5162
  })
4275
5163
  );
4276
5164
  if (choice === "create") {
4277
5165
  await handleCreate(void 0, void 0, flags);
4278
- } else {
5166
+ } else if (choice === "import") {
4279
5167
  await handleImport(void 0, void 0, flags);
5168
+ } else if (choice === "lint") {
5169
+ await handleLint(flags);
4280
5170
  }
4281
5171
  } catch (err) {
4282
5172
  handleCliError(err);
@@ -4302,6 +5192,22 @@ function createCli() {
4302
5192
  handleCliError(err);
4303
5193
  }
4304
5194
  });
5195
+ cli.command(
5196
+ "lint [target]",
5197
+ "Scan codebase and verify Clean Architecture dependency rules"
5198
+ ).option(
5199
+ "-c, --complete",
5200
+ "Scan entire codebase without failing fast, compiling all errors at the end"
5201
+ ).option("-q, --quiet", "Suppress non-error output").example("mason lint").example("mason lint --complete").example("mason lint ./src").action(async (target, flags = {}) => {
5202
+ try {
5203
+ await handleLint({
5204
+ ...flags,
5205
+ targetDir: target || flags.target || flags.dest
5206
+ });
5207
+ } catch (err) {
5208
+ handleCliError(err);
5209
+ }
5210
+ });
4305
5211
  const createSubtypes = [
4306
5212
  "feature",
4307
5213
  "shared",
@@ -4333,7 +5239,10 @@ function handleCliError(err) {
4333
5239
  process.exit(0);
4334
5240
  }
4335
5241
  const message = err instanceof Error ? err.message : String(err);
4336
- p14__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
5242
+ p14__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
5243
+ if (process.env.VITEST) {
5244
+ throw err;
5245
+ }
4337
5246
  process.exit(1);
4338
5247
  }
4339
5248
  async function runCli(argv = process.argv) {
@@ -4342,6 +5251,8 @@ async function runCli(argv = process.argv) {
4342
5251
  }
4343
5252
 
4344
5253
  exports.DEFAULT_REGISTRY_BASE = DEFAULT_REGISTRY_BASE;
5254
+ exports.DependencyRuleError = DependencyRuleError;
5255
+ exports.FeatureDependencyGraph = FeatureDependencyGraph;
4345
5256
  exports.PromptCancelledError = PromptCancelledError;
4346
5257
  exports.computeDirectoryHash = computeDirectoryHash;
4347
5258
  exports.concreteInfraTemplate = concreteInfraTemplate;
@@ -4386,6 +5297,7 @@ exports.getTemplatesDir = getTemplatesDir;
4386
5297
  exports.handleCancel = handleCancel;
4387
5298
  exports.handleCreate = handleCreate;
4388
5299
  exports.handleImport = handleImport;
5300
+ exports.handleLint = handleLint;
4389
5301
  exports.hbs = hbs;
4390
5302
  exports.httpHandlerTemplate = httpHandlerTemplate;
4391
5303
  exports.injectableInterfaceTemplate = injectableInterfaceTemplate;
@@ -4394,6 +5306,7 @@ exports.installNpmPackages = installNpmPackages;
4394
5306
  exports.interfaceTemplate = interfaceTemplate;
4395
5307
  exports.isValidIdentifier = isValidIdentifier;
4396
5308
  exports.levenshteinDistance = levenshteinDistance;
5309
+ exports.lintCodebase = lintCodebase;
4397
5310
  exports.listDomainEntities = listDomainEntities;
4398
5311
  exports.listDomainInterfaces = listDomainInterfaces;
4399
5312
  exports.listExistingFeatures = listExistingFeatures;