@archpilotlabs/archpilot 0.0.9 → 0.0.11

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.
Files changed (2) hide show
  1. package/dist/archpilot-cli.js +1227 -201
  2. package/package.json +1 -1
@@ -213475,7 +213475,264 @@ async function listModuleDirectoryNames(workspaceRoot, modulesRootRelativePath =
213475
213475
  const entries = await import_node_fs.promises.readdir(modulesRoot, { withFileTypes: true });
213476
213476
  return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
213477
213477
  }
213478
+ var ignoredModuleDirectoryNames = /* @__PURE__ */ new Set([
213479
+ ".git",
213480
+ ".archpilot",
213481
+ ".vscode",
213482
+ ".vscode-test",
213483
+ "node_modules",
213484
+ "dist",
213485
+ "build",
213486
+ "out",
213487
+ "coverage",
213488
+ ".next",
213489
+ "generated",
213490
+ "target",
213491
+ "vendor",
213492
+ ".venv",
213493
+ "venv",
213494
+ "__pycache__"
213495
+ ]);
213496
+ var noisyModuleDirectoryNames = /* @__PURE__ */ new Set([
213497
+ "components",
213498
+ "hooks",
213499
+ "utils",
213500
+ "util",
213501
+ "lib",
213502
+ "common",
213503
+ "shared",
213504
+ "shared-ui",
213505
+ "types",
213506
+ "constants",
213507
+ "config",
213508
+ "test",
213509
+ "tests",
213510
+ "__tests__",
213511
+ "fixtures",
213512
+ "mocks",
213513
+ "__mocks__",
213514
+ "generated",
213515
+ "dist",
213516
+ "build",
213517
+ "out",
213518
+ "node_modules"
213519
+ ]);
213520
+ var sourceFileExtensions = /* @__PURE__ */ new Set([
213521
+ ".ts",
213522
+ ".tsx",
213523
+ ".js",
213524
+ ".jsx",
213525
+ ".vue",
213526
+ ".java",
213527
+ ".py",
213528
+ ".php",
213529
+ ".go",
213530
+ ".cs",
213531
+ ".rb",
213532
+ ".kt",
213533
+ ".swift",
213534
+ ".rs"
213535
+ ]);
213536
+ var publicEntrypointNames = /* @__PURE__ */ new Set([
213537
+ "index.ts",
213538
+ "index.tsx",
213539
+ "index.js",
213540
+ "index.jsx",
213541
+ "index.vue",
213542
+ "__init__.py",
213543
+ "main.go",
213544
+ "mod.ts",
213545
+ "mod.js"
213546
+ ]);
213547
+ function isIgnoredDirectoryName(directoryName) {
213548
+ return ignoredModuleDirectoryNames.has(directoryName.toLowerCase());
213549
+ }
213550
+ function isNoisyModuleName(moduleName) {
213551
+ return noisyModuleDirectoryNames.has(moduleName.toLowerCase());
213552
+ }
213553
+ function toPathSegments(relativePath) {
213554
+ return normalizePath(relativePath).split("/").filter((segment) => segment.length > 0);
213555
+ }
213556
+ function toModuleIdFromPath(relativePath, fallbackRoot) {
213557
+ const segments = toPathSegments(relativePath);
213558
+ const lowerSegments = segments.map((segment) => segment.toLowerCase());
213559
+ const sourceIndex = lowerSegments.lastIndexOf("src");
213560
+ if (sourceIndex >= 0 && sourceIndex + 2 < segments.length) {
213561
+ const parent = segments[sourceIndex - 1];
213562
+ const container = lowerSegments[sourceIndex + 1];
213563
+ if (["modules", "features"].includes(container)) {
213564
+ return [parent, segments[sourceIndex + 2]].filter(Boolean).join("/");
213565
+ }
213566
+ if (["domain", "domains", "application", "infrastructure"].includes(container)) {
213567
+ return [segments[sourceIndex + 1], segments[sourceIndex + 2]].join("/");
213568
+ }
213569
+ return [parent, segments[sourceIndex + 1]].filter(Boolean).join("/");
213570
+ }
213571
+ if (sourceIndex >= 0 && sourceIndex + 1 < segments.length) {
213572
+ const parent = segments[sourceIndex - 1];
213573
+ return [parent, segments[sourceIndex + 1]].filter(Boolean).join("/");
213574
+ }
213575
+ if (segments[0] === "src" && segments.length >= 3) {
213576
+ return `${segments[1]}/${segments[2]}`;
213577
+ }
213578
+ if (fallbackRoot) {
213579
+ const rootSegments = toPathSegments(fallbackRoot);
213580
+ const relativeSegments = segments.slice(rootSegments.length);
213581
+ if (relativeSegments.length > 0) {
213582
+ return relativeSegments.join("/");
213583
+ }
213584
+ }
213585
+ return segments.slice(-1)[0] ?? relativePath;
213586
+ }
213587
+ function isKnownNestedModuleRoot(relativePath) {
213588
+ const segments = toPathSegments(relativePath).map((segment) => segment.toLowerCase());
213589
+ if (segments.length < 2) {
213590
+ return false;
213591
+ }
213592
+ const lastParent = segments[segments.length - 2];
213593
+ if (["modules", "features", "domain", "domains", "application", "infrastructure"].includes(lastParent)) {
213594
+ return true;
213595
+ }
213596
+ if (segments.length >= 3 && segments[segments.length - 3] === "src") {
213597
+ return true;
213598
+ }
213599
+ return false;
213600
+ }
213601
+ async function inspectPotentialModuleRoot(absolutePath) {
213602
+ let sourceFileCount = 0;
213603
+ let hasPublicEntrypoint = false;
213604
+ const queue = [{ absolutePath, depth: 0 }];
213605
+ while (queue.length > 0) {
213606
+ const current = queue.shift();
213607
+ if (!current) {
213608
+ continue;
213609
+ }
213610
+ let entries;
213611
+ try {
213612
+ entries = await import_node_fs.promises.readdir(current.absolutePath, { withFileTypes: true });
213613
+ } catch {
213614
+ continue;
213615
+ }
213616
+ for (const entry of entries) {
213617
+ const entryPath = path.join(current.absolutePath, entry.name);
213618
+ if (entry.isDirectory()) {
213619
+ if (current.depth >= 2 || isIgnoredDirectoryName(entry.name)) {
213620
+ continue;
213621
+ }
213622
+ queue.push({ absolutePath: entryPath, depth: current.depth + 1 });
213623
+ continue;
213624
+ }
213625
+ if (!entry.isFile()) {
213626
+ continue;
213627
+ }
213628
+ if (sourceFileExtensions.has(path.extname(entry.name).toLowerCase())) {
213629
+ sourceFileCount += 1;
213630
+ }
213631
+ if (current.depth === 0 && publicEntrypointNames.has(entry.name.toLowerCase())) {
213632
+ hasPublicEntrypoint = true;
213633
+ }
213634
+ }
213635
+ }
213636
+ return { sourceFileCount, hasPublicEntrypoint };
213637
+ }
213638
+ async function addModuleIfStrongCandidate(workspaceRoot, discovered, sourcePath, moduleId, options) {
213639
+ const normalizedSourcePath = normalizePath(sourcePath);
213640
+ const name = normalizedSourcePath.split("/").pop() ?? moduleId;
213641
+ if (options?.allowNoisyNames !== true && isNoisyModuleName(name)) {
213642
+ return;
213643
+ }
213644
+ const absolutePath = path.join(workspaceRoot, ...normalizedSourcePath.split("/"));
213645
+ const inspected = await inspectPotentialModuleRoot(absolutePath);
213646
+ const hasStrongEvidence = inspected.sourceFileCount >= 2 || inspected.hasPublicEntrypoint || isKnownNestedModuleRoot(normalizedSourcePath);
213647
+ if (options?.requireStrongEvidence !== false && !hasStrongEvidence) {
213648
+ return;
213649
+ }
213650
+ const normalizedModuleId = normalizePath(moduleId || toModuleIdFromPath(normalizedSourcePath, options?.fallbackRoot));
213651
+ if (!normalizedModuleId || options?.allowNoisyNames !== true && isNoisyModuleName(normalizedModuleId.split("/").pop() ?? normalizedModuleId)) {
213652
+ return;
213653
+ }
213654
+ if (!discovered.has(normalizedModuleId)) {
213655
+ discovered.set(normalizedModuleId, {
213656
+ moduleId: normalizedModuleId,
213657
+ sourcePath: normalizedSourcePath
213658
+ });
213659
+ }
213660
+ }
213661
+ async function addChildrenFromRoot(workspaceRoot, discovered, rootRelativePath, options) {
213662
+ const normalizedRoot = normalizePath(rootRelativePath);
213663
+ const absoluteRoot = path.join(workspaceRoot, ...normalizedRoot.split("/"));
213664
+ if (!await pathExists(absoluteRoot)) {
213665
+ return;
213666
+ }
213667
+ const entries = (await import_node_fs.promises.readdir(absoluteRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !isIgnoredDirectoryName(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
213668
+ for (const entry of entries) {
213669
+ const sourcePath = `${normalizedRoot}/${entry.name}`;
213670
+ const moduleId = options?.includeParentPrefix && options.parentId ? `${options.parentId}/${entry.name}` : toModuleIdFromPath(sourcePath, normalizedRoot);
213671
+ await addModuleIfStrongCandidate(workspaceRoot, discovered, sourcePath, moduleId, {
213672
+ fallbackRoot: normalizedRoot,
213673
+ ...options?.requireStrongEvidence !== void 0 ? { requireStrongEvidence: options.requireStrongEvidence } : {},
213674
+ ...options?.allowNoisyNames !== void 0 ? { allowNoisyNames: options.allowNoisyNames } : {}
213675
+ });
213676
+ }
213677
+ }
213678
+ async function addMonorepoContainerModules(workspaceRoot, discovered, container) {
213679
+ const absoluteContainer = path.join(workspaceRoot, container);
213680
+ if (!await pathExists(absoluteContainer)) {
213681
+ return;
213682
+ }
213683
+ const entries = (await import_node_fs.promises.readdir(absoluteContainer, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && !isIgnoredDirectoryName(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
213684
+ for (const entry of entries) {
213685
+ const packagePath = `${container}/${entry.name}`;
213686
+ await addModuleIfStrongCandidate(workspaceRoot, discovered, packagePath, entry.name, {
213687
+ requireStrongEvidence: false
213688
+ });
213689
+ for (const sourceRoot of [
213690
+ `${packagePath}/src`,
213691
+ `${packagePath}/src/modules`,
213692
+ `${packagePath}/src/features`,
213693
+ `${packagePath}/src/domain`,
213694
+ `${packagePath}/src/domains`,
213695
+ `${packagePath}/src/application`,
213696
+ `${packagePath}/src/infrastructure`
213697
+ ]) {
213698
+ await addChildrenFromRoot(workspaceRoot, discovered, sourceRoot, {
213699
+ parentId: entry.name,
213700
+ includeParentPrefix: true
213701
+ });
213702
+ }
213703
+ }
213704
+ }
213705
+ async function discoverHierarchicalModuleRoots(workspaceRoot, modulesRootRelativePath = "src/modules") {
213706
+ const discovered = /* @__PURE__ */ new Map();
213707
+ await addChildrenFromRoot(workspaceRoot, discovered, normalizePath(modulesRootRelativePath), {
213708
+ requireStrongEvidence: false,
213709
+ allowNoisyNames: normalizePath(modulesRootRelativePath) === "src"
213710
+ });
213711
+ for (const root of [
213712
+ "src/modules",
213713
+ "src/features",
213714
+ "src/domain",
213715
+ "src/domains",
213716
+ "src/application",
213717
+ "src/infrastructure"
213718
+ ]) {
213719
+ await addChildrenFromRoot(workspaceRoot, discovered, root);
213720
+ }
213721
+ for (const container of ["packages", "apps", "services"]) {
213722
+ await addMonorepoContainerModules(workspaceRoot, discovered, container);
213723
+ }
213724
+ return [...discovered.values()].sort((left, right) => left.moduleId.localeCompare(right.moduleId));
213725
+ }
213478
213726
  async function discoverModuleRoots(workspaceRoot, modulesRootRelativePath = "src/modules") {
213727
+ const hierarchicalModules = await discoverHierarchicalModuleRoots(workspaceRoot, modulesRootRelativePath);
213728
+ if (hierarchicalModules.length > 0) {
213729
+ return new Map(
213730
+ hierarchicalModules.map((entry) => [
213731
+ entry.moduleId,
213732
+ path.join(workspaceRoot, ...entry.sourcePath.split("/"))
213733
+ ])
213734
+ );
213735
+ }
213479
213736
  const moduleDirectories = await listModuleDirectoryNames(workspaceRoot, modulesRootRelativePath);
213480
213737
  const modulesRoot = path.join(workspaceRoot, ...modulesRootRelativePath.split("/"));
213481
213738
  return new Map(
@@ -214854,7 +215111,8 @@ function buildArchitectureContract(base, overrides) {
214854
215111
  }
214855
215112
  function buildDefaultModuleContractPath(moduleName, contract) {
214856
215113
  const contractsRoot = resolveContractsRootFromContract(contract);
214857
- return `${contractsRoot}/${moduleName}.contract.json`;
215114
+ const safeModuleName = moduleName.replace(/\\/g, "/").split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0).join(".");
215115
+ return `${contractsRoot}/${safeModuleName}.contract.json`;
214858
215116
  }
214859
215117
  async function loadArchitectureContract(workspaceRoot) {
214860
215118
  const configPath = path7.join(workspaceRoot, ".archpilot", "architecture.json");
@@ -214985,8 +215243,63 @@ var DependencyRulesConfigError = class extends Error {
214985
215243
  function normalizePath2(filePath) {
214986
215244
  return filePath.replace(/\\/g, "/");
214987
215245
  }
214988
- function getModulesRoot(workspaceRoot, modulesRootRelativePath = "src/modules") {
214989
- return path8.join(workspaceRoot, ...modulesRootRelativePath.split("/"));
215246
+ function toSafeContractModuleFileStem(moduleName) {
215247
+ return normalizePath2(moduleName).split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0).join(".");
215248
+ }
215249
+ function normalizeAbsolutePath(filePath) {
215250
+ return normalizePath2(path8.resolve(filePath)).toLowerCase();
215251
+ }
215252
+ function isPathInsideOrEqual(childPath, parentPath) {
215253
+ const child = normalizeAbsolutePath(childPath);
215254
+ const parent = normalizeAbsolutePath(parentPath);
215255
+ return child === parent || child.startsWith(`${parent}/`);
215256
+ }
215257
+ function isParentChildModuleRelationship(leftModule, rightModule) {
215258
+ if (leftModule === rightModule) {
215259
+ return false;
215260
+ }
215261
+ return leftModule.startsWith(`${rightModule}/`) || rightModule.startsWith(`${leftModule}/`);
215262
+ }
215263
+ function isPeerModuleImport(dependencyImport) {
215264
+ return !isParentChildModuleRelationship(
215265
+ dependencyImport.sourceModule,
215266
+ dependencyImport.targetModule
215267
+ );
215268
+ }
215269
+ function filterPeerModuleImports(imports) {
215270
+ return imports.filter(isPeerModuleImport);
215271
+ }
215272
+ function buildModuleHierarchyEdges(moduleIds) {
215273
+ const moduleIdSet = new Set(moduleIds);
215274
+ const edges = [];
215275
+ for (const moduleId of moduleIds) {
215276
+ const parentModule = moduleId.split("/").slice(0, -1).map((_, index, segments) => segments.slice(0, segments.length - index).join("/")).find((candidate) => moduleIdSet.has(candidate));
215277
+ if (!parentModule) {
215278
+ continue;
215279
+ }
215280
+ edges.push({ parentModule, childModule: moduleId });
215281
+ }
215282
+ return edges.sort((left, right) => {
215283
+ const parentCompare = left.parentModule.localeCompare(right.parentModule);
215284
+ if (parentCompare !== 0) {
215285
+ return parentCompare;
215286
+ }
215287
+ return left.childModule.localeCompare(right.childModule);
215288
+ });
215289
+ }
215290
+ function buildChildModulesByParent(hierarchyEdges) {
215291
+ const byParent = /* @__PURE__ */ new Map();
215292
+ for (const edge of hierarchyEdges) {
215293
+ const children = byParent.get(edge.parentModule) ?? /* @__PURE__ */ new Set();
215294
+ children.add(edge.childModule);
215295
+ byParent.set(edge.parentModule, children);
215296
+ }
215297
+ return new Map(
215298
+ [...byParent.entries()].map(([parentModule, childModules]) => [
215299
+ parentModule,
215300
+ sortUnique2(childModules)
215301
+ ])
215302
+ );
214990
215303
  }
214991
215304
  function getScriptKind(filePath) {
214992
215305
  const extension = path8.extname(filePath).toLowerCase();
@@ -215020,6 +215333,14 @@ function usesPythonImportParser(adapter) {
215020
215333
  function supportsDependencyImportParsing(adapter) {
215021
215334
  return usesTypeScriptParser(adapter) || usesJavaImportParser(adapter) || usesPhpImportParser(adapter) || usesGoImportParser(adapter) || usesPythonImportParser(adapter);
215022
215335
  }
215336
+ async function pathExists4(targetPath) {
215337
+ try {
215338
+ await import_node_fs8.promises.access(targetPath);
215339
+ return true;
215340
+ } catch {
215341
+ return false;
215342
+ }
215343
+ }
215023
215344
  async function readTextFileIfExists2(filePath) {
215024
215345
  try {
215025
215346
  return await import_node_fs8.promises.readFile(filePath, { encoding: "utf8" });
@@ -215027,6 +215348,95 @@ async function readTextFileIfExists2(filePath) {
215027
215348
  return void 0;
215028
215349
  }
215029
215350
  }
215351
+ async function readPackageName(packageJsonPath) {
215352
+ const contents = await readTextFileIfExists2(packageJsonPath);
215353
+ if (!contents) {
215354
+ return void 0;
215355
+ }
215356
+ try {
215357
+ const parsed = JSON.parse(contents);
215358
+ return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : void 0;
215359
+ } catch {
215360
+ return void 0;
215361
+ }
215362
+ }
215363
+ async function buildWorkspacePackageAliasMap(workspaceRoot, moduleRoots) {
215364
+ const aliases = [];
215365
+ for (const [moduleId, moduleRoot] of [...moduleRoots.entries()].sort(
215366
+ ([left], [right]) => left.localeCompare(right)
215367
+ )) {
215368
+ const packageName = await readPackageName(path8.join(moduleRoot, "package.json"));
215369
+ if (!packageName) {
215370
+ continue;
215371
+ }
215372
+ aliases.push({ packageName, moduleId, moduleRoot });
215373
+ }
215374
+ aliases.push(...await buildTsConfigPathAliasMap(workspaceRoot, moduleRoots));
215375
+ return aliases.sort((left, right) => {
215376
+ const lengthCompare = right.packageName.length - left.packageName.length;
215377
+ if (lengthCompare !== 0) {
215378
+ return lengthCompare;
215379
+ }
215380
+ return left.packageName.localeCompare(right.packageName);
215381
+ });
215382
+ }
215383
+ async function buildTsConfigPathAliasMap(workspaceRoot, moduleRoots) {
215384
+ const contents = await readTextFileIfExists2(path8.join(workspaceRoot, "tsconfig.json"));
215385
+ if (!contents) {
215386
+ return [];
215387
+ }
215388
+ let paths;
215389
+ try {
215390
+ const parsed = JSON.parse(contents);
215391
+ paths = parsed.compilerOptions?.paths;
215392
+ } catch {
215393
+ return [];
215394
+ }
215395
+ if (!paths) {
215396
+ return [];
215397
+ }
215398
+ const aliases = [];
215399
+ for (const [aliasPattern, targetPatterns] of Object.entries(paths).sort(
215400
+ ([left], [right]) => left.localeCompare(right)
215401
+ )) {
215402
+ if (!Array.isArray(targetPatterns)) {
215403
+ continue;
215404
+ }
215405
+ const aliasPrefix = aliasPattern.replace(/\/\*$/u, "");
215406
+ if (aliasPrefix.length === 0) {
215407
+ continue;
215408
+ }
215409
+ for (const targetPattern of targetPatterns) {
215410
+ if (typeof targetPattern !== "string" || targetPattern.trim().length === 0) {
215411
+ continue;
215412
+ }
215413
+ const isWildcard = /\/\*$/u.test(aliasPattern) && /\/\*$/u.test(targetPattern);
215414
+ const normalizedTarget = normalizePath2(targetPattern).replace(/\/\*$/u, "");
215415
+ const absoluteTarget = path8.join(workspaceRoot, ...normalizedTarget.split("/"));
215416
+ const moduleEntry = [...moduleRoots.entries()].filter(([, moduleRoot2]) => isPathInsideOrEqual(absoluteTarget, moduleRoot2)).sort((left, right) => right[1].length - left[1].length || left[0].localeCompare(right[0]))[0];
215417
+ if (!moduleEntry) {
215418
+ continue;
215419
+ }
215420
+ const [moduleId, moduleRoot] = moduleEntry;
215421
+ if (isWildcard) {
215422
+ aliases.push({
215423
+ packageName: aliasPrefix,
215424
+ moduleId,
215425
+ moduleRoot,
215426
+ targetRoot: absoluteTarget
215427
+ });
215428
+ continue;
215429
+ }
215430
+ aliases.push({
215431
+ packageName: aliasPrefix,
215432
+ moduleId,
215433
+ moduleRoot,
215434
+ targetFile: absoluteTarget
215435
+ });
215436
+ }
215437
+ }
215438
+ return aliases;
215439
+ }
215030
215440
  function validateDependencyModuleRules(moduleName, value) {
215031
215441
  if (!value || typeof value !== "object" || Array.isArray(value)) {
215032
215442
  throw new DependencyRulesConfigError(
@@ -215086,8 +215496,44 @@ async function readDependencyRulesConfig(workspaceRoot) {
215086
215496
  }, {});
215087
215497
  return { modules };
215088
215498
  }
215089
- async function findModuleRoots(workspaceRoot, modulesRootRelativePath = "src/modules") {
215090
- return discoverModuleRoots(workspaceRoot, modulesRootRelativePath);
215499
+ async function findModuleRoots(workspaceRoot, modulesRootRelativePath = "src/modules", architectureContract) {
215500
+ const discoveredModuleRoots = await discoverModuleRoots(
215501
+ workspaceRoot,
215502
+ modulesRootRelativePath
215503
+ );
215504
+ const moduleRoots = /* @__PURE__ */ new Map();
215505
+ const registryEntries = architectureContract?.modules ? Object.entries(architectureContract.modules).sort(
215506
+ ([left], [right]) => left.localeCompare(right)
215507
+ ) : [];
215508
+ for (const [moduleName, moduleReference] of registryEntries) {
215509
+ const configuredPath = moduleReference.path;
215510
+ if (!configuredPath || typeof configuredPath !== "string") {
215511
+ continue;
215512
+ }
215513
+ const normalizedConfiguredPath = normalizePath2(configuredPath);
215514
+ if (/\.[a-z0-9]+$/iu.test(normalizedConfiguredPath)) {
215515
+ continue;
215516
+ }
215517
+ const absolutePath = path8.join(
215518
+ workspaceRoot,
215519
+ ...normalizedConfiguredPath.split("/")
215520
+ );
215521
+ try {
215522
+ const stats = await import_node_fs8.promises.stat(absolutePath);
215523
+ if (stats.isDirectory()) {
215524
+ moduleRoots.set(moduleName, absolutePath);
215525
+ }
215526
+ } catch {
215527
+ }
215528
+ }
215529
+ for (const [moduleName, moduleRoot] of discoveredModuleRoots.entries()) {
215530
+ if (!moduleRoots.has(moduleName)) {
215531
+ moduleRoots.set(moduleName, moduleRoot);
215532
+ }
215533
+ }
215534
+ return new Map(
215535
+ [...moduleRoots.entries()].sort(([left], [right]) => left.localeCompare(right))
215536
+ );
215091
215537
  }
215092
215538
  async function buildExplicitStandaloneModulePathMap(workspaceRoot, architectureContract) {
215093
215539
  const moduleRegistry = architectureContract?.modules && typeof architectureContract.modules === "object" && !Array.isArray(architectureContract.modules) ? architectureContract.modules : void 0;
@@ -215500,7 +215946,45 @@ async function resolveImportPath(basePath, adapter) {
215500
215946
  }
215501
215947
  return void 0;
215502
215948
  }
215503
- async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath, moduleRoots, explicitStandaloneModuleByPath, sourceFilePath, importSpecifier, adapter) {
215949
+ function resolvePackageAliasRemainder(packageName, importSpecifier) {
215950
+ if (importSpecifier === packageName) {
215951
+ return "";
215952
+ }
215953
+ if (importSpecifier.startsWith(`${packageName}/`)) {
215954
+ return importSpecifier.slice(packageName.length + 1);
215955
+ }
215956
+ return void 0;
215957
+ }
215958
+ async function resolveWorkspacePackageImportPath(importSpecifier, workspacePackageAliases, adapter) {
215959
+ const packageAlias = workspacePackageAliases.map((alias2) => ({
215960
+ alias: alias2,
215961
+ remainder: resolvePackageAliasRemainder(alias2.packageName, importSpecifier)
215962
+ })).sort((left, right) => {
215963
+ const leftCanResolveRemainder = left.remainder !== void 0 && left.remainder.length > 0 && left.alias.targetRoot !== void 0;
215964
+ const rightCanResolveRemainder = right.remainder !== void 0 && right.remainder.length > 0 && right.alias.targetRoot !== void 0;
215965
+ if (leftCanResolveRemainder !== rightCanResolveRemainder) {
215966
+ return leftCanResolveRemainder ? -1 : 1;
215967
+ }
215968
+ return 0;
215969
+ }).find(
215970
+ (candidate) => candidate.remainder !== void 0
215971
+ );
215972
+ if (!packageAlias) {
215973
+ return void 0;
215974
+ }
215975
+ const { alias, remainder } = packageAlias;
215976
+ if (remainder.length === 0) {
215977
+ return resolveImportPath(alias.targetFile ?? alias.targetRoot ?? alias.moduleRoot, adapter);
215978
+ }
215979
+ if (alias.targetRoot) {
215980
+ return resolveImportPath(
215981
+ path8.join(alias.targetRoot, ...remainder.split("/").filter((segment) => segment.length > 0)),
215982
+ adapter
215983
+ );
215984
+ }
215985
+ return void 0;
215986
+ }
215987
+ async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath, moduleRoots, explicitStandaloneModuleByPath, workspacePackageAliases, sourceFilePath, importSpecifier, adapter) {
215504
215988
  let basePath;
215505
215989
  const normalizedModulesRoot = normalizePath2(modulesRootRelativePath).replace(/^\/+/, "");
215506
215990
  if (usesJavaImportParser(adapter) && !importSpecifier.startsWith(".")) {
@@ -215555,6 +216039,22 @@ async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath,
215555
216039
  basePath = path8.join(workspaceRoot, importSpecifier.slice(1));
215556
216040
  } else if (normalizedModulesRoot.startsWith("src/") && importSpecifier.startsWith(`${normalizedModulesRoot.slice("src/".length)}/`)) {
215557
216041
  basePath = path8.join(workspaceRoot, "src", importSpecifier);
216042
+ } else {
216043
+ const workspacePackageImportPath = await resolveWorkspacePackageImportPath(
216044
+ importSpecifier,
216045
+ workspacePackageAliases,
216046
+ adapter
216047
+ );
216048
+ if (workspacePackageImportPath) {
216049
+ basePath = workspacePackageImportPath;
216050
+ } else {
216051
+ const moduleImportTarget = [...moduleRoots.entries()].filter(([moduleId]) => importSpecifier === moduleId || importSpecifier.startsWith(`${moduleId}/`)).sort((left, right) => right[0].length - left[0].length || left[0].localeCompare(right[0]))[0];
216052
+ if (moduleImportTarget) {
216053
+ const [moduleId, moduleRoot] = moduleImportTarget;
216054
+ const remainder = importSpecifier === moduleId ? "" : importSpecifier.slice(moduleId.length + 1);
216055
+ basePath = path8.join(moduleRoot, ...remainder.split("/").filter((segment) => segment.length > 0));
216056
+ }
216057
+ }
215558
216058
  }
215559
216059
  if (!basePath) {
215560
216060
  return void 0;
@@ -215563,17 +216063,8 @@ async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath,
215563
216063
  if (!resolvedPath) {
215564
216064
  return void 0;
215565
216065
  }
215566
- const modulesRoot = getModulesRoot(workspaceRoot, modulesRootRelativePath);
215567
- const relativeToModulesRoot = normalizePath2(path8.relative(modulesRoot, resolvedPath));
215568
- if (relativeToModulesRoot === "" || relativeToModulesRoot.startsWith("../") || relativeToModulesRoot.startsWith("..\\")) {
215569
- return void 0;
215570
- }
215571
- const segments = relativeToModulesRoot.split("/");
215572
- if (segments.length === 0) {
215573
- return void 0;
215574
- }
215575
- const targetModule = segments[0];
215576
- if (!moduleRoots.has(targetModule)) {
216066
+ const targetModuleEntry = [...moduleRoots.entries()].filter(([, moduleRoot]) => isPathInsideOrEqual(resolvedPath, moduleRoot)).sort((left, right) => right[1].length - left[1].length || left[0].localeCompare(right[0]))[0];
216067
+ if (!targetModuleEntry) {
215577
216068
  const standaloneTargetModule = explicitStandaloneModuleByPath.get(
215578
216069
  normalizePath2(path8.relative(workspaceRoot, resolvedPath))
215579
216070
  );
@@ -215586,11 +216077,18 @@ async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath,
215586
216077
  isPublicImport: true
215587
216078
  };
215588
216079
  }
215589
- const targetSubPath = segments.slice(1).join("/");
216080
+ const [targetModule, targetModuleRoot] = targetModuleEntry;
216081
+ const targetSubPath = normalizePath2(path8.relative(targetModuleRoot, resolvedPath));
216082
+ if (targetSubPath === "" || targetSubPath.startsWith("../") || targetSubPath.startsWith("..\\")) {
216083
+ return void 0;
216084
+ }
215590
216085
  const indexEntrypointCandidates = adapter.dependencyParsingFileExtensions.map(
215591
216086
  (extension) => `index${extension}`
215592
216087
  );
215593
- const isPublicImport = indexEntrypointCandidates.includes(targetSubPath) || adapter.publicEntrypointDirectoryNames.some(
216088
+ const nestedIndexEntrypointCandidates = adapter.dependencyParsingFileExtensions.flatMap(
216089
+ (extension) => [`src/index${extension}`, `public/index${extension}`]
216090
+ );
216091
+ const isPublicImport = indexEntrypointCandidates.includes(targetSubPath) || nestedIndexEntrypointCandidates.includes(targetSubPath) || adapter.publicEntrypointDirectoryNames.some(
215594
216092
  (dirName) => targetSubPath.startsWith(`${dirName}/`)
215595
216093
  );
215596
216094
  return {
@@ -215599,7 +216097,7 @@ async function resolveCrossModuleImport(workspaceRoot, modulesRootRelativePath,
215599
216097
  isPublicImport
215600
216098
  };
215601
216099
  }
215602
- async function collectCrossModuleImports(workspaceRoot, modulesRootRelativePath, moduleRoots, explicitStandaloneModuleByPath, adapter, options) {
216100
+ async function collectCrossModuleImports(workspaceRoot, modulesRootRelativePath, moduleRoots, explicitStandaloneModuleByPath, workspacePackageAliases, adapter, options) {
215603
216101
  const imports = [];
215604
216102
  const includeTestFiles = options?.includeTestFiles === true;
215605
216103
  if (!supportsDependencyImportParsing(adapter)) {
@@ -215608,7 +216106,10 @@ async function collectCrossModuleImports(workspaceRoot, modulesRootRelativePath,
215608
216106
  for (const [moduleName, moduleRoot] of [...moduleRoots.entries()].sort(
215609
216107
  ([left], [right]) => left.localeCompare(right)
215610
216108
  )) {
215611
- const sourceFiles = await collectSourceFiles(moduleRoot, adapter);
216109
+ const childModuleRoots = [...moduleRoots.entries()].filter(([childModuleName, childModuleRoot]) => childModuleName !== moduleName && isPathInsideOrEqual(childModuleRoot, moduleRoot)).map(([, childModuleRoot]) => childModuleRoot);
216110
+ const sourceFiles = (await collectSourceFiles(moduleRoot, adapter)).filter(
216111
+ (sourceFile) => !childModuleRoots.some((childModuleRoot) => isPathInsideOrEqual(sourceFile, childModuleRoot))
216112
+ );
215612
216113
  for (const sourceFilePath of sourceFiles) {
215613
216114
  const sourceFileRelativePath = normalizePath2(
215614
216115
  path8.relative(workspaceRoot, sourceFilePath)
@@ -215631,6 +216132,7 @@ async function collectCrossModuleImports(workspaceRoot, modulesRootRelativePath,
215631
216132
  modulesRootRelativePath,
215632
216133
  moduleRoots,
215633
216134
  explicitStandaloneModuleByPath,
216135
+ workspacePackageAliases,
215634
216136
  sourceFilePath,
215635
216137
  importReference.specifier,
215636
216138
  adapter
@@ -215776,7 +216278,7 @@ async function loadModuleArchitectureContract(workspaceRoot, architectureContrac
215776
216278
  const registryEntry = architectureContract.modules[moduleName];
215777
216279
  const contractPath = registryEntry?.contract ?? path8.posix.join(
215778
216280
  resolveContractsRootFromContract(architectureContract),
215779
- `${moduleName}.contract.json`
216281
+ `${toSafeContractModuleFileStem(moduleName)}.contract.json`
215780
216282
  );
215781
216283
  const fullContractPath = path8.join(workspaceRoot, ...contractPath.split("/"));
215782
216284
  const content = await readTextFileIfExists2(fullContractPath);
@@ -215862,11 +216364,89 @@ function isImportWithinPublicEntrypoints(resolvedTargetRelativePath, publicEntry
215862
216364
  }
215863
216365
  }
215864
216366
  }
215865
- return false;
216367
+ return false;
216368
+ }
216369
+ async function filterExistingPublicEntrypoints(workspaceRoot, publicEntrypoints) {
216370
+ const existingEntrypoints = [];
216371
+ for (const publicEntrypoint of normalizePathList([...publicEntrypoints])) {
216372
+ const absolutePath = path8.join(workspaceRoot, ...publicEntrypoint.split("/"));
216373
+ if (await pathExists4(absolutePath)) {
216374
+ existingEntrypoints.push(publicEntrypoint);
216375
+ }
216376
+ }
216377
+ return existingEntrypoints;
216378
+ }
216379
+ async function inferExistingPublicEntrypointsFromModuleRoot(workspaceRoot, moduleRoot) {
216380
+ if (!moduleRoot) {
216381
+ return [];
216382
+ }
216383
+ const candidateSubpaths = [
216384
+ "index.ts",
216385
+ "index.tsx",
216386
+ "index.js",
216387
+ "index.jsx",
216388
+ "src/index.ts",
216389
+ "src/index.tsx",
216390
+ "src/index.js",
216391
+ "src/index.jsx",
216392
+ "public/index.ts",
216393
+ "public/index.tsx",
216394
+ "public/index.js",
216395
+ "public/index.jsx"
216396
+ ];
216397
+ const existingEntrypoints = [];
216398
+ for (const candidateSubpath of candidateSubpaths) {
216399
+ const candidatePath = path8.join(moduleRoot, ...candidateSubpath.split("/"));
216400
+ if (await pathExists4(candidatePath)) {
216401
+ existingEntrypoints.push(
216402
+ normalizePath2(path8.relative(workspaceRoot, candidatePath))
216403
+ );
216404
+ }
216405
+ }
216406
+ return existingEntrypoints.sort((left, right) => left.localeCompare(right));
216407
+ }
216408
+ async function resolveModulePublicEntrypoints(workspaceRoot, architectureContract, moduleName, targetContract, moduleRoots) {
216409
+ const registryPublicEntrypoints = architectureContract.modules[moduleName]?.publicEntrypoints ?? [];
216410
+ const explicitPublicEntrypoints = sortUnique2([
216411
+ ...registryPublicEntrypoints,
216412
+ ...targetContract.publicEntrypoints
216413
+ ]);
216414
+ const existingExplicitEntrypoints = await filterExistingPublicEntrypoints(
216415
+ workspaceRoot,
216416
+ explicitPublicEntrypoints
216417
+ );
216418
+ if (existingExplicitEntrypoints.length > 0) {
216419
+ return existingExplicitEntrypoints;
216420
+ }
216421
+ return inferExistingPublicEntrypointsFromModuleRoot(
216422
+ workspaceRoot,
216423
+ moduleRoots.get(moduleName)
216424
+ );
216425
+ }
216426
+ function labelPublicEntrypoint(publicEntrypoint) {
216427
+ const normalizedEntrypoint = normalizePath2(publicEntrypoint);
216428
+ const fileName = normalizedEntrypoint.split("/").at(-1) ?? "";
216429
+ return /^index\.[cm]?[jt]sx?$/iu.test(fileName) ? `${fileName} public entrypoint` : "public entrypoint";
215866
216430
  }
215867
- function buildModuleIndexEntrypointPath(modulesRootRelativePath, moduleName, adapter) {
215868
- const entrypointFileName = adapter.scaffoldingDefaults?.moduleIndexFileName ?? "index.ts";
215869
- return normalizePath2(`${modulesRootRelativePath}/${moduleName}/${entrypointFileName}`);
216431
+ function describePublicEntrypointTargets(publicEntrypoints) {
216432
+ if (publicEntrypoints.length <= 1) {
216433
+ return publicEntrypoints[0] ?? "unknown public entrypoint";
216434
+ }
216435
+ return publicEntrypoints.join(", ");
216436
+ }
216437
+ async function describeModulePublicImportGuidance(workspaceRoot, moduleName, moduleRoots) {
216438
+ const moduleRoot = moduleRoots.get(moduleName);
216439
+ const inferredEntrypoints = await inferExistingPublicEntrypointsFromModuleRoot(
216440
+ workspaceRoot,
216441
+ moduleRoot
216442
+ );
216443
+ if (inferredEntrypoints.length > 0) {
216444
+ return `Use the public entrypoint at ${describePublicEntrypointTargets(inferredEntrypoints)} instead.`;
216445
+ }
216446
+ if (moduleRoot) {
216447
+ return `Use an existing public entrypoint under ${normalizePath2(path8.relative(workspaceRoot, moduleRoot))} instead.`;
216448
+ }
216449
+ return "Use an existing target module public entrypoint instead.";
215870
216450
  }
215871
216451
  function buildActualImportedModuleSetBySource(imports, registeredModuleIds) {
215872
216452
  const bySource = /* @__PURE__ */ new Map();
@@ -215918,11 +216498,19 @@ function buildInboundDependencyMap(bySource, moduleIds) {
215918
216498
  async function buildDependencyGraphSummary(workspaceRoot, architectureContract, options) {
215919
216499
  const modulesRoot = options?.modulesRootRelativePath ?? resolveModulesRootFromContract(architectureContract);
215920
216500
  const adapter = resolvePrimaryAdapterFromContract(architectureContract);
215921
- const moduleRoots = await findModuleRoots(workspaceRoot, modulesRoot);
216501
+ const moduleRoots = await findModuleRoots(
216502
+ workspaceRoot,
216503
+ modulesRoot,
216504
+ architectureContract
216505
+ );
215922
216506
  const explicitStandaloneModuleByPath = await buildExplicitStandaloneModulePathMap(
215923
216507
  workspaceRoot,
215924
216508
  architectureContract
215925
216509
  );
216510
+ const workspacePackageAliases = await buildWorkspacePackageAliasMap(
216511
+ workspaceRoot,
216512
+ moduleRoots
216513
+ );
215926
216514
  const moduleIds = sortUnique2([...moduleRoots.keys(), ...explicitStandaloneModuleByPath.values()]);
215927
216515
  const registeredModuleIds = new Set(moduleIds);
215928
216516
  const imports = await collectCrossModuleImports(
@@ -215930,9 +216518,11 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
215930
216518
  modulesRoot,
215931
216519
  moduleRoots,
215932
216520
  explicitStandaloneModuleByPath,
216521
+ workspacePackageAliases,
215933
216522
  adapter
215934
216523
  );
215935
- const actualBySource = buildActualImportedModuleSetBySource(imports, registeredModuleIds);
216524
+ const peerImports = filterPeerModuleImports(imports);
216525
+ const actualBySource = buildActualImportedModuleSetBySource(peerImports, registeredModuleIds);
215936
216526
  const moduleContractCache = /* @__PURE__ */ new Map();
215937
216527
  const declaredBySource = /* @__PURE__ */ new Map();
215938
216528
  const hasContractByModule = /* @__PURE__ */ new Map();
@@ -215950,7 +216540,7 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
215950
216540
  new Set(
215951
216541
  sortUnique2(
215952
216542
  sourceContract.contract.dependsOn.filter(
215953
- (dependencyModule) => registeredModuleIds.has(dependencyModule)
216543
+ (dependencyModule) => registeredModuleIds.has(dependencyModule) && !isParentChildModuleRelationship(moduleId, dependencyModule)
215954
216544
  )
215955
216545
  )
215956
216546
  )
@@ -215962,6 +216552,11 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
215962
216552
  }
215963
216553
  const inboundDeclared = buildInboundDependencyMap(declaredBySource, moduleIds);
215964
216554
  const inboundActual = buildInboundDependencyMap(actualBySource, moduleIds);
216555
+ const hierarchyEdges = buildModuleHierarchyEdges(moduleIds);
216556
+ const childModulesByParent = buildChildModulesByParent(hierarchyEdges);
216557
+ const parentModuleByChild = new Map(
216558
+ hierarchyEdges.map((edge) => [edge.childModule, edge.parentModule])
216559
+ );
215965
216560
  return {
215966
216561
  modules: moduleIds.map((moduleId) => {
215967
216562
  const declaredDependencies = sortUnique2(declaredBySource.get(moduleId) ?? []);
@@ -215975,6 +216570,8 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
215975
216570
  return {
215976
216571
  module: moduleId,
215977
216572
  hasContract: hasContractByModule.get(moduleId) ?? false,
216573
+ ...parentModuleByChild.has(moduleId) ? { parentModule: parentModuleByChild.get(moduleId) } : {},
216574
+ childModules: childModulesByParent.get(moduleId) ?? [],
215978
216575
  declaredDependencies,
215979
216576
  actualDependencies,
215980
216577
  inboundDeclaredFrom: inboundDeclared.get(moduleId) ?? [],
@@ -215982,7 +216579,8 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
215982
216579
  unusedDeclaredDependencies,
215983
216580
  undeclaredActualDependencies
215984
216581
  };
215985
- })
216582
+ }),
216583
+ hierarchyEdges
215986
216584
  };
215987
216585
  }
215988
216586
  async function validateDependencyContractBoundaries(workspaceRoot, architectureContract, options) {
@@ -215991,24 +216589,34 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
215991
216589
  }
215992
216590
  const modulesRoot = options.modulesRootRelativePath ?? resolveModulesRootFromContract(architectureContract);
215993
216591
  const adapter = resolvePrimaryAdapterFromContract(architectureContract);
215994
- const moduleRoots = await findModuleRoots(workspaceRoot, modulesRoot);
216592
+ const moduleRoots = await findModuleRoots(
216593
+ workspaceRoot,
216594
+ modulesRoot,
216595
+ architectureContract
216596
+ );
215995
216597
  const explicitStandaloneModuleByPath = await buildExplicitStandaloneModulePathMap(
215996
216598
  workspaceRoot,
215997
216599
  architectureContract
215998
216600
  );
216601
+ const workspacePackageAliases = await buildWorkspacePackageAliasMap(
216602
+ workspaceRoot,
216603
+ moduleRoots
216604
+ );
215999
216605
  const imports = await collectCrossModuleImports(
216000
216606
  workspaceRoot,
216001
216607
  modulesRoot,
216002
216608
  moduleRoots,
216003
216609
  explicitStandaloneModuleByPath,
216610
+ workspacePackageAliases,
216004
216611
  adapter
216005
216612
  );
216613
+ const peerImports = filterPeerModuleImports(imports);
216006
216614
  const findings = [];
216007
216615
  const moduleContractCache = /* @__PURE__ */ new Map();
216008
216616
  if (options.checkDeclaredDependencies) {
216009
216617
  const failures = [];
216010
216618
  const skippedFindings = /* @__PURE__ */ new Map();
216011
- for (const dependencyImport of imports) {
216619
+ for (const dependencyImport of peerImports) {
216012
216620
  const sourceContract = await loadModuleArchitectureContract(
216013
216621
  workspaceRoot,
216014
216622
  architectureContract,
@@ -216050,6 +216658,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216050
216658
  ),
216051
216659
  filePath: dependencyImport.sourceFileRelativePath,
216052
216660
  line: dependencyImport.line,
216661
+ importSpecifier: dependencyImport.importSpecifier,
216053
216662
  findingType: "module-dependency",
216054
216663
  module: dependencyImport.sourceModule,
216055
216664
  target: dependencyImport.targetModule
@@ -216072,6 +216681,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216072
216681
  ),
216073
216682
  filePath: dependencyImport.sourceFileRelativePath,
216074
216683
  line: dependencyImport.line,
216684
+ importSpecifier: dependencyImport.importSpecifier,
216075
216685
  findingType: "module-dependency",
216076
216686
  module: dependencyImport.sourceModule,
216077
216687
  target: dependencyImport.targetModule
@@ -216080,7 +216690,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216080
216690
  }
216081
216691
  if (failures.length > 0) {
216082
216692
  findings.push(...failures);
216083
- } else if (imports.length === 0) {
216693
+ } else if (peerImports.length === 0) {
216084
216694
  findings.push({
216085
216695
  result: makeValidationResult(
216086
216696
  "AP-DEP-004",
@@ -216105,7 +216715,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216105
216715
  if (options.checkPublicSurfaceImports) {
216106
216716
  const failures = [];
216107
216717
  const skippedFindings = /* @__PURE__ */ new Map();
216108
- for (const dependencyImport of imports) {
216718
+ for (const dependencyImport of peerImports) {
216109
216719
  const sourceContract = await loadModuleArchitectureContract(
216110
216720
  workspaceRoot,
216111
216721
  architectureContract,
@@ -216165,6 +216775,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216165
216775
  ),
216166
216776
  filePath: dependencyImport.sourceFileRelativePath,
216167
216777
  line: dependencyImport.line,
216778
+ importSpecifier: dependencyImport.importSpecifier,
216168
216779
  findingType: "module-dependency",
216169
216780
  module: dependencyImport.sourceModule,
216170
216781
  target: dependencyImport.targetModule
@@ -216172,41 +216783,31 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216172
216783
  }
216173
216784
  continue;
216174
216785
  }
216175
- if (targetContract.contract.publicEntrypoints.length > 0 && normalizePath2(dependencyImport.resolvedTargetRelativePath) !== buildModuleIndexEntrypointPath(
216176
- modulesRoot,
216786
+ const publicEntrypoints = await resolveModulePublicEntrypoints(
216787
+ workspaceRoot,
216788
+ architectureContract,
216177
216789
  dependencyImport.targetModule,
216178
- adapter
216179
- )) {
216180
- failures.push({
216181
- result: makeValidationResult(
216182
- "AP-DEP-005",
216183
- "error",
216184
- false,
216185
- `Module '${dependencyImport.sourceModule}' must import module '${dependencyImport.targetModule}' via its index.ts public entrypoint: ${buildModuleIndexEntrypointPath(modulesRoot, dependencyImport.targetModule, adapter)}.`,
216186
- {
216187
- findingType: "module-dependency",
216188
- module: dependencyImport.sourceModule,
216189
- target: dependencyImport.targetModule
216190
- }
216191
- ),
216192
- filePath: dependencyImport.sourceFileRelativePath,
216193
- line: dependencyImport.line,
216194
- findingType: "module-dependency",
216195
- module: dependencyImport.sourceModule,
216196
- target: dependencyImport.targetModule
216197
- });
216790
+ targetContract.contract,
216791
+ moduleRoots
216792
+ );
216793
+ if (publicEntrypoints.length === 0) {
216794
+ continue;
216795
+ }
216796
+ if (options.suppressPublicSurfaceFailuresForNonPublicImports === true && !dependencyImport.isPublicImport) {
216198
216797
  continue;
216199
216798
  }
216200
- if (targetContract.contract.publicEntrypoints.length === 0 && !isImportWithinPublicEntrypoints(
216799
+ if (!isImportWithinPublicEntrypoints(
216201
216800
  dependencyImport.resolvedTargetRelativePath,
216202
- targetContract.contract.publicEntrypoints
216801
+ publicEntrypoints
216203
216802
  )) {
216803
+ const publicEntrypointLabel = labelPublicEntrypoint(publicEntrypoints[0] ?? "");
216804
+ const expectedEntrypoint = describePublicEntrypointTargets(publicEntrypoints);
216204
216805
  failures.push({
216205
216806
  result: makeValidationResult(
216206
216807
  "AP-DEP-005",
216207
216808
  "error",
216208
216809
  false,
216209
- `Module '${dependencyImport.sourceModule}' imports a non-public path from module '${dependencyImport.targetModule}': ${dependencyImport.resolvedTargetRelativePath}`,
216810
+ `Module '${dependencyImport.sourceModule}' must import module '${dependencyImport.targetModule}' via its ${publicEntrypointLabel}: ${expectedEntrypoint}.`,
216210
216811
  {
216211
216812
  findingType: "module-dependency",
216212
216813
  module: dependencyImport.sourceModule,
@@ -216215,6 +216816,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216215
216816
  ),
216216
216817
  filePath: dependencyImport.sourceFileRelativePath,
216217
216818
  line: dependencyImport.line,
216819
+ importSpecifier: dependencyImport.importSpecifier,
216218
216820
  findingType: "module-dependency",
216219
216821
  module: dependencyImport.sourceModule,
216220
216822
  target: dependencyImport.targetModule
@@ -216223,7 +216825,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216223
216825
  }
216224
216826
  if (failures.length > 0) {
216225
216827
  findings.push(...failures);
216226
- } else if (imports.length === 0) {
216828
+ } else if (peerImports.length === 0) {
216227
216829
  findings.push({
216228
216830
  result: makeValidationResult(
216229
216831
  "AP-DEP-005",
@@ -216252,7 +216854,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216252
216854
  ...explicitStandaloneModuleByPath.values()
216253
216855
  ]);
216254
216856
  const importedBySource = buildActualImportedModuleSetBySource(
216255
- imports,
216857
+ peerImports,
216256
216858
  registeredModuleIds
216257
216859
  );
216258
216860
  for (const moduleName of [...registeredModuleIds].sort(
@@ -216267,7 +216869,9 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216267
216869
  if (sourceContract.status !== "ok" || !sourceContract.contract) {
216268
216870
  continue;
216269
216871
  }
216270
- const declaredDependencies = [...sourceContract.contract.dependsOn].filter((dependencyModule) => registeredModuleIds.has(dependencyModule)).sort((left, right) => left.localeCompare(right));
216872
+ const declaredDependencies = [...sourceContract.contract.dependsOn].filter(
216873
+ (dependencyModule) => registeredModuleIds.has(dependencyModule) && !isParentChildModuleRelationship(moduleName, dependencyModule)
216874
+ ).sort((left, right) => left.localeCompare(right));
216271
216875
  if (declaredDependencies.length === 0) {
216272
216876
  continue;
216273
216877
  }
@@ -216317,18 +216921,28 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216317
216921
  const parsedArchitectureContract = await loadArchitectureContract(workspaceRoot);
216318
216922
  const adapter = resolvePrimaryAdapterFromContract(parsedArchitectureContract);
216319
216923
  const modulesRootRelativePath = options.modulesRootRelativePath ?? resolveModulesRootFromContract(parsedArchitectureContract);
216320
- const moduleRoots = await findModuleRoots(workspaceRoot, modulesRootRelativePath);
216924
+ const moduleRoots = await findModuleRoots(
216925
+ workspaceRoot,
216926
+ modulesRootRelativePath,
216927
+ parsedArchitectureContract
216928
+ );
216321
216929
  const explicitStandaloneModuleByPath = await buildExplicitStandaloneModulePathMap(
216322
216930
  workspaceRoot,
216323
216931
  parsedArchitectureContract
216324
216932
  );
216933
+ const workspacePackageAliases = await buildWorkspacePackageAliasMap(
216934
+ workspaceRoot,
216935
+ moduleRoots
216936
+ );
216325
216937
  const imports = await collectCrossModuleImports(
216326
216938
  workspaceRoot,
216327
216939
  modulesRootRelativePath,
216328
216940
  moduleRoots,
216329
216941
  explicitStandaloneModuleByPath,
216942
+ workspacePackageAliases,
216330
216943
  adapter
216331
216944
  );
216945
+ const peerImports = filterPeerModuleImports(imports);
216332
216946
  const findings = [];
216333
216947
  let dependencyRules;
216334
216948
  let dependencyRulesSkipReason;
@@ -216349,7 +216963,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216349
216963
  }
216350
216964
  }
216351
216965
  if (options.checkCircularDependencies) {
216352
- const cycles = detectCircularDependencies([...moduleRoots.keys()], imports);
216966
+ const cycles = detectCircularDependencies([...moduleRoots.keys()], peerImports);
216353
216967
  if (cycles.length === 0) {
216354
216968
  findings.push({
216355
216969
  result: makeValidationResult(
@@ -216381,7 +216995,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216381
216995
  })
216382
216996
  );
216383
216997
  } else {
216384
- const violations = imports.filter((dependencyImport) => {
216998
+ const violations = peerImports.filter((dependencyImport) => {
216385
216999
  const moduleRules = dependencyRules.modules[dependencyImport.sourceModule];
216386
217000
  if (!moduleRules) {
216387
217001
  return false;
@@ -216416,6 +217030,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216416
217030
  ),
216417
217031
  filePath: violation.sourceFileRelativePath,
216418
217032
  line: violation.line,
217033
+ importSpecifier: violation.importSpecifier,
216419
217034
  findingType: "module-dependency",
216420
217035
  module: violation.sourceModule,
216421
217036
  target: violation.targetModule
@@ -216433,7 +217048,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216433
217048
  })
216434
217049
  );
216435
217050
  } else {
216436
- const violations = imports.filter((dependencyImport) => {
217051
+ const violations = peerImports.filter((dependencyImport) => {
216437
217052
  const moduleRules = dependencyRules.modules[dependencyImport.sourceModule];
216438
217053
  return moduleRules?.publicEntrypointsOnly === true && !dependencyImport.isPublicImport;
216439
217054
  });
@@ -216448,12 +217063,17 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216448
217063
  });
216449
217064
  } else {
216450
217065
  for (const violation of violations) {
217066
+ const publicImportGuidance = await describeModulePublicImportGuidance(
217067
+ workspaceRoot,
217068
+ violation.targetModule,
217069
+ moduleRoots
217070
+ );
216451
217071
  findings.push({
216452
217072
  result: makeValidationResult(
216453
217073
  "AP-DEP-003",
216454
217074
  "error",
216455
217075
  false,
216456
- `${violation.sourceFileRelativePath}:${violation.line} imports non-public path "${violation.importSpecifier}" from module "${violation.targetModule}". Use the public entrypoint at ${modulesRootRelativePath}/${violation.targetModule} or ${modulesRootRelativePath}/${violation.targetModule}/public instead.`,
217076
+ `${violation.sourceFileRelativePath}:${violation.line} imports non-public path "${violation.importSpecifier}" from module "${violation.targetModule}". ${publicImportGuidance}`,
216457
217077
  {
216458
217078
  findingType: "module-dependency",
216459
217079
  module: violation.sourceModule,
@@ -216462,6 +217082,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
216462
217082
  ),
216463
217083
  filePath: violation.sourceFileRelativePath,
216464
217084
  line: violation.line,
217085
+ importSpecifier: violation.importSpecifier,
216465
217086
  findingType: "module-dependency",
216466
217087
  module: violation.sourceModule,
216467
217088
  target: violation.targetModule
@@ -216476,11 +217097,19 @@ async function buildModuleDependencyGraphSnapshot(workspaceRoot, options) {
216476
217097
  const parsedArchitectureContract = await loadArchitectureContract(workspaceRoot);
216477
217098
  const adapter = resolvePrimaryAdapterFromContract(parsedArchitectureContract);
216478
217099
  const modulesRootRelativePath = options?.modulesRootRelativePath ?? resolveModulesRootFromContract(parsedArchitectureContract);
216479
- const moduleRoots = await findModuleRoots(workspaceRoot, modulesRootRelativePath);
217100
+ const moduleRoots = await findModuleRoots(
217101
+ workspaceRoot,
217102
+ modulesRootRelativePath,
217103
+ parsedArchitectureContract
217104
+ );
216480
217105
  const explicitStandaloneModuleByPath = await buildExplicitStandaloneModulePathMap(
216481
217106
  workspaceRoot,
216482
217107
  parsedArchitectureContract
216483
217108
  );
217109
+ const workspacePackageAliases = await buildWorkspacePackageAliasMap(
217110
+ workspaceRoot,
217111
+ moduleRoots
217112
+ );
216484
217113
  const discoveredModules = sortUnique2([
216485
217114
  ...moduleRoots.keys(),
216486
217115
  ...explicitStandaloneModuleByPath.values()
@@ -216493,9 +217122,10 @@ async function buildModuleDependencyGraphSnapshot(workspaceRoot, options) {
216493
217122
  modulesRootRelativePath,
216494
217123
  moduleRoots,
216495
217124
  explicitStandaloneModuleByPath,
217125
+ workspacePackageAliases,
216496
217126
  adapter
216497
217127
  );
216498
- const imports = allImports.filter(
217128
+ const imports = filterPeerModuleImports(allImports).filter(
216499
217129
  (dependencyImport) => moduleSet.has(dependencyImport.sourceModule) && moduleSet.has(dependencyImport.targetModule)
216500
217130
  ).sort((left, right) => {
216501
217131
  const sourceCompare = left.sourceModule.localeCompare(right.sourceModule);
@@ -216548,6 +217178,7 @@ async function buildModuleDependencyGraphSnapshot(workspaceRoot, options) {
216548
217178
  return {
216549
217179
  modules,
216550
217180
  edges,
217181
+ hierarchyEdges: buildModuleHierarchyEdges(modules),
216551
217182
  cycles,
216552
217183
  imports
216553
217184
  };
@@ -216572,7 +217203,7 @@ function normalizeStringArray(value) {
216572
217203
  }
216573
217204
  return normalizedValues;
216574
217205
  }
216575
- async function pathExists4(targetPath) {
217206
+ async function pathExists5(targetPath) {
216576
217207
  try {
216577
217208
  await import_node_fs9.promises.access(targetPath);
216578
217209
  return true;
@@ -216708,7 +217339,7 @@ async function validateModuleContractIntegrity(workspaceRoot, contract, options)
216708
217339
  if (!moduleReference.contract) {
216709
217340
  continue;
216710
217341
  }
216711
- const exists = await pathExists4(
217342
+ const exists = await pathExists5(
216712
217343
  toWorkspaceAbsolutePath(workspaceRoot, moduleReference.contract)
216713
217344
  );
216714
217345
  if (!exists) {
@@ -216744,7 +217375,7 @@ async function validateModuleContractIntegrity(workspaceRoot, contract, options)
216744
217375
  }
216745
217376
  const contractPath = moduleReference.contract;
216746
217377
  const fullContractPath = toWorkspaceAbsolutePath(workspaceRoot, contractPath);
216747
- if (!await pathExists4(fullContractPath)) {
217378
+ if (!await pathExists5(fullContractPath)) {
216748
217379
  continue;
216749
217380
  }
216750
217381
  const parsedContract = await readModuleContract(workspaceRoot, contractPath);
@@ -216803,7 +217434,7 @@ async function validateModuleContractIntegrity(workspaceRoot, contract, options)
216803
217434
  }
216804
217435
  const contractPath = moduleReference.contract;
216805
217436
  const fullContractPath = toWorkspaceAbsolutePath(workspaceRoot, contractPath);
216806
- if (!await pathExists4(fullContractPath)) {
217437
+ if (!await pathExists5(fullContractPath)) {
216807
217438
  continue;
216808
217439
  }
216809
217440
  const parsedContract = await readModuleContract(workspaceRoot, contractPath);
@@ -217161,7 +217792,7 @@ function hasKeywordMatch(record, keywords) {
217161
217792
  const haystack = `${record.fileName} ${record.title}`.toLowerCase();
217162
217793
  return keywords.some((keyword) => haystack.includes(keyword));
217163
217794
  }
217164
- function pathExists5(targetPath) {
217795
+ function pathExists6(targetPath) {
217165
217796
  return (0, import_promises.access)(targetPath).then(() => true).catch(() => false);
217166
217797
  }
217167
217798
  function buildSummary(records) {
@@ -217186,7 +217817,7 @@ function createAdrValidationResult(input2) {
217186
217817
  async function validateAdrEnforcement(workspaceRoot, config) {
217187
217818
  const adrDirectoryRelativePath = await resolveAdrDirectoryRelativePath(workspaceRoot);
217188
217819
  const adrDirectory = getAdrDirectoryPath(workspaceRoot, adrDirectoryRelativePath);
217189
- const adrDirectoryExists = await pathExists5(adrDirectory);
217820
+ const adrDirectoryExists = await pathExists6(adrDirectory);
217190
217821
  if (!adrDirectoryExists) {
217191
217822
  return {
217192
217823
  results: [
@@ -217239,7 +217870,7 @@ async function validateAdrEnforcement(workspaceRoot, config) {
217239
217870
  }
217240
217871
  ]
217241
217872
  ];
217242
- const dependencyContextExists = await pathExists5(path11.join(workspaceRoot, ".archpilot", "dependency-rules.json")) || await pathExists5(path11.join(workspaceRoot, ".archpilot", "contracts"));
217873
+ const dependencyContextExists = await pathExists6(path11.join(workspaceRoot, ".archpilot", "dependency-rules.json")) || await pathExists6(path11.join(workspaceRoot, ".archpilot", "contracts"));
217243
217874
  const checksToRun = [
217244
217875
  requiredCoverageChecks[0],
217245
217876
  ...dependencyContextExists ? [requiredCoverageChecks[1]] : [],
@@ -217750,13 +218381,6 @@ var overallSetupGapWarningPenalty = 1;
217750
218381
  var overallSetupGapInfoPenalty = 1;
217751
218382
  var minScore = 0;
217752
218383
  var maxScore = 100;
217753
- var crossModuleDependencyRuleIds = /* @__PURE__ */ new Set([
217754
- "AP-DEP-002",
217755
- "AP-DEP-003",
217756
- "AP-DEP-004",
217757
- "AP-DEP-005",
217758
- "AP-DEP-010"
217759
- ]);
217760
218384
  var crossModuleContractRuleIds = /* @__PURE__ */ new Set(["AP-DEP-004", "AP-DEP-005"]);
217761
218385
  var cycleRuleIds = /* @__PURE__ */ new Set(["AP-DEP-001", "AP-DEP-008"]);
217762
218386
  function clampScore(value) {
@@ -217810,6 +218434,22 @@ function getQualityPenaltyForResult(result) {
217810
218434
  }
217811
218435
  return base + getAdditionalQualityPenalty(result);
217812
218436
  }
218437
+ function dedupeResultsByDeductionKey(results) {
218438
+ const seen = /* @__PURE__ */ new Set();
218439
+ const deduped = [];
218440
+ for (const result of results) {
218441
+ if (!result.deductionKey) {
218442
+ deduped.push(result);
218443
+ continue;
218444
+ }
218445
+ if (seen.has(result.deductionKey)) {
218446
+ continue;
218447
+ }
218448
+ seen.add(result.deductionKey);
218449
+ deduped.push(result);
218450
+ }
218451
+ return deduped;
218452
+ }
217813
218453
  function getReadinessPenaltyForSetupGapSeverity(severity) {
217814
218454
  if (severity === "error") {
217815
218455
  return readinessSetupGapErrorPenalty;
@@ -217820,7 +218460,7 @@ function getReadinessPenaltyForSetupGapSeverity(severity) {
217820
218460
  return readinessSetupGapInfoPenalty;
217821
218461
  }
217822
218462
  function deriveGovernanceRiskLevelFromResults(results) {
217823
- const qualityFailures = results.filter(
218463
+ const qualityFailures = dedupeResultsByDeductionKey(results).filter(
217824
218464
  (result) => !result.passed && (result.impact ?? "quality") === "quality"
217825
218465
  );
217826
218466
  const hasCriticalRule = qualityFailures.some(
@@ -217839,21 +218479,14 @@ function deriveGovernanceRiskLevelFromResults(results) {
217839
218479
  }
217840
218480
  function applyFinalScoreAdjustments(input2) {
217841
218481
  let adjusted = input2.baseScore;
217842
- const crossModuleDependencyViolations = input2.results.filter(
217843
- (result) => !result.passed && (result.impact ?? "quality") === "quality" && typeof result.ruleId === "string" && crossModuleDependencyRuleIds.has(result.ruleId)
217844
- ).length;
217845
- const crossModuleContractViolations = input2.results.filter(
218482
+ const uniqueResults = dedupeResultsByDeductionKey(input2.results);
218483
+ const crossModuleContractViolations = uniqueResults.filter(
217846
218484
  (result) => !result.passed && (result.impact ?? "quality") === "quality" && typeof result.ruleId === "string" && crossModuleContractRuleIds.has(result.ruleId)
217847
218485
  ).length;
217848
- const cycleViolations = input2.results.filter(
218486
+ const cycleViolations = uniqueResults.filter(
217849
218487
  (result) => !result.passed && (result.impact ?? "quality") === "quality" && typeof result.ruleId === "string" && cycleRuleIds.has(result.ruleId)
217850
218488
  ).length;
217851
- const blockingViolations = input2.results.filter(
217852
- (result) => !result.passed && result.severity === "error" && (result.impact ?? "quality") === "quality"
217853
- ).length;
217854
- adjusted -= Math.min(8, blockingViolations * 2);
217855
218489
  adjusted -= Math.min(6, crossModuleContractViolations * 2);
217856
- adjusted -= Math.min(4, crossModuleDependencyViolations);
217857
218490
  adjusted -= Math.min(4, cycleViolations * 2);
217858
218491
  const governanceRiskLevel = input2.context?.governanceRiskLevel ?? deriveGovernanceRiskLevelFromResults(input2.results);
217859
218492
  if (governanceRiskLevel === "critical") {
@@ -219337,7 +219970,7 @@ function normalizeSqlTableName(raw) {
219337
219970
  }
219338
219971
  return trimmed;
219339
219972
  }
219340
- async function pathExists6(targetPath) {
219973
+ async function pathExists7(targetPath) {
219341
219974
  try {
219342
219975
  await import_node_fs19.promises.access(targetPath);
219343
219976
  return true;
@@ -219358,7 +219991,7 @@ async function readTextFileIfReasonable(filePath) {
219358
219991
  }
219359
219992
  async function collectFilesRecursive(rootPath, includeFile, ignoredDirectoryNames8) {
219360
219993
  const collected = [];
219361
- if (!await pathExists6(rootPath)) {
219994
+ if (!await pathExists7(rootPath)) {
219362
219995
  return collected;
219363
219996
  }
219364
219997
  const stack = [rootPath];
@@ -219949,11 +220582,11 @@ src/
219949
220582
  },
219950
220583
  DEP_ISOLATED_MODULE_DETECTED: {
219951
220584
  id: ValidationRuleIds.DEP_ISOLATED_MODULE_DETECTED,
219952
- title: "Isolated module detected",
220585
+ title: "Potential orphan module detected",
219953
220586
  category: "dependency",
219954
220587
  defaultSeverity: "warning",
219955
- description: "Checks whether a registered module has no declared or actual inbound/outbound module dependencies.",
219956
- recommendedFix: "Connect the module to the architecture through legitimate dependencies, or remove it if it is unused."
220588
+ description: "Checks whether a registered module has no peer-module dependencies and little evidence of intentional architectural use.",
220589
+ recommendedFix: "Add intentional-use evidence such as a contract, README, public entrypoint, package entrypoint, or remove the module if it is stale."
219957
220590
  },
219958
220591
  DEP_TRANSITIVE_CIRCULAR_MODULE_DEPENDENCY: {
219959
220592
  id: ValidationRuleIds.DEP_TRANSITIVE_CIRCULAR_MODULE_DEPENDENCY,
@@ -220242,6 +220875,23 @@ function buildSetupGapDeductionKeyFromMessage(message) {
220242
220875
  }
220243
220876
  return void 0;
220244
220877
  }
220878
+ function buildDependencyDeductionKey(result) {
220879
+ if (result.findingType !== "module-dependency") {
220880
+ return void 0;
220881
+ }
220882
+ if (typeof result.sourceFile !== "string" || typeof result.sourceLine !== "number" || typeof result.module !== "string" || typeof result.target !== "string") {
220883
+ return void 0;
220884
+ }
220885
+ return [
220886
+ "quality",
220887
+ result.id,
220888
+ result.module,
220889
+ result.target,
220890
+ result.sourceFile.replace(/\\/g, "/"),
220891
+ String(result.sourceLine),
220892
+ result.importSpecifier ?? ""
220893
+ ].join("|");
220894
+ }
220245
220895
  function classifyValidationResult(result) {
220246
220896
  if (result.passed) {
220247
220897
  return {
@@ -220269,7 +220919,7 @@ function classifyValidationResult(result) {
220269
220919
  return {
220270
220920
  kind: "violation",
220271
220921
  scoreImpact: "quality",
220272
- deductionKey: `quality:${result.id}:${normalizedMessage}`
220922
+ deductionKey: buildDependencyDeductionKey(result) ?? `quality:${result.id}:${normalizedMessage}`
220273
220923
  };
220274
220924
  }
220275
220925
  function getRuleIdSet() {
@@ -220859,7 +221509,7 @@ function applyValidationExceptions(results, exceptions) {
220859
221509
  approvedExceptions: dedupedApprovedExceptions
220860
221510
  };
220861
221511
  }
220862
- async function pathExists7(targetPath) {
221512
+ async function pathExists8(targetPath) {
220863
221513
  try {
220864
221514
  await import_node_fs20.promises.access(targetPath);
220865
221515
  return true;
@@ -220898,11 +221548,11 @@ async function readDependencyRulesRawConfig(workspaceRoot) {
220898
221548
  async function resolveValidationConfigPath(workspaceRoot) {
220899
221549
  const configDir = path21.join(workspaceRoot, ".archpilot");
220900
221550
  const jsoncPath = path21.join(configDir, "validation-config.jsonc");
220901
- if (await pathExists7(jsoncPath)) {
221551
+ if (await pathExists8(jsoncPath)) {
220902
221552
  return jsoncPath;
220903
221553
  }
220904
221554
  const jsonPath = path21.join(configDir, "validation-config.json");
220905
- if (await pathExists7(jsonPath)) {
221555
+ if (await pathExists8(jsonPath)) {
220906
221556
  return jsonPath;
220907
221557
  }
220908
221558
  return void 0;
@@ -221028,7 +221678,7 @@ async function validateApiStyle(workspaceRoot, contract, config, validationConfi
221028
221678
  }
221029
221679
  const openApiRelativePath = resolveOpenApiRelativePath(contract);
221030
221680
  const openApiPath = path21.join(workspaceRoot, ...openApiRelativePath.split("/"));
221031
- const exists = await pathExists7(openApiPath);
221681
+ const exists = await pathExists8(openApiPath);
221032
221682
  if (openApiRequired) {
221033
221683
  results.push({
221034
221684
  id: ValidationRuleIds.API_OPENAPI_EXISTS,
@@ -221081,7 +221731,7 @@ async function validateTenantModel(workspaceRoot, contract, config, validationCo
221081
221731
  if (config.tenantModel === "same_db_different_schema" && !isRuleDisabled(ValidationRuleIds.DOC_TENANT_DIFFERENT_SCHEMA_ADR, validationConfig)) {
221082
221732
  const tenantModelAdrRelativePath = resolveTenantModelAdrRelativePath(contract);
221083
221733
  const adrPath = path21.join(workspaceRoot, ...tenantModelAdrRelativePath.split("/"));
221084
- const exists = await pathExists7(adrPath);
221734
+ const exists = await pathExists8(adrPath);
221085
221735
  results.push({
221086
221736
  id: ValidationRuleIds.DOC_TENANT_DIFFERENT_SCHEMA_ADR,
221087
221737
  severity: "warning",
@@ -221134,7 +221784,7 @@ async function validateArchitectureStyle(workspaceRoot, contract, validationConf
221134
221784
  if (implementationProfile === "typescript-backend" && !isRuleDisabled(ValidationRuleIds.ARCH_OVERVIEW_ARTIFACT_EXISTS, validationConfig)) {
221135
221785
  const overviewRelativePath = resolveOverviewRelativePath(contract);
221136
221786
  const overviewPath = path21.join(workspaceRoot, ...overviewRelativePath.split("/"));
221137
- const overviewExists = await pathExists7(overviewPath);
221787
+ const overviewExists = await pathExists8(overviewPath);
221138
221788
  results.push({
221139
221789
  id: ValidationRuleIds.ARCH_OVERVIEW_ARTIFACT_EXISTS,
221140
221790
  severity: "error",
@@ -221146,7 +221796,7 @@ async function validateArchitectureStyle(workspaceRoot, contract, validationConf
221146
221796
  const normalizeEntrypoint = (entry) => normalizeRelativePath2(entry.trim());
221147
221797
  const hasDeclaredPublicEntrypoint = async (moduleName, entry) => {
221148
221798
  const entryPath = path21.join(workspaceRoot, ...normalizeEntrypoint(entry).split("/"));
221149
- return pathExists7(entryPath);
221799
+ return pathExists8(entryPath);
221150
221800
  };
221151
221801
  const readModuleContractPublicEntrypoints = async (moduleName) => {
221152
221802
  const registryEntry = contract.modules[moduleName];
@@ -221171,9 +221821,18 @@ async function validateArchitectureStyle(workspaceRoot, contract, validationConf
221171
221821
  };
221172
221822
  const modulesRootRelativePath2 = await getConfiguredModulesRoot(workspaceRoot, contract);
221173
221823
  const modulesRoot = path21.join(workspaceRoot, ...modulesRootRelativePath2.split("/"));
221174
- if (await pathExists7(modulesRoot)) {
221175
- const moduleEntries = await import_node_fs20.promises.readdir(modulesRoot, { withFileTypes: true });
221176
- const moduleDirectories2 = moduleEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
221824
+ if (await pathExists8(modulesRoot)) {
221825
+ const hierarchicalModules = await discoverHierarchicalModuleRoots(
221826
+ workspaceRoot,
221827
+ modulesRootRelativePath2
221828
+ );
221829
+ const moduleDirectories2 = hierarchicalModules.length > 0 ? hierarchicalModules.map((entry) => ({
221830
+ moduleName: entry.moduleId,
221831
+ sourcePath: entry.sourcePath
221832
+ })) : (await import_node_fs20.promises.readdir(modulesRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => ({
221833
+ moduleName: entry.name,
221834
+ sourcePath: `${modulesRootRelativePath2}/${entry.name}`
221835
+ })).sort((left, right) => left.moduleName.localeCompare(right.moduleName));
221177
221836
  const dependencySummary = await buildDependencyGraphSummary(workspaceRoot, contract, {
221178
221837
  modulesRootRelativePath: modulesRootRelativePath2
221179
221838
  });
@@ -221181,10 +221840,11 @@ async function validateArchitectureStyle(workspaceRoot, contract, validationConf
221181
221840
  dependencySummary.modules.filter((moduleSummary) => moduleSummary.inboundDeclaredFrom.length > 0).map((moduleSummary) => moduleSummary.module)
221182
221841
  );
221183
221842
  const incompleteModules = [];
221184
- for (const moduleName of moduleDirectories2) {
221185
- const moduleDir = path21.join(modulesRoot, moduleName);
221186
- const readmeExists = await pathExists7(path21.join(moduleDir, "README.md"));
221187
- const indexExists = await pathExists7(path21.join(moduleDir, "index.ts"));
221843
+ for (const moduleDirectory of moduleDirectories2) {
221844
+ const moduleName = moduleDirectory.moduleName;
221845
+ const moduleDir = path21.join(workspaceRoot, ...moduleDirectory.sourcePath.split("/"));
221846
+ const readmeExists = await pathExists8(path21.join(moduleDir, "README.md"));
221847
+ const indexExists = await pathExists8(path21.join(moduleDir, "index.ts"));
221188
221848
  const registryPublicEntrypoints = (contract.modules[moduleName]?.publicEntrypoints ?? []).filter((entry) => typeof entry === "string").map((entry) => normalizeEntrypoint(entry));
221189
221849
  const contractPublicEntrypoints = await readModuleContractPublicEntrypoints(moduleName);
221190
221850
  const declaredPublicEntrypoints = [
@@ -221231,7 +221891,7 @@ async function validateDatabaseArtifacts(workspaceRoot, contract, config, valida
221231
221891
  }
221232
221892
  const sqlBaselineRelativePath = resolveSqlBaselineRelativePath(contract);
221233
221893
  const sqlBaselinePath = path21.join(workspaceRoot, ...sqlBaselineRelativePath.split("/"));
221234
- const exists = await pathExists7(sqlBaselinePath);
221894
+ const exists = await pathExists8(sqlBaselinePath);
221235
221895
  results.push({
221236
221896
  id: ValidationRuleIds.DB_SQL_BASELINE_EXISTS,
221237
221897
  severity: "error",
@@ -221240,6 +221900,34 @@ async function validateDatabaseArtifacts(workspaceRoot, contract, config, valida
221240
221900
  });
221241
221901
  return results;
221242
221902
  }
221903
+ function buildDependencyFindingStableKey(finding) {
221904
+ if (!finding.result.id || !finding.module || !finding.target || !finding.filePath || finding.line === void 0 || !finding.importSpecifier) {
221905
+ return void 0;
221906
+ }
221907
+ return [
221908
+ finding.result.id,
221909
+ finding.module,
221910
+ finding.target,
221911
+ finding.filePath.replace(/\\/g, "/"),
221912
+ String(finding.line),
221913
+ finding.importSpecifier
221914
+ ].join("|");
221915
+ }
221916
+ function dedupeDependencyFindings(findings) {
221917
+ const seen = /* @__PURE__ */ new Set();
221918
+ const deduped = [];
221919
+ for (const finding of findings) {
221920
+ const stableKey = buildDependencyFindingStableKey(finding);
221921
+ if (stableKey && seen.has(stableKey)) {
221922
+ continue;
221923
+ }
221924
+ if (stableKey) {
221925
+ seen.add(stableKey);
221926
+ }
221927
+ deduped.push(finding);
221928
+ }
221929
+ return deduped;
221930
+ }
221243
221931
  async function validateModuleDependencies(workspaceRoot, contract, validationConfig, onDependencyConfigError, dependencyRulesConfigOverride) {
221244
221932
  const modulesRootRelativePath = await getConfiguredModulesRoot(workspaceRoot, contract);
221245
221933
  const dependencyRuleChecks = {
@@ -221279,10 +221967,11 @@ async function validateModuleDependencies(workspaceRoot, contract, validationCon
221279
221967
  }),
221280
221968
  validateDependencyContractBoundaries(workspaceRoot, contract, {
221281
221969
  ...contractDependencyRuleChecks,
221282
- modulesRootRelativePath
221970
+ modulesRootRelativePath,
221971
+ suppressPublicSurfaceFailuresForNonPublicImports: dependencyRuleChecks.checkNonPublicImports
221283
221972
  })
221284
221973
  ]);
221285
- const findings = [...legacyFindings, ...contractFindings];
221974
+ const findings = dedupeDependencyFindings([...legacyFindings, ...contractFindings]);
221286
221975
  const results = [];
221287
221976
  for (const finding of findings) {
221288
221977
  const resultWithSource = {
@@ -221293,7 +221982,8 @@ async function validateModuleDependencies(workspaceRoot, contract, validationCon
221293
221982
  ...finding.api ? { api: finding.api } : {},
221294
221983
  ...finding.findingType ? { findingType: finding.findingType } : {},
221295
221984
  ...finding.filePath ? { sourceFile: finding.filePath, filePath: finding.filePath } : {},
221296
- ...finding.line !== void 0 ? { sourceLine: finding.line } : {}
221985
+ ...finding.line !== void 0 ? { sourceLine: finding.line } : {},
221986
+ ...finding.importSpecifier ? { importSpecifier: finding.importSpecifier } : {}
221297
221987
  };
221298
221988
  results.push(resultWithSource);
221299
221989
  }
@@ -221311,24 +222001,117 @@ async function validateDependencyGraphIsolation(workspaceRoot, contract, depende
221311
222001
  return [];
221312
222002
  }
221313
222003
  const isolatedResults = [];
221314
- const isolationExemptInfrastructureModules = /* @__PURE__ */ new Set(["ci", "policy", "validation"]);
222004
+ const sourceFileExtensions4 = /* @__PURE__ */ new Set([
222005
+ ".ts",
222006
+ ".tsx",
222007
+ ".js",
222008
+ ".jsx",
222009
+ ".mjs",
222010
+ ".cjs",
222011
+ ".mts",
222012
+ ".cts",
222013
+ ".py",
222014
+ ".go",
222015
+ ".java",
222016
+ ".kt",
222017
+ ".kts",
222018
+ ".cs",
222019
+ ".php",
222020
+ ".rb",
222021
+ ".vue",
222022
+ ".svelte"
222023
+ ]);
221315
222024
  const isTestLikeModule = (moduleName) => {
221316
222025
  const normalized = moduleName.toLowerCase();
221317
222026
  return normalized === "test" || normalized === "tests" || normalized.endsWith("-test") || normalized.endsWith("-tests");
221318
222027
  };
222028
+ const collectModuleSourceEvidence2 = async (directoryPath) => {
222029
+ let entries;
222030
+ try {
222031
+ entries = await import_node_fs20.promises.readdir(directoryPath, { withFileTypes: true });
222032
+ } catch {
222033
+ return { sourceFileCount: 0, hasReadme: false, hasEntrypointFile: false };
222034
+ }
222035
+ let sourceFileCount = 0;
222036
+ let hasReadme = false;
222037
+ let hasEntrypointFile = false;
222038
+ for (const entry of entries) {
222039
+ const fullPath = path21.join(directoryPath, entry.name);
222040
+ if (entry.isDirectory()) {
222041
+ if (["node_modules", "dist", "out", "build", "coverage", ".git"].includes(entry.name)) {
222042
+ continue;
222043
+ }
222044
+ const nested = await collectModuleSourceEvidence2(fullPath);
222045
+ sourceFileCount += nested.sourceFileCount;
222046
+ hasReadme ||= nested.hasReadme;
222047
+ hasEntrypointFile ||= nested.hasEntrypointFile;
222048
+ continue;
222049
+ }
222050
+ if (!entry.isFile()) {
222051
+ continue;
222052
+ }
222053
+ const lowerName = entry.name.toLowerCase();
222054
+ if (lowerName === "readme.md" || lowerName === "readme.mdx") {
222055
+ hasReadme = true;
222056
+ }
222057
+ if (/^(index|main|app|extension|cli)\.[cm]?[jt]sx?$/iu.test(entry.name)) {
222058
+ hasEntrypointFile = true;
222059
+ }
222060
+ if (sourceFileExtensions4.has(path21.extname(entry.name).toLowerCase())) {
222061
+ sourceFileCount += 1;
222062
+ }
222063
+ }
222064
+ return { sourceFileCount, hasReadme, hasEntrypointFile };
222065
+ };
222066
+ const readPackageJsonIfExists2 = async (moduleDirectoryPath) => {
222067
+ const content = await readTextFileIfExists3(path21.join(moduleDirectoryPath, "package.json"));
222068
+ if (!content) {
222069
+ return void 0;
222070
+ }
222071
+ try {
222072
+ const parsed = JSON.parse(content);
222073
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
222074
+ } catch {
222075
+ return void 0;
222076
+ }
222077
+ };
222078
+ const hasPackageEntrypoint2 = async (moduleDirectoryPath) => {
222079
+ const packageJson = await readPackageJsonIfExists2(moduleDirectoryPath);
222080
+ if (!packageJson) {
222081
+ return false;
222082
+ }
222083
+ return ["main", "module", "browser", "types", "bin", "exports"].some(
222084
+ (field) => packageJson[field] !== void 0
222085
+ );
222086
+ };
222087
+ const hasKnownBoundaryRole2 = (moduleName) => {
222088
+ const normalized = moduleName.toLowerCase();
222089
+ const compact = normalized.replace(/[-_/]/gu, "");
222090
+ const segments = normalized.split(/[/-]/u);
222091
+ return ["adr", "ci", "compliance", "config", "governance", "policy", "validation", "smartinit"].some(
222092
+ (role) => compact.includes(role) || segments.includes(role)
222093
+ );
222094
+ };
222095
+ const hasConfiguredPublicEntrypoint2 = (moduleName) => {
222096
+ const publicEntrypoints = contract.modules[moduleName]?.publicEntrypoints;
222097
+ return Array.isArray(publicEntrypoints) && publicEntrypoints.some(
222098
+ (entry) => typeof entry === "string" && entry.trim().length > 0
222099
+ );
222100
+ };
222101
+ const hasIntentionalIndependentEvidence2 = async (moduleSummary, moduleDirectoryPath) => {
222102
+ const sourceEvidence = await collectModuleSourceEvidence2(moduleDirectoryPath);
222103
+ return moduleSummary.hasContract || hasConfiguredPublicEntrypoint2(moduleSummary.module) || sourceEvidence.hasReadme || sourceEvidence.hasEntrypointFile || await hasPackageEntrypoint2(moduleDirectoryPath) || moduleSummary.childModules.length > 0 || moduleSummary.parentModule !== void 0 && moduleSummary.childModules.length === 0 && sourceEvidence.sourceFileCount > 0 || hasKnownBoundaryRole2(moduleSummary.module) || sourceEvidence.sourceFileCount >= 2;
222104
+ };
221319
222105
  for (const moduleSummary of dependencyGraphSummary.modules) {
221320
222106
  if (isTestLikeModule(moduleSummary.module)) {
221321
222107
  continue;
221322
222108
  }
221323
- if (isolationExemptInfrastructureModules.has(moduleSummary.module.toLowerCase())) {
221324
- continue;
221325
- }
221326
222109
  const registryEntry = contract.modules[moduleSummary.module];
221327
222110
  if (!registryEntry?.path) {
221328
222111
  continue;
221329
222112
  }
221330
222113
  const moduleDirectoryPath = path21.join(workspaceRoot, ...registryEntry.path.split("/"));
221331
- const moduleDirectoryExists = await pathExists7(moduleDirectoryPath);
222114
+ const moduleDirectoryExists = await pathExists8(moduleDirectoryPath);
221332
222115
  if (!moduleDirectoryExists) {
221333
222116
  continue;
221334
222117
  }
@@ -221336,11 +222119,14 @@ async function validateDependencyGraphIsolation(workspaceRoot, contract, depende
221336
222119
  if (!isIsolated) {
221337
222120
  continue;
221338
222121
  }
222122
+ if (await hasIntentionalIndependentEvidence2(moduleSummary, moduleDirectoryPath)) {
222123
+ continue;
222124
+ }
221339
222125
  isolatedResults.push({
221340
222126
  id: ValidationRuleIds.DEP_ISOLATED_MODULE_DETECTED,
221341
222127
  severity: "warning",
221342
222128
  passed: false,
221343
- message: `Module '${moduleSummary.module}' is isolated: it has no declared or actual inbound/outbound module dependencies.`,
222129
+ message: `Module '${moduleSummary.module}' is a potential orphan: it has no declared or actual inbound/outbound module dependencies and little evidence of intentional architectural use.`,
221344
222130
  findingType: "module-dependency",
221345
222131
  module: moduleSummary.module
221346
222132
  });
@@ -221353,7 +222139,7 @@ async function validateDependencyGraphIsolation(workspaceRoot, contract, depende
221353
222139
  id: ValidationRuleIds.DEP_ISOLATED_MODULE_DETECTED,
221354
222140
  severity: "warning",
221355
222141
  passed: true,
221356
- message: "No isolated modules detected among registered modules with existing registry paths."
222142
+ message: "No potential orphan modules detected among registered modules with existing registry paths."
221357
222143
  }
221358
222144
  ];
221359
222145
  }
@@ -222135,7 +222921,10 @@ function filterDependencyGraphSummaryByModuleScope(dependencyGraphSummary, modul
222135
222921
  )
222136
222922
  }));
222137
222923
  return {
222138
- modules: scopedModules
222924
+ modules: scopedModules,
222925
+ hierarchyEdges: dependencyGraphSummary.hierarchyEdges.filter(
222926
+ (edge) => scopeSet.has(edge.parentModule) && scopeSet.has(edge.childModule)
222927
+ )
222139
222928
  };
222140
222929
  }
222141
222930
  async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
@@ -223892,7 +224681,7 @@ var import_node_fs22 = require("node:fs");
223892
224681
  async function ensureDirectory(directoryPath) {
223893
224682
  await import_node_fs22.promises.mkdir(directoryPath, { recursive: true });
223894
224683
  }
223895
- async function pathExists8(targetPath) {
224684
+ async function pathExists9(targetPath) {
223896
224685
  try {
223897
224686
  await import_node_fs22.promises.access(targetPath);
223898
224687
  return true;
@@ -223901,7 +224690,7 @@ async function pathExists8(targetPath) {
223901
224690
  }
223902
224691
  }
223903
224692
  async function createFileIfMissing(filePath, content) {
223904
- if (await pathExists8(filePath)) {
224693
+ if (await pathExists9(filePath)) {
223905
224694
  return false;
223906
224695
  }
223907
224696
  await ensureDirectory(path23.dirname(filePath));
@@ -224009,7 +224798,7 @@ async function previewArch001(context) {
224009
224798
  ];
224010
224799
  const filesCreated = [];
224011
224800
  for (const targetPath of targetPaths) {
224012
- if (!await pathExists8(targetPath)) {
224801
+ if (!await pathExists9(targetPath)) {
224013
224802
  filesCreated.push(relative5(targetPath, context.workspaceRoot));
224014
224803
  }
224015
224804
  }
@@ -224030,7 +224819,7 @@ async function applyArch001(context) {
224030
224819
  ];
224031
224820
  const filesCreated = [];
224032
224821
  for (const targetPath of targetPaths) {
224033
- const existed = await pathExists8(targetPath);
224822
+ const existed = await pathExists9(targetPath);
224034
224823
  await ensureDirectory(targetPath);
224035
224824
  if (!existed) {
224036
224825
  filesCreated.push(relative5(targetPath, context.workspaceRoot));
@@ -224046,8 +224835,8 @@ async function applyArch001(context) {
224046
224835
  async function previewArch003(context) {
224047
224836
  const directoryPath = path24.join(context.workspaceRoot, "docs", "architecture");
224048
224837
  const filePath = path24.join(directoryPath, "overview.md");
224049
- const directoryExists2 = await pathExists8(directoryPath);
224050
- const fileExists4 = await pathExists8(filePath);
224838
+ const directoryExists2 = await pathExists9(directoryPath);
224839
+ const fileExists4 = await pathExists9(filePath);
224051
224840
  const filesCreated = [];
224052
224841
  if (!directoryExists2) {
224053
224842
  filesCreated.push(relative5(directoryPath, context.workspaceRoot));
@@ -224066,7 +224855,7 @@ async function previewArch003(context) {
224066
224855
  async function applyArch003(context) {
224067
224856
  const directoryPath = path24.join(context.workspaceRoot, "docs", "architecture");
224068
224857
  const filePath = path24.join(directoryPath, "overview.md");
224069
- const directoryExisted = await pathExists8(directoryPath);
224858
+ const directoryExisted = await pathExists9(directoryPath);
224070
224859
  const fileCreated = await createFileIfMissing(filePath, architectureOverviewStarterTemplate);
224071
224860
  const filesCreated = [];
224072
224861
  if (!directoryExisted) {
@@ -224084,7 +224873,7 @@ async function applyArch003(context) {
224084
224873
  }
224085
224874
  async function previewApi001(context) {
224086
224875
  const filePath = path24.join(context.workspaceRoot, "contracts", "openapi.yaml");
224087
- const exists = await pathExists8(filePath);
224876
+ const exists = await pathExists9(filePath);
224088
224877
  return {
224089
224878
  filesCreated: exists ? [] : [relative5(filePath, context.workspaceRoot)],
224090
224879
  filesModified: [],
@@ -224125,7 +224914,7 @@ async function previewArch005(context) {
224125
224914
  };
224126
224915
  }
224127
224916
  const absolutePath = path24.join(context.workspaceRoot, ...modulePath.split("/"));
224128
- const exists = await pathExists8(absolutePath);
224917
+ const exists = await pathExists9(absolutePath);
224129
224918
  return {
224130
224919
  filesCreated: exists ? [] : [modulePath],
224131
224920
  filesModified: [],
@@ -224144,7 +224933,7 @@ async function applyArch005(context) {
224144
224933
  };
224145
224934
  }
224146
224935
  const absolutePath = path24.join(context.workspaceRoot, ...modulePath.split("/"));
224147
- const existed = await pathExists8(absolutePath);
224936
+ const existed = await pathExists9(absolutePath);
224148
224937
  await ensureDirectory(absolutePath);
224149
224938
  return {
224150
224939
  applied: !existed,
@@ -224165,7 +224954,7 @@ async function previewArch006(context) {
224165
224954
  };
224166
224955
  }
224167
224956
  const absolutePath = path24.join(context.workspaceRoot, ...contractPath.split("/"));
224168
- const exists = await pathExists8(absolutePath);
224957
+ const exists = await pathExists9(absolutePath);
224169
224958
  return {
224170
224959
  filesCreated: exists ? [] : [contractPath],
224171
224960
  filesModified: [],
@@ -224207,7 +224996,7 @@ async function applyArch006(context) {
224207
224996
  }
224208
224997
  async function previewDoc001(context) {
224209
224998
  const filePath = path24.join(context.workspaceRoot, "docs", "adrs", "adr-001-tenant-model.md");
224210
- const exists = await pathExists8(filePath);
224999
+ const exists = await pathExists9(filePath);
224211
225000
  return {
224212
225001
  filesCreated: exists ? [] : [relative5(filePath, context.workspaceRoot)],
224213
225002
  filesModified: [],
@@ -224240,7 +225029,7 @@ async function applyDoc001(context) {
224240
225029
  }
224241
225030
  async function previewDoc002(context) {
224242
225031
  const filePath = path24.join(context.workspaceRoot, "docs", "adrs", "adr-rbac-model.md");
224243
- const exists = await pathExists8(filePath);
225032
+ const exists = await pathExists9(filePath);
224244
225033
  return {
224245
225034
  filesCreated: exists ? [] : [relative5(filePath, context.workspaceRoot)],
224246
225035
  filesModified: [],
@@ -224375,7 +225164,7 @@ async function applyDep006(context) {
224375
225164
  "contracts",
224376
225165
  `${parsed.sourceModule}.contract.json`
224377
225166
  );
224378
- if (!await pathExists8(contractPath)) {
225167
+ if (!await pathExists9(contractPath)) {
224379
225168
  return {
224380
225169
  applied: false,
224381
225170
  filesCreated: [],
@@ -224788,7 +225577,7 @@ var jsxExtensions = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
224788
225577
  function normalizePath4(value) {
224789
225578
  return value.replace(/\\/g, "/").replace(/^\.\/+/u, "").replace(/\/+$/u, "").toLowerCase();
224790
225579
  }
224791
- function pathExists9(targetPath) {
225580
+ function pathExists10(targetPath) {
224792
225581
  try {
224793
225582
  fs24.accessSync(targetPath);
224794
225583
  return true;
@@ -224875,7 +225664,7 @@ function hasDependencyPrefix(dependencies, prefixes) {
224875
225664
  return false;
224876
225665
  }
224877
225666
  function hasAnyFile(candidateRoot, filePaths) {
224878
- return filePaths.some((filePath) => pathExists9(path25.join(candidateRoot, ...filePath.split("/"))));
225667
+ return filePaths.some((filePath) => pathExists10(path25.join(candidateRoot, ...filePath.split("/"))));
224879
225668
  }
224880
225669
  function scanForJsxFiles(candidateRoot) {
224881
225670
  const queue = [{ absolutePath: candidateRoot, depth: 0 }];
@@ -224911,25 +225700,25 @@ function collectCandidateSignals(candidateRoot) {
224911
225700
  const dependencies = collectDependencyNames(packageJsonPath);
224912
225701
  return {
224913
225702
  dependencies,
224914
- hasNextConfig: pathExists9(path25.join(candidateRoot, "next.config.js")) || pathExists9(path25.join(candidateRoot, "next.config.mjs")) || pathExists9(path25.join(candidateRoot, "next.config.ts")),
224915
- hasViteConfig: pathExists9(path25.join(candidateRoot, "vite.config.js")) || pathExists9(path25.join(candidateRoot, "vite.config.ts")) || pathExists9(path25.join(candidateRoot, "vite.config.mjs")),
224916
- hasIndexHtml: pathExists9(path25.join(candidateRoot, "index.html")),
224917
- hasPublicDir: pathExists9(path25.join(candidateRoot, "public")),
224918
- hasSrcComponents: pathExists9(path25.join(candidateRoot, "src", "components")),
224919
- hasSrcPages: pathExists9(path25.join(candidateRoot, "src", "pages")),
224920
- hasSrcApp: pathExists9(path25.join(candidateRoot, "src", "app")),
225703
+ hasNextConfig: pathExists10(path25.join(candidateRoot, "next.config.js")) || pathExists10(path25.join(candidateRoot, "next.config.mjs")) || pathExists10(path25.join(candidateRoot, "next.config.ts")),
225704
+ hasViteConfig: pathExists10(path25.join(candidateRoot, "vite.config.js")) || pathExists10(path25.join(candidateRoot, "vite.config.ts")) || pathExists10(path25.join(candidateRoot, "vite.config.mjs")),
225705
+ hasIndexHtml: pathExists10(path25.join(candidateRoot, "index.html")),
225706
+ hasPublicDir: pathExists10(path25.join(candidateRoot, "public")),
225707
+ hasSrcComponents: pathExists10(path25.join(candidateRoot, "src", "components")),
225708
+ hasSrcPages: pathExists10(path25.join(candidateRoot, "src", "pages")),
225709
+ hasSrcApp: pathExists10(path25.join(candidateRoot, "src", "app")),
224921
225710
  hasJsxTsconfig: hasJsxEnabled(path25.join(candidateRoot, "tsconfig.json")) || hasJsxEnabled(path25.join(candidateRoot, "jsconfig.json")),
224922
225711
  hasJsxFiles: scanForJsxFiles(candidateRoot),
224923
- hasRoutesDir: pathExists9(path25.join(candidateRoot, "routes")),
224924
- hasControllersDir: pathExists9(path25.join(candidateRoot, "controllers")),
224925
- hasPrismaDir: pathExists9(path25.join(candidateRoot, "prisma")),
224926
- hasDbDir: pathExists9(path25.join(candidateRoot, "db")),
224927
- hasMigrationsDir: pathExists9(path25.join(candidateRoot, "migrations")),
224928
- hasApiDir: pathExists9(path25.join(candidateRoot, "api")),
225712
+ hasRoutesDir: pathExists10(path25.join(candidateRoot, "routes")),
225713
+ hasControllersDir: pathExists10(path25.join(candidateRoot, "controllers")),
225714
+ hasPrismaDir: pathExists10(path25.join(candidateRoot, "prisma")),
225715
+ hasDbDir: pathExists10(path25.join(candidateRoot, "db")),
225716
+ hasMigrationsDir: pathExists10(path25.join(candidateRoot, "migrations")),
225717
+ hasApiDir: pathExists10(path25.join(candidateRoot, "api")),
224929
225718
  hasServerEntrypoint: hasAnyFile(candidateRoot, backendEntrypoints),
224930
225719
  hasOpenApiArtifacts: hasAnyFile(candidateRoot, openApiArtifacts),
224931
225720
  hasWorkerEntrypoint: hasAnyFile(candidateRoot, workerEntrypoints),
224932
- hasPackageJson: pathExists9(packageJsonPath),
225721
+ hasPackageJson: pathExists10(packageJsonPath),
224933
225722
  hasExportsField: hasExportsField(packageJsonPath),
224934
225723
  hasIndexEntrypoint: hasAnyFile(candidateRoot, indexEntrypoints)
224935
225724
  };
@@ -225098,7 +225887,7 @@ function classifyModuleScope(input2) {
225098
225887
  return "backend";
225099
225888
  }
225100
225889
  const candidateRoots = getCandidateRoots(input2.workspaceRoot, input2.modulePath).filter(
225101
- (candidate) => pathExists9(candidate)
225890
+ (candidate) => pathExists10(candidate)
225102
225891
  );
225103
225892
  const scores = sumScores(
225104
225893
  candidateRoots.map(
@@ -225121,7 +225910,7 @@ function classifyModuleScope(input2) {
225121
225910
  function normalizePath5(value) {
225122
225911
  return value.replace(/\\/g, "/");
225123
225912
  }
225124
- async function pathExists10(targetPath) {
225913
+ async function pathExists11(targetPath) {
225125
225914
  try {
225126
225915
  await import_node_fs24.promises.access(targetPath);
225127
225916
  return true;
@@ -225136,9 +225925,32 @@ async function readTextFileIfExists4(filePath) {
225136
225925
  return void 0;
225137
225926
  }
225138
225927
  }
225928
+ var sourceFileExtensions2 = /* @__PURE__ */ new Set([
225929
+ ".ts",
225930
+ ".tsx",
225931
+ ".js",
225932
+ ".jsx",
225933
+ ".mjs",
225934
+ ".cjs",
225935
+ ".mts",
225936
+ ".cts",
225937
+ ".py",
225938
+ ".go",
225939
+ ".java",
225940
+ ".kt",
225941
+ ".kts",
225942
+ ".cs",
225943
+ ".php",
225944
+ ".rb",
225945
+ ".vue",
225946
+ ".svelte"
225947
+ ]);
225139
225948
  function sortUnique3(values) {
225140
225949
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
225141
225950
  }
225951
+ function toSafeContractModuleFileStem2(moduleName) {
225952
+ return normalizePath5(moduleName).split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0).join(".");
225953
+ }
225142
225954
  function getModuleRegistry(contract) {
225143
225955
  const modules = contract?.modules;
225144
225956
  if (!modules || typeof modules !== "object" || Array.isArray(modules)) {
@@ -225149,6 +225961,83 @@ function getModuleRegistry(contract) {
225149
225961
  function normalizePublicEntrypointList(entries) {
225150
225962
  return sortUnique3(entries.map((entry) => normalizePath5(entry.trim())).filter((entry) => entry.length > 0));
225151
225963
  }
225964
+ function hasConfiguredPublicEntrypoint(registryEntry) {
225965
+ return Array.isArray(registryEntry?.publicEntrypoints) && registryEntry.publicEntrypoints.some(
225966
+ (entry) => typeof entry === "string" && entry.trim().length > 0
225967
+ );
225968
+ }
225969
+ async function readPackageJsonIfExists(moduleDirectoryPath) {
225970
+ const content = await readTextFileIfExists4(path26.join(moduleDirectoryPath, "package.json"));
225971
+ if (!content) {
225972
+ return void 0;
225973
+ }
225974
+ try {
225975
+ const parsed = JSON.parse(content);
225976
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
225977
+ } catch {
225978
+ return void 0;
225979
+ }
225980
+ }
225981
+ async function collectModuleSourceEvidence(directoryPath) {
225982
+ let entries;
225983
+ try {
225984
+ entries = await import_node_fs24.promises.readdir(directoryPath, { withFileTypes: true });
225985
+ } catch {
225986
+ return { sourceFileCount: 0, hasReadme: false, hasEntrypointFile: false };
225987
+ }
225988
+ let sourceFileCount = 0;
225989
+ let hasReadme = false;
225990
+ let hasEntrypointFile = false;
225991
+ for (const entry of entries) {
225992
+ const fullPath = path26.join(directoryPath, entry.name);
225993
+ if (entry.isDirectory()) {
225994
+ if (["node_modules", "dist", "out", "build", "coverage", ".git"].includes(entry.name)) {
225995
+ continue;
225996
+ }
225997
+ const nested = await collectModuleSourceEvidence(fullPath);
225998
+ sourceFileCount += nested.sourceFileCount;
225999
+ hasReadme ||= nested.hasReadme;
226000
+ hasEntrypointFile ||= nested.hasEntrypointFile;
226001
+ continue;
226002
+ }
226003
+ if (!entry.isFile()) {
226004
+ continue;
226005
+ }
226006
+ const lowerName = entry.name.toLowerCase();
226007
+ if (lowerName === "readme.md" || lowerName === "readme.mdx") {
226008
+ hasReadme = true;
226009
+ }
226010
+ if (/^(index|main|app|extension|cli)\.[cm]?[jt]sx?$/iu.test(entry.name)) {
226011
+ hasEntrypointFile = true;
226012
+ }
226013
+ if (sourceFileExtensions2.has(path26.extname(entry.name).toLowerCase())) {
226014
+ sourceFileCount += 1;
226015
+ }
226016
+ }
226017
+ return { sourceFileCount, hasReadme, hasEntrypointFile };
226018
+ }
226019
+ function hasKnownBoundaryRole(moduleName) {
226020
+ const normalized = moduleName.toLowerCase();
226021
+ const compact = normalized.replace(/[-_/]/gu, "");
226022
+ const segments = normalized.split(/[/-]/u);
226023
+ return ["adr", "ci", "compliance", "config", "governance", "policy", "validation", "smartinit"].some(
226024
+ (role) => compact.includes(role) || segments.includes(role)
226025
+ );
226026
+ }
226027
+ async function hasPackageEntrypoint(moduleDirectoryPath) {
226028
+ const packageJson = await readPackageJsonIfExists(moduleDirectoryPath);
226029
+ if (!packageJson) {
226030
+ return false;
226031
+ }
226032
+ return ["main", "module", "browser", "types", "bin", "exports"].some(
226033
+ (field) => packageJson[field] !== void 0
226034
+ );
226035
+ }
226036
+ async function hasIntentionalIndependentEvidence(args) {
226037
+ const moduleDirectoryPath = path26.join(args.workspaceRoot, ...args.moduleEntry.sourcePath.split("/"));
226038
+ const evidence = await collectModuleSourceEvidence(moduleDirectoryPath);
226039
+ return args.moduleEntry.contractExists || args.moduleEntry.hasPublicEntrypoint === true || evidence.hasReadme || evidence.hasEntrypointFile || await hasPackageEntrypoint(moduleDirectoryPath) || args.hasChildren || args.isLeafChild && evidence.sourceFileCount > 0 || hasKnownBoundaryRole(args.moduleEntry.moduleName) || evidence.sourceFileCount >= 2;
226040
+ }
225152
226041
  async function loadModuleContractPublicEntrypoints(workspaceRoot, contractPath) {
225153
226042
  const fullContractPath = path26.join(workspaceRoot, ...contractPath.split("/"));
225154
226043
  const content = await readTextFileIfExists4(fullContractPath);
@@ -225189,7 +226078,7 @@ async function discoverModulesFromArchitectureContract(workspaceRoot, contract,
225189
226078
  const discovered = [];
225190
226079
  const discoveredNames = /* @__PURE__ */ new Set();
225191
226080
  const toContractPath = (moduleName, registryEntry) => normalizePath5(
225192
- typeof registryEntry?.contract === "string" ? registryEntry.contract : `${contractsRoot}/${moduleName}.contract.json`
226081
+ typeof registryEntry?.contract === "string" ? registryEntry.contract : `${contractsRoot}/${toSafeContractModuleFileStem2(moduleName)}.contract.json`
225193
226082
  );
225194
226083
  const resolveMissingPublicEntrypointAlignment = async (registryEntry, contractPath) => {
225195
226084
  if (!Array.isArray(registryEntry?.publicEntrypoints)) {
@@ -225233,9 +226122,9 @@ async function discoverModulesFromArchitectureContract(workspaceRoot, contract,
225233
226122
  continue;
225234
226123
  }
225235
226124
  const contractPath = normalizePath5(
225236
- typeof registryEntry.contract === "string" ? registryEntry.contract : `${contractsRoot}/${moduleName}.contract.json`
226125
+ typeof registryEntry.contract === "string" ? registryEntry.contract : `${contractsRoot}/${toSafeContractModuleFileStem2(moduleName)}.contract.json`
225237
226126
  );
225238
- const contractExists = await pathExists10(path26.join(workspaceRoot, ...contractPath.split("/")));
226127
+ const contractExists = await pathExists11(path26.join(workspaceRoot, ...contractPath.split("/")));
225239
226128
  const missingPublicEntrypointAlignment = await resolveMissingPublicEntrypointAlignment(
225240
226129
  registryEntry,
225241
226130
  contractPath
@@ -225248,6 +226137,7 @@ async function discoverModulesFromArchitectureContract(workspaceRoot, contract,
225248
226137
  ...resolveModuleScopeForPath(workspaceRoot, normalizedSourcePath, contract) ? { scope: resolveModuleScopeForPath(workspaceRoot, normalizedSourcePath, contract) } : {},
225249
226138
  contractPath,
225250
226139
  contractExists,
226140
+ hasPublicEntrypoint: hasConfiguredPublicEntrypoint(registryEntry),
225251
226141
  ...missingPublicEntrypointAlignment !== void 0 ? { missingPublicEntrypointAlignment } : {}
225252
226142
  });
225253
226143
  discoveredNames.add(moduleName);
@@ -225259,7 +226149,7 @@ async function discoverModulesFromArchitectureContract(workspaceRoot, contract,
225259
226149
  const registryEntry = moduleRegistry[moduleName];
225260
226150
  const sourcePath = normalizePath5(registryEntry?.path ?? `${normalizedModulesRoot}/${moduleName}`);
225261
226151
  const contractPath = toContractPath(moduleName, registryEntry);
225262
- const contractExists = await pathExists10(path26.join(workspaceRoot, ...contractPath.split("/")));
226152
+ const contractExists = await pathExists11(path26.join(workspaceRoot, ...contractPath.split("/")));
225263
226153
  const missingPublicEntrypointAlignment = await resolveMissingPublicEntrypointAlignment(
225264
226154
  registryEntry,
225265
226155
  contractPath
@@ -225272,18 +226162,56 @@ async function discoverModulesFromArchitectureContract(workspaceRoot, contract,
225272
226162
  ...resolveModuleScopeForPath(workspaceRoot, sourcePath, contract) ? { scope: resolveModuleScopeForPath(workspaceRoot, sourcePath, contract) } : {},
225273
226163
  contractPath,
225274
226164
  contractExists,
226165
+ hasPublicEntrypoint: hasConfiguredPublicEntrypoint(registryEntry),
225275
226166
  ...missingPublicEntrypointAlignment !== void 0 ? { missingPublicEntrypointAlignment } : {}
225276
226167
  });
225277
226168
  discoveredNames.add(moduleName);
225278
226169
  }
226170
+ for (const moduleRoot of await discoverHierarchicalModuleRoots(workspaceRoot, normalizedModulesRoot)) {
226171
+ if (discoveredNames.has(moduleRoot.moduleId)) {
226172
+ continue;
226173
+ }
226174
+ const registryEntry = moduleRegistry[moduleRoot.moduleId];
226175
+ const contractPath = toContractPath(moduleRoot.moduleId, registryEntry);
226176
+ const contractExists = await pathExists11(path26.join(workspaceRoot, ...contractPath.split("/")));
226177
+ const missingPublicEntrypointAlignment = await resolveMissingPublicEntrypointAlignment(
226178
+ registryEntry,
226179
+ contractPath
226180
+ );
226181
+ discovered.push({
226182
+ moduleName: moduleRoot.moduleId,
226183
+ sourcePath: moduleRoot.sourcePath,
226184
+ discoverySource: "architecture.json",
226185
+ sourcePathExists: true,
226186
+ ...resolveModuleScopeForPath(workspaceRoot, moduleRoot.sourcePath, contract) ? { scope: resolveModuleScopeForPath(workspaceRoot, moduleRoot.sourcePath, contract) } : {},
226187
+ contractPath,
226188
+ contractExists,
226189
+ hasPublicEntrypoint: hasConfiguredPublicEntrypoint(registryEntry),
226190
+ ...missingPublicEntrypointAlignment !== void 0 ? { missingPublicEntrypointAlignment } : {}
226191
+ });
226192
+ discoveredNames.add(moduleRoot.moduleId);
226193
+ }
225279
226194
  return discovered.sort((left, right) => left.moduleName.localeCompare(right.moduleName));
225280
226195
  }
225281
226196
  async function discoverModulesFromFallbackScan(workspaceRoot, modulesRoot = "src/modules", contractsRoot = ".archpilot/contracts") {
225282
- const moduleNames = await listModuleDirectoryNames(workspaceRoot, modulesRoot);
226197
+ const hierarchicalModules = await discoverHierarchicalModuleRoots(workspaceRoot, modulesRoot);
226198
+ const moduleNames = hierarchicalModules.length > 0 ? [] : await listModuleDirectoryNames(workspaceRoot, modulesRoot);
225283
226199
  const discovered = [];
226200
+ for (const moduleRoot of hierarchicalModules) {
226201
+ const contractPath = `${contractsRoot}/${toSafeContractModuleFileStem2(moduleRoot.moduleId)}.contract.json`;
226202
+ discovered.push({
226203
+ moduleName: moduleRoot.moduleId,
226204
+ sourcePath: moduleRoot.sourcePath,
226205
+ discoverySource: "inferred-scan",
226206
+ sourcePathExists: true,
226207
+ ...resolveModuleScopeForPath(workspaceRoot, moduleRoot.sourcePath) ? { scope: resolveModuleScopeForPath(workspaceRoot, moduleRoot.sourcePath) } : {},
226208
+ contractPath,
226209
+ contractExists: await pathExists11(path26.join(workspaceRoot, ...contractPath.split("/")))
226210
+ });
226211
+ }
225284
226212
  for (const moduleName of moduleNames) {
225285
226213
  const sourcePath = `${modulesRoot}/${moduleName}`;
225286
- const contractPath = `${contractsRoot}/${moduleName}.contract.json`;
226214
+ const contractPath = `${contractsRoot}/${toSafeContractModuleFileStem2(moduleName)}.contract.json`;
225287
226215
  discovered.push({
225288
226216
  moduleName,
225289
226217
  sourcePath,
@@ -225291,7 +226219,7 @@ async function discoverModulesFromFallbackScan(workspaceRoot, modulesRoot = "src
225291
226219
  sourcePathExists: true,
225292
226220
  ...resolveModuleScopeForPath(workspaceRoot, sourcePath) ? { scope: resolveModuleScopeForPath(workspaceRoot, sourcePath) } : {},
225293
226221
  contractPath,
225294
- contractExists: await pathExists10(path26.join(workspaceRoot, ...contractPath.split("/")))
226222
+ contractExists: await pathExists11(path26.join(workspaceRoot, ...contractPath.split("/")))
225295
226223
  });
225296
226224
  }
225297
226225
  return discovered;
@@ -225381,14 +226309,39 @@ async function generateArchitectureMap(workspaceRoot, options) {
225381
226309
  moduleFilter: moduleIds
225382
226310
  });
225383
226311
  const edges = dependencySnapshot.edges.map((edge) => toArchitectureMapEdge(edge));
226312
+ const hierarchyEdges = dependencySnapshot.hierarchyEdges;
225384
226313
  const parsedCycles = dependencySnapshot.cycles.map((cycle) => parseCycleString(cycle)).filter((cycle) => cycle.length > 1).sort((left, right) => left.join("->").localeCompare(right.join("->")));
225385
226314
  const incomingMap = buildIncomingMap(modules, edges);
225386
226315
  const outgoingMap = buildOutgoingMap(modules, edges);
225387
226316
  const cycleMembership = new Set(parsedCycles.flatMap((cycle) => cycle));
225388
- const hotspotSummary = modules.map((moduleEntry) => {
226317
+ const hierarchyParentByChild = new Map(
226318
+ hierarchyEdges.map((edge) => [edge.childModule, edge.parentModule])
226319
+ );
226320
+ const childModulesByParent = /* @__PURE__ */ new Map();
226321
+ for (const edge of hierarchyEdges) {
226322
+ const children = childModulesByParent.get(edge.parentModule) ?? [];
226323
+ children.push(edge.childModule);
226324
+ childModulesByParent.set(edge.parentModule, children);
226325
+ }
226326
+ const independentModuleNames = [];
226327
+ const potentialOrphanModuleNames = [];
226328
+ const hotspotSummary = await Promise.all(modules.map(async (moduleEntry) => {
225389
226329
  const incomingDependencyCount = incomingMap.get(moduleEntry.moduleName)?.length ?? 0;
225390
226330
  const outgoingDependencyCount = outgoingMap.get(moduleEntry.moduleName)?.length ?? 0;
225391
- const orphan = incomingDependencyCount === 0 && outgoingDependencyCount === 0;
226331
+ const hasNoPeerUsage = incomingDependencyCount === 0 && outgoingDependencyCount === 0;
226332
+ const hasIntentionalEvidence = hasNoPeerUsage ? await hasIntentionalIndependentEvidence({
226333
+ workspaceRoot,
226334
+ moduleEntry,
226335
+ hasChildren: (childModulesByParent.get(moduleEntry.moduleName)?.length ?? 0) > 0,
226336
+ isLeafChild: hierarchyParentByChild.has(moduleEntry.moduleName) && (childModulesByParent.get(moduleEntry.moduleName)?.length ?? 0) === 0
226337
+ }) : false;
226338
+ const orphan = hasNoPeerUsage && !hasIntentionalEvidence;
226339
+ if (hasNoPeerUsage && hasIntentionalEvidence) {
226340
+ independentModuleNames.push(moduleEntry.moduleName);
226341
+ }
226342
+ if (orphan) {
226343
+ potentialOrphanModuleNames.push(moduleEntry.moduleName);
226344
+ }
225392
226345
  return {
225393
226346
  moduleName: moduleEntry.moduleName,
225394
226347
  incomingDependencyCount,
@@ -225400,7 +226353,7 @@ async function generateArchitectureMap(workspaceRoot, options) {
225400
226353
  missingPublicEntrypointAlignment: moduleEntry.missingPublicEntrypointAlignment
225401
226354
  } : {}
225402
226355
  };
225403
- });
226356
+ }));
225404
226357
  const mostDependedOnModules = sortRankedCounts(
225405
226358
  hotspotSummary.filter((entry) => entry.incomingDependencyCount > 0).map((entry) => ({
225406
226359
  moduleName: entry.moduleName,
@@ -225416,9 +226369,9 @@ async function generateArchitectureMap(workspaceRoot, options) {
225416
226369
  const modulesInCircularDependencies = sortUnique3(
225417
226370
  hotspotSummary.filter((entry) => entry.participatesInCycle).map((entry) => entry.moduleName)
225418
226371
  );
225419
- const orphanModules = sortUnique3(
225420
- hotspotSummary.filter((entry) => entry.orphan).map((entry) => entry.moduleName)
225421
- );
226372
+ const independentModules = sortUnique3(independentModuleNames);
226373
+ const potentialOrphanModules = sortUnique3(potentialOrphanModuleNames);
226374
+ const orphanModules = potentialOrphanModules;
225422
226375
  const missingContracts = sortUnique3(
225423
226376
  hotspotSummary.filter((entry) => entry.missingContract).map((entry) => entry.moduleName)
225424
226377
  );
@@ -225441,11 +226394,14 @@ async function generateArchitectureMap(workspaceRoot, options) {
225441
226394
  modules,
225442
226395
  moduleHotspotSummary: hotspotSummary,
225443
226396
  edges,
226397
+ hierarchyEdges,
225444
226398
  cycles: parsedCycles,
225445
226399
  hotspots: {
225446
226400
  mostDependedOnModules,
225447
226401
  highestOutgoingFanOutModules,
225448
226402
  modulesInCircularDependencies,
226403
+ independentModules,
226404
+ potentialOrphanModules,
225449
226405
  orphanModules,
225450
226406
  missingContracts,
225451
226407
  highlyConnectedModules,
@@ -225455,6 +226411,8 @@ async function generateArchitectureMap(workspaceRoot, options) {
225455
226411
  moduleCount: modules.length,
225456
226412
  edgeCount: edges.length,
225457
226413
  cycleCount: parsedCycles.length,
226414
+ independentCount: independentModules.length,
226415
+ potentialOrphanCount: potentialOrphanModules.length,
225458
226416
  orphanCount: orphanModules.length,
225459
226417
  highlyConnectedCount: highlyConnectedModules.length,
225460
226418
  missingContractCount: missingContracts.length
@@ -225470,6 +226428,11 @@ async function generateArchitectureMap(workspaceRoot, options) {
225470
226428
  importCount: edge.importCount,
225471
226429
  publicImportCount: edge.publicImportCount,
225472
226430
  nonPublicImportCount: edge.nonPublicImportCount
226431
+ })),
226432
+ hierarchyEdges: hierarchyEdges.map((edge) => ({
226433
+ source: edge.parentModule,
226434
+ target: edge.childModule,
226435
+ relationship: "contains"
225473
226436
  }))
225474
226437
  },
225475
226438
  notes: {
@@ -227018,7 +227981,7 @@ async function runGithubPrCommentAdapter(workspaceRoot) {
227018
227981
  // ../core/src/generateOnboardingDocs.ts
227019
227982
  var path31 = __toESM(require("node:path"));
227020
227983
  var import_node_fs29 = require("node:fs");
227021
- async function pathExists11(filePath) {
227984
+ async function pathExists12(filePath) {
227022
227985
  try {
227023
227986
  await import_node_fs29.promises.access(filePath);
227024
227987
  return true;
@@ -227072,10 +228035,10 @@ async function generateOnboardingDocs(workspaceRoot) {
227072
228035
  [...map.modules].sort((left, right) => left.moduleName.localeCompare(right.moduleName)).map(async (moduleEntry) => {
227073
228036
  const readmePath = `${moduleEntry.sourcePath}/README.md`;
227074
228037
  const indexPath = `${moduleEntry.sourcePath}/index.ts`;
227075
- const readmeExists = await pathExists11(
228038
+ const readmeExists = await pathExists12(
227076
228039
  path31.join(workspaceRoot, ...readmePath.split("/"))
227077
228040
  );
227078
- const indexExists = await pathExists11(
228041
+ const indexExists = await pathExists12(
227079
228042
  path31.join(workspaceRoot, ...indexPath.split("/"))
227080
228043
  );
227081
228044
  const hotspot = hotspotByModule.get(moduleEntry.moduleName);
@@ -227857,7 +228820,7 @@ function normalizePath9(value) {
227857
228820
  function sortUnique10(values) {
227858
228821
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
227859
228822
  }
227860
- async function pathExists12(targetPath) {
228823
+ async function pathExists13(targetPath) {
227861
228824
  try {
227862
228825
  await import_node_fs30.promises.access(targetPath);
227863
228826
  return true;
@@ -228102,18 +229065,18 @@ async function generateImpactAnalysis(workspaceRoot, target, options) {
228102
229065
  const contractPath = moduleEntry.contractPath;
228103
229066
  suggestedFilesToInspect.push({
228104
229067
  path: readmePath,
228105
- exists: await pathExists12(path32.join(workspaceRoot, ...readmePath.split("/"))),
229068
+ exists: await pathExists13(path32.join(workspaceRoot, ...readmePath.split("/"))),
228106
229069
  kind: "readme"
228107
229070
  });
228108
229071
  suggestedFilesToInspect.push({
228109
229072
  path: indexPath,
228110
- exists: await pathExists12(path32.join(workspaceRoot, ...indexPath.split("/"))),
229073
+ exists: await pathExists13(path32.join(workspaceRoot, ...indexPath.split("/"))),
228111
229074
  kind: "entrypoint"
228112
229075
  });
228113
229076
  if (contractPath) {
228114
229077
  suggestedFilesToInspect.push({
228115
229078
  path: contractPath,
228116
- exists: await pathExists12(path32.join(workspaceRoot, ...contractPath.split("/"))),
229079
+ exists: await pathExists13(path32.join(workspaceRoot, ...contractPath.split("/"))),
228117
229080
  kind: "contract"
228118
229081
  });
228119
229082
  }
@@ -230436,7 +231399,7 @@ async function applyQuickFixPlan(workspaceRoot, plan) {
230436
231399
  continue;
230437
231400
  }
230438
231401
  const absoluteTargetPath = path43.resolve(workspaceRoot, action.targetPath);
230439
- if (await pathExists8(absoluteTargetPath)) {
231402
+ if (await pathExists9(absoluteTargetPath)) {
230440
231403
  skippedExists.push(action.targetPath);
230441
231404
  continue;
230442
231405
  }
@@ -230489,7 +231452,7 @@ function normalizePath11(value) {
230489
231452
  function toAbsolutePath(workspaceRoot, relativePath) {
230490
231453
  return path44.join(workspaceRoot, ...relativePath.split("/"));
230491
231454
  }
230492
- async function pathExists13(targetPath) {
231455
+ async function pathExists14(targetPath) {
230493
231456
  try {
230494
231457
  await import_node_fs39.promises.access(targetPath);
230495
231458
  return true;
@@ -230500,6 +231463,9 @@ async function pathExists13(targetPath) {
230500
231463
  function sortUnique12(values) {
230501
231464
  return [...new Set(values)].sort((left, right) => left.localeCompare(right));
230502
231465
  }
231466
+ function toSafeContractModuleFileStem3(moduleName) {
231467
+ return normalizePath11(moduleName).split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0).join(".");
231468
+ }
230503
231469
  function wildcardToRegex(pattern) {
230504
231470
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
230505
231471
  return new RegExp(`^${escaped.replace(/\*/g, ".*")}$`, "u");
@@ -230515,7 +231481,7 @@ async function inferModulePublicEntrypoints(workspaceRoot, moduleSourcePath, ada
230515
231481
  const normalizedSourcePath = normalizePath11(moduleSourcePath);
230516
231482
  if (/\.[a-z0-9]+$/iu.test(normalizedSourcePath)) {
230517
231483
  const absoluteSourcePath = toAbsolutePath(workspaceRoot, normalizedSourcePath);
230518
- if (await pathExists13(absoluteSourcePath)) {
231484
+ if (await pathExists14(absoluteSourcePath)) {
230519
231485
  return [normalizedSourcePath];
230520
231486
  }
230521
231487
  }
@@ -230542,14 +231508,14 @@ async function inferModulePublicEntrypoints(workspaceRoot, moduleSourcePath, ada
230542
231508
  const inferred = [];
230543
231509
  for (const fileName of orderedCandidates) {
230544
231510
  const candidateRelative = `${normalizedSourcePath}/${fileName}`;
230545
- if (await pathExists13(toAbsolutePath(workspaceRoot, candidateRelative))) {
231511
+ if (await pathExists14(toAbsolutePath(workspaceRoot, candidateRelative))) {
230546
231512
  inferred.push(candidateRelative);
230547
231513
  break;
230548
231514
  }
230549
231515
  }
230550
231516
  for (const publicDirName of adapter.publicEntrypointDirectoryNames) {
230551
231517
  const publicDirectoryRelative = `${normalizedSourcePath}/${publicDirName}`;
230552
- if (await pathExists13(toAbsolutePath(workspaceRoot, publicDirectoryRelative))) {
231518
+ if (await pathExists14(toAbsolutePath(workspaceRoot, publicDirectoryRelative))) {
230553
231519
  inferred.push(publicDirectoryRelative);
230554
231520
  break;
230555
231521
  }
@@ -230581,7 +231547,10 @@ function resolveContractPath(contract, moduleName) {
230581
231547
  return normalizePath11(configured);
230582
231548
  }
230583
231549
  return normalizePath11(
230584
- path44.posix.join(resolveContractsRootFromContract(contract), `${moduleName}.contract.json`)
231550
+ path44.posix.join(
231551
+ resolveContractsRootFromContract(contract),
231552
+ `${toSafeContractModuleFileStem3(moduleName)}.contract.json`
231553
+ )
230585
231554
  );
230586
231555
  }
230587
231556
  function createModuleContract(input2) {
@@ -230593,6 +231562,23 @@ function createModuleContract(input2) {
230593
231562
  exposedApis: []
230594
231563
  };
230595
231564
  }
231565
+ async function readExistingModuleContractPublicEntrypoints(workspaceRoot, contractPath) {
231566
+ try {
231567
+ const raw = await import_node_fs39.promises.readFile(toAbsolutePath(workspaceRoot, contractPath), {
231568
+ encoding: "utf8"
231569
+ });
231570
+ const parsed = JSON.parse(raw);
231571
+ if (!Array.isArray(parsed.publicEntrypoints)) {
231572
+ return void 0;
231573
+ }
231574
+ if (parsed.publicEntrypoints.some((entry) => typeof entry !== "string")) {
231575
+ return void 0;
231576
+ }
231577
+ return sortUnique12(parsed.publicEntrypoints.map((entry) => normalizePath11(entry)));
231578
+ } catch {
231579
+ return void 0;
231580
+ }
231581
+ }
230596
231582
  function isRegistryEntryDifferent(input2) {
230597
231583
  const moduleRegistry = getModuleRegistry2(input2.contract);
230598
231584
  const existing = moduleRegistry[input2.moduleName] ?? {};
@@ -230619,7 +231605,15 @@ async function generateMissingModuleContracts(workspaceRoot) {
230619
231605
  )) {
230620
231606
  const contractPath = resolveContractPath(contract, moduleEntry.moduleName);
230621
231607
  const sourcePath = normalizePath11(moduleEntry.sourcePath);
230622
- const publicEntrypoints = await inferModulePublicEntrypoints(
231608
+ const absoluteContractPath = toAbsolutePath(workspaceRoot, contractPath);
231609
+ const contractExists = await pathExists14(absoluteContractPath);
231610
+ const existingContractPublicEntrypoints = contractExists ? await readExistingModuleContractPublicEntrypoints(workspaceRoot, contractPath) : void 0;
231611
+ const configuredPublicEntrypoints = !contractExists && Array.isArray(getModuleRegistry2(contract)[moduleEntry.moduleName]?.publicEntrypoints) ? sortUnique12(
231612
+ getModuleRegistry2(contract)[moduleEntry.moduleName]?.publicEntrypoints?.map(
231613
+ (entry) => normalizePath11(entry)
231614
+ ) ?? []
231615
+ ) : void 0;
231616
+ const publicEntrypoints = existingContractPublicEntrypoints ?? configuredPublicEntrypoints ?? await inferModulePublicEntrypoints(
230623
231617
  workspaceRoot,
230624
231618
  sourcePath,
230625
231619
  adapter
@@ -230629,8 +231623,7 @@ async function generateMissingModuleContracts(workspaceRoot) {
230629
231623
  dependsOn: dependsOnBySource.get(moduleEntry.moduleName) ?? [],
230630
231624
  publicEntrypoints
230631
231625
  });
230632
- const absoluteContractPath = toAbsolutePath(workspaceRoot, contractPath);
230633
- if (await pathExists13(absoluteContractPath)) {
231626
+ if (contractExists) {
230634
231627
  skippedExistingContracts.push(contractPath);
230635
231628
  } else {
230636
231629
  await import_node_fs39.promises.mkdir(path44.dirname(absoluteContractPath), { recursive: true });
@@ -230676,7 +231669,7 @@ function normalizePath12(value) {
230676
231669
  function toAbsolutePath2(workspaceRoot, relativePath) {
230677
231670
  return path45.join(workspaceRoot, ...relativePath.split("/"));
230678
231671
  }
230679
- async function pathExists14(targetPath) {
231672
+ async function pathExists15(targetPath) {
230680
231673
  try {
230681
231674
  await import_node_fs40.promises.access(targetPath);
230682
231675
  return true;
@@ -230739,7 +231732,7 @@ async function bootstrapArchitectureConfig(workspaceRoot, options) {
230739
231732
  ensuredDirectories.push(".archpilot", ".archpilot/contracts");
230740
231733
  const dependencyRulesPath = ".archpilot/dependency-rules.json";
230741
231734
  const dependencyRulesAbsolutePath = toAbsolutePath2(workspaceRoot, dependencyRulesPath);
230742
- if (await pathExists14(dependencyRulesAbsolutePath)) {
231735
+ if (await pathExists15(dependencyRulesAbsolutePath)) {
230743
231736
  skippedExistingFiles.push(dependencyRulesPath);
230744
231737
  } else {
230745
231738
  await import_node_fs40.promises.writeFile(
@@ -230751,7 +231744,7 @@ async function bootstrapArchitectureConfig(workspaceRoot, options) {
230751
231744
  }
230752
231745
  const layerRulesPath = ".archpilot/layer-rules.json";
230753
231746
  const layerRulesAbsolutePath = toAbsolutePath2(workspaceRoot, layerRulesPath);
230754
- if (await pathExists14(layerRulesAbsolutePath)) {
231747
+ if (await pathExists15(layerRulesAbsolutePath)) {
230755
231748
  skippedExistingFiles.push(layerRulesPath);
230756
231749
  } else {
230757
231750
  await import_node_fs40.promises.writeFile(layerRulesAbsolutePath, renderLayerRulesConfig(modules), {
@@ -232776,7 +233769,7 @@ var appRouteReservedDirectoryNames = /* @__PURE__ */ new Set([
232776
233769
  "common",
232777
233770
  "shared-ui"
232778
233771
  ]);
232779
- var sourceFileExtensions = /* @__PURE__ */ new Set([
233772
+ var sourceFileExtensions3 = /* @__PURE__ */ new Set([
232780
233773
  ".ts",
232781
233774
  ".tsx",
232782
233775
  ".js",
@@ -232830,7 +233823,7 @@ function safeReadDirEntries(directoryPath) {
232830
233823
  return [];
232831
233824
  }
232832
233825
  }
232833
- function pathExists15(pathValue) {
233826
+ function pathExists16(pathValue) {
232834
233827
  try {
232835
233828
  fs48.accessSync(pathValue);
232836
233829
  return true;
@@ -232894,7 +233887,7 @@ function inspectModuleFolder(absolutePath) {
232894
233887
  continue;
232895
233888
  }
232896
233889
  const extension = path53.extname(entry.name).toLowerCase();
232897
- if (sourceFileExtensions.has(extension)) {
233890
+ if (sourceFileExtensions3.has(extension)) {
232898
233891
  hasSourceFiles = true;
232899
233892
  sourceFileCount += 1;
232900
233893
  }
@@ -232974,14 +233967,17 @@ function collectModuleCandidatesFromRoot(workspaceRoot, modulesRootRelative, def
232974
233967
  if (skipFrontendUtilities && frontendUtilityDirectoryNames.has(entry.name.toLowerCase())) {
232975
233968
  continue;
232976
233969
  }
232977
- const moduleName = entry.name;
232978
- const moduleRelativePath = normalizePath13(`${modulesRootRelative}/${moduleName}`);
233970
+ const rootSegments = modulesRootRelative.split("/").filter((segment) => segment.length > 0);
233971
+ const sourceIndex = rootSegments.lastIndexOf("src");
233972
+ const rootLeaf = rootSegments[rootSegments.length - 1]?.toLowerCase();
233973
+ const parentName = sourceIndex > 0 && (rootLeaf === "src" || ["modules", "features"].includes(rootLeaf ?? "")) ? rootSegments[sourceIndex - 1] : void 0;
233974
+ const moduleName = parentName && parentName !== "src" ? `${parentName}/${entry.name}` : ["domain", "domains", "application", "infrastructure"].includes(rootLeaf ?? "") ? `${rootSegments[rootSegments.length - 1]}/${entry.name}` : entry.name;
232979
233975
  const moduleAbsolutePath = path53.join(absoluteRoot, entry.name);
232980
233976
  const inspected = inspectModuleFolder(moduleAbsolutePath);
232981
233977
  const detected = makeDetectedModule(
232982
233978
  workspaceRoot,
232983
233979
  moduleName,
232984
- moduleRelativePath,
233980
+ normalizePath13(`${modulesRootRelative}/${entry.name}`),
232985
233981
  modulesRootRelative,
232986
233982
  inspected.hasSourceFiles,
232987
233983
  inspected.hasRoleFiles,
@@ -233042,6 +234038,10 @@ function collectExplicitRoots(workspaceRoot) {
233042
234038
  for (const root of [
233043
234039
  "src/modules",
233044
234040
  "src/features",
234041
+ "src/domain",
234042
+ "src/domains",
234043
+ "src/application",
234044
+ "src/infrastructure",
233045
234045
  "src/app",
233046
234046
  "app",
233047
234047
  "backend/src/modules",
@@ -233058,7 +234058,7 @@ function collectExplicitRoots(workspaceRoot) {
233058
234058
  "packages",
233059
234059
  "libs"
233060
234060
  ]) {
233061
- if (pathExists15(path53.join(workspaceRoot, ...root.split("/")))) {
234061
+ if (pathExists16(path53.join(workspaceRoot, ...root.split("/")))) {
233062
234062
  roots.push(root);
233063
234063
  }
233064
234064
  }
@@ -233068,23 +234068,49 @@ function collectExplicitRoots(workspaceRoot) {
233068
234068
  continue;
233069
234069
  }
233070
234070
  for (const candidate of [
234071
+ `apps/${appEntry.name}/src`,
233071
234072
  `apps/${appEntry.name}/src/modules`,
233072
234073
  `apps/${appEntry.name}/src/features`,
234074
+ `apps/${appEntry.name}/src/domain`,
234075
+ `apps/${appEntry.name}/src/domains`,
234076
+ `apps/${appEntry.name}/src/application`,
234077
+ `apps/${appEntry.name}/src/infrastructure`,
233073
234078
  `apps/${appEntry.name}/app`,
233074
234079
  `apps/${appEntry.name}/src/app`
233075
234080
  ]) {
233076
- if (pathExists15(path53.join(workspaceRoot, ...candidate.split("/")))) {
234081
+ if (pathExists16(path53.join(workspaceRoot, ...candidate.split("/")))) {
233077
234082
  roots.push(candidate);
233078
234083
  }
233079
234084
  }
233080
234085
  }
234086
+ for (const container of ["packages", "services"]) {
234087
+ const containerRoot = path53.join(workspaceRoot, container);
234088
+ for (const containerEntry of safeReadDirEntries(containerRoot)) {
234089
+ if (!containerEntry.isDirectory() || ignoredDirectoryNames4.has(containerEntry.name)) {
234090
+ continue;
234091
+ }
234092
+ for (const candidate of [
234093
+ `${container}/${containerEntry.name}/src`,
234094
+ `${container}/${containerEntry.name}/src/modules`,
234095
+ `${container}/${containerEntry.name}/src/features`,
234096
+ `${container}/${containerEntry.name}/src/domain`,
234097
+ `${container}/${containerEntry.name}/src/domains`,
234098
+ `${container}/${containerEntry.name}/src/application`,
234099
+ `${container}/${containerEntry.name}/src/infrastructure`
234100
+ ]) {
234101
+ if (pathExists16(path53.join(workspaceRoot, ...candidate.split("/")))) {
234102
+ roots.push(candidate);
234103
+ }
234104
+ }
234105
+ }
234106
+ }
233081
234107
  return uniqueSorted(roots);
233082
234108
  }
233083
234109
  function collectJavaPackageRoots(workspaceRoot) {
233084
234110
  const roots = [];
233085
234111
  for (const sourceRoot of ["src/main/java", "src/main/kotlin", "src"]) {
233086
234112
  const absoluteSourceRoot = path53.join(workspaceRoot, ...sourceRoot.split("/"));
233087
- if (!pathExists15(absoluteSourceRoot)) {
234113
+ if (!pathExists16(absoluteSourceRoot)) {
233088
234114
  continue;
233089
234115
  }
233090
234116
  const queue = [
@@ -233891,7 +234917,7 @@ function safeReadText3(filePath, maxChars = 3e4) {
233891
234917
  return void 0;
233892
234918
  }
233893
234919
  }
233894
- function pathExists16(pathValue) {
234920
+ function pathExists17(pathValue) {
233895
234921
  try {
233896
234922
  fs51.accessSync(pathValue);
233897
234923
  return true;
@@ -233997,7 +235023,7 @@ function collectCandidateRoots(workspaceRoot, topology) {
233997
235023
  }
233998
235024
  }
233999
235025
  for (const root of ["backend", "frontend"]) {
234000
- if (pathExists16(path56.join(workspaceRoot, root))) {
235026
+ if (pathExists17(path56.join(workspaceRoot, root))) {
234001
235027
  roots.add(root);
234002
235028
  }
234003
235029
  }