@archpilotlabs/archpilot 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/archpilot-cli.js +683 -226
  2. package/package.json +1 -1
@@ -214118,9 +214118,17 @@ async function pathExists3(targetPath) {
214118
214118
  async function countContractFiles(contractsDir) {
214119
214119
  try {
214120
214120
  const entries = await import_node_fs5.promises.readdir(contractsDir, { withFileTypes: true });
214121
- return entries.filter(
214122
- (entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".contract.json")
214123
- ).length;
214121
+ let count = 0;
214122
+ for (const entry of entries) {
214123
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".contract.json")) {
214124
+ count += 1;
214125
+ continue;
214126
+ }
214127
+ if (entry.isDirectory()) {
214128
+ count += await countContractFiles(path5.join(contractsDir, entry.name));
214129
+ }
214130
+ }
214131
+ return count;
214124
214132
  } catch {
214125
214133
  return 0;
214126
214134
  }
@@ -214135,7 +214143,29 @@ function countGovernedModulesFromConfig(value) {
214135
214143
  return modules.filter((entry) => typeof entry === "string" && entry.trim().length > 0).length;
214136
214144
  }
214137
214145
  if (modules && typeof modules === "object" && !Array.isArray(modules)) {
214138
- return Object.keys(modules).filter((key) => key.trim().length > 0).length;
214146
+ const moduleCount = Object.keys(modules).filter((key) => key.trim().length > 0).length;
214147
+ if (moduleCount > 0) {
214148
+ return moduleCount;
214149
+ }
214150
+ }
214151
+ const components = typed.components;
214152
+ if (Array.isArray(components)) {
214153
+ return components.filter((entry) => {
214154
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
214155
+ return false;
214156
+ }
214157
+ const component = entry;
214158
+ return typeof component.id === "string" && component.id.trim().length > 0 && typeof component.path === "string" && component.path.trim().length > 0 && component.path.trim() !== ".";
214159
+ }).length;
214160
+ }
214161
+ if (components && typeof components === "object" && !Array.isArray(components)) {
214162
+ return Object.entries(components).filter(([key, value2]) => {
214163
+ if (key.trim().length === 0 || !value2 || typeof value2 !== "object" || Array.isArray(value2)) {
214164
+ return false;
214165
+ }
214166
+ const component = value2;
214167
+ return typeof component.path === "string" && component.path.trim().length > 0 && component.path.trim() !== ".";
214168
+ }).length;
214139
214169
  }
214140
214170
  return 0;
214141
214171
  }
@@ -214628,12 +214658,12 @@ function buildModelFromSnapshot(snapshot, generatedAtUtc, renderContext) {
214628
214658
  command: "archpilot bootstrap --with-contracts",
214629
214659
  inspectorActionId: "bootstrap-config-and-contracts",
214630
214660
  description: "Create missing setup config and contracts in one deterministic pass."
214631
- } : {
214661
+ } : hasGovernedModules && hasMissingContracts ? {
214632
214662
  label: "Generate Contracts",
214633
214663
  command: "archpilot contracts generate",
214634
214664
  inspectorActionId: "generate-module-contracts",
214635
214665
  description: "Generate missing module contracts for discovered modules."
214636
- } : hasFailedFindings ? reviewFindingsAction : !snapshot.hasBaselineSnapshot ? createBaselineAction : snapshot.hasBaselineSnapshot && !snapshot.hasDriftReport ? runDriftCheckAction : missingAdrScaffolds ? generateAdrScaffoldsAction : missingGithubCiWorkflow ? {
214666
+ } : runValidationAction : hasFailedFindings ? reviewFindingsAction : !snapshot.hasBaselineSnapshot ? createBaselineAction : snapshot.hasBaselineSnapshot && !snapshot.hasDriftReport ? runDriftCheckAction : missingAdrScaffolds ? generateAdrScaffoldsAction : missingGithubCiWorkflow ? {
214637
214667
  label: "Set up GitHub CI validation + upload",
214638
214668
  command: "ArchPilot: Setup GitHub CI Validation + Upload",
214639
214669
  inspectorActionId: "setup-github-ci-governance",
@@ -215263,6 +215293,35 @@ function isPeerModuleImport(dependencyImport) {
215263
215293
  function filterPeerModuleImports(imports) {
215264
215294
  return imports.filter(isPeerModuleImport);
215265
215295
  }
215296
+ function getComponentModuleRoots(workspaceRoot, architectureContract) {
215297
+ const components = architectureContract?.components;
215298
+ if (!components) {
215299
+ return /* @__PURE__ */ new Map();
215300
+ }
215301
+ const rawEntries = Array.isArray(components) ? components.map((entry) => {
215302
+ const typed = entry;
215303
+ return [typed.id, entry];
215304
+ }) : Object.entries(components);
215305
+ const componentRoots = /* @__PURE__ */ new Map();
215306
+ for (const [key, value] of rawEntries) {
215307
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
215308
+ continue;
215309
+ }
215310
+ const typed = value;
215311
+ const componentId = typeof typed.id === "string" && typed.id.trim().length > 0 ? typed.id.trim() : typeof key === "string" && key.trim().length > 0 ? key.trim() : void 0;
215312
+ const componentPath = typeof typed.path === "string" && typed.path.trim().length > 0 ? normalizePath3(typed.path.trim()) : void 0;
215313
+ if (!componentId || !componentPath || componentPath === ".") {
215314
+ continue;
215315
+ }
215316
+ componentRoots.set(
215317
+ componentId,
215318
+ path7.join(workspaceRoot, ...componentPath.split("/"))
215319
+ );
215320
+ }
215321
+ return new Map(
215322
+ [...componentRoots.entries()].sort(([left], [right]) => left.localeCompare(right))
215323
+ );
215324
+ }
215266
215325
  function buildModuleHierarchyEdges(moduleIds) {
215267
215326
  const moduleIdSet = new Set(moduleIds);
215268
215327
  const edges = [];
@@ -215594,6 +215653,23 @@ async function findModuleRoots(workspaceRoot, modulesRootRelativePath = "src/mod
215594
215653
  [...moduleRoots.entries()].sort(([left], [right]) => left.localeCompare(right))
215595
215654
  );
215596
215655
  }
215656
+ const componentModuleRoots = getComponentModuleRoots(workspaceRoot, architectureContract);
215657
+ if (componentModuleRoots.size > 0) {
215658
+ for (const [moduleName, moduleRoot] of componentModuleRoots.entries()) {
215659
+ try {
215660
+ const stats = await import_node_fs6.promises.stat(moduleRoot);
215661
+ if (stats.isDirectory()) {
215662
+ moduleRoots.set(moduleName, moduleRoot);
215663
+ }
215664
+ } catch {
215665
+ }
215666
+ }
215667
+ if (moduleRoots.size > 0) {
215668
+ return new Map(
215669
+ [...moduleRoots.entries()].sort(([left], [right]) => left.localeCompare(right))
215670
+ );
215671
+ }
215672
+ }
215597
215673
  for (const [moduleName, moduleRoot] of discoveredModuleRoots.entries()) {
215598
215674
  if (!moduleRoots.has(moduleName)) {
215599
215675
  moduleRoots.set(moduleName, moduleRoot);
@@ -215666,20 +215742,30 @@ function isTestSourcePath(relativePath) {
215666
215742
  }
215667
215743
  return /(^|\/)[^/]+\.(test|spec)\.[cm]?[jt]sx?$/u.test(normalized);
215668
215744
  }
215669
- async function collectSourceFiles(directoryPath, adapter, scanRootPath = directoryPath) {
215745
+ async function collectSourceFiles(directoryPath, adapter, scanRootPath = directoryPath, excludedDirectoryRoots = []) {
215670
215746
  const entries = await import_node_fs6.promises.readdir(directoryPath, { withFileTypes: true });
215671
215747
  entries.sort((left, right) => left.name.localeCompare(right.name));
215672
215748
  const files = [];
215673
215749
  for (const entry of entries) {
215674
215750
  const fullPath = path7.join(directoryPath, entry.name);
215675
215751
  if (entry.isDirectory()) {
215752
+ if (excludedDirectoryRoots.some(
215753
+ (excludedRoot) => isPathInsideOrEqual2(fullPath, excludedRoot)
215754
+ )) {
215755
+ continue;
215756
+ }
215676
215757
  const normalizedRelativeDirectory = normalizePath3(
215677
215758
  path7.relative(scanRootPath, fullPath)
215678
215759
  );
215679
215760
  if (adapter.ignoreDirectoryNames.includes(entry.name) || adapter.ignoreDirectoryNames.includes(normalizedRelativeDirectory)) {
215680
215761
  continue;
215681
215762
  }
215682
- files.push(...await collectSourceFiles(fullPath, adapter, scanRootPath));
215763
+ files.push(...await collectSourceFiles(
215764
+ fullPath,
215765
+ adapter,
215766
+ scanRootPath,
215767
+ excludedDirectoryRoots
215768
+ ));
215683
215769
  continue;
215684
215770
  }
215685
215771
  if (entry.isFile() && adapter.dependencyParsingFileExtensions.includes(
@@ -216331,8 +216417,11 @@ async function collectCrossModuleImports(workspaceRoot, modulesRootRelativePath,
216331
216417
  ([left], [right]) => left.localeCompare(right)
216332
216418
  )) {
216333
216419
  const childModuleRoots = [...moduleRoots.entries()].filter(([childModuleName, childModuleRoot]) => childModuleName !== moduleName && isPathInsideOrEqual2(childModuleRoot, moduleRoot)).map(([, childModuleRoot]) => childModuleRoot);
216334
- const sourceFiles = (await collectSourceFiles(moduleRoot, adapter)).filter(
216335
- (sourceFile) => !childModuleRoots.some((childModuleRoot) => isPathInsideOrEqual2(sourceFile, childModuleRoot))
216420
+ const sourceFiles = await collectSourceFiles(
216421
+ moduleRoot,
216422
+ adapter,
216423
+ moduleRoot,
216424
+ childModuleRoots
216336
216425
  );
216337
216426
  for (const sourceFilePath of sourceFiles) {
216338
216427
  const sourceFileRelativePath = normalizePath3(
@@ -216558,6 +216647,16 @@ function validateComponentDependencyPolicies(architectureContract, imports) {
216558
216647
  }
216559
216648
  return findings;
216560
216649
  }
216650
+ function isModuleScopedDependencyImport(architectureContract, dependencyImport) {
216651
+ if (!architectureContract) {
216652
+ return true;
216653
+ }
216654
+ const dependencyMetadata = resolveImportDependencyContextMetadata(
216655
+ architectureContract,
216656
+ dependencyImport
216657
+ );
216658
+ return !dependencyMetadata.isExternal && dependencyMetadata.dependencyType !== "cross-component";
216659
+ }
216561
216660
  function buildDependencyConfigSkippedFindings(reason, options) {
216562
216661
  const findings = [];
216563
216662
  if (options.checkForbiddenDependencies) {
@@ -216861,8 +216960,12 @@ async function buildDependencyGraphSummary(workspaceRoot, architectureContract,
216861
216960
  workspacePackageAliases,
216862
216961
  adapter
216863
216962
  );
216963
+ options?.onCollectedImports?.(imports);
216864
216964
  const peerImports = filterPeerModuleImports(imports);
216865
- const actualBySource = buildActualImportedModuleSetBySource(peerImports, registeredModuleIds);
216965
+ const moduleScopedPeerImports = peerImports.filter(
216966
+ (dependencyImport) => isModuleScopedDependencyImport(architectureContract, dependencyImport)
216967
+ );
216968
+ const actualBySource = buildActualImportedModuleSetBySource(moduleScopedPeerImports, registeredModuleIds);
216866
216969
  const moduleContractCache = /* @__PURE__ */ new Map();
216867
216970
  const declaredBySource = /* @__PURE__ */ new Map();
216868
216971
  const hasContractByModule = /* @__PURE__ */ new Map();
@@ -216942,7 +217045,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216942
217045
  workspaceRoot,
216943
217046
  moduleRoots
216944
217047
  );
216945
- const imports = await collectCrossModuleImports(
217048
+ const imports = options.precomputedImports ? [...options.precomputedImports] : await collectCrossModuleImports(
216946
217049
  workspaceRoot,
216947
217050
  modulesRoot,
216948
217051
  moduleRoots,
@@ -216951,12 +217054,15 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
216951
217054
  adapter
216952
217055
  );
216953
217056
  const peerImports = filterPeerModuleImports(imports);
217057
+ const moduleScopedPeerImports = peerImports.filter(
217058
+ (dependencyImport) => isModuleScopedDependencyImport(architectureContract, dependencyImport)
217059
+ );
216954
217060
  const findings = [];
216955
217061
  const moduleContractCache = /* @__PURE__ */ new Map();
216956
217062
  if (options.checkDeclaredDependencies) {
216957
217063
  const failuresByDependency = /* @__PURE__ */ new Map();
216958
217064
  const skippedFindings = /* @__PURE__ */ new Map();
216959
- for (const dependencyImport of peerImports) {
217065
+ for (const dependencyImport of moduleScopedPeerImports) {
216960
217066
  const dependencyMetadata = resolveImportDependencyContextMetadata(
216961
217067
  architectureContract,
216962
217068
  dependencyImport
@@ -217063,7 +217169,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
217063
217169
  });
217064
217170
  if (failures.length > 0) {
217065
217171
  findings.push(...failures);
217066
- } else if (peerImports.length === 0) {
217172
+ } else if (moduleScopedPeerImports.length === 0) {
217067
217173
  findings.push({
217068
217174
  result: makeValidationResult(
217069
217175
  "AP-DEP-004",
@@ -217088,7 +217194,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
217088
217194
  if (options.checkPublicSurfaceImports) {
217089
217195
  const failures = [];
217090
217196
  const skippedFindings = /* @__PURE__ */ new Map();
217091
- for (const dependencyImport of peerImports) {
217197
+ for (const dependencyImport of moduleScopedPeerImports) {
217092
217198
  const dependencyMetadata = resolveImportDependencyContextMetadata(
217093
217199
  architectureContract,
217094
217200
  dependencyImport
@@ -217212,7 +217318,7 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
217212
217318
  }
217213
217319
  if (failures.length > 0) {
217214
217320
  findings.push(...failures);
217215
- } else if (peerImports.length === 0) {
217321
+ } else if (moduleScopedPeerImports.length === 0) {
217216
217322
  findings.push({
217217
217323
  result: makeValidationResult(
217218
217324
  "AP-DEP-005",
@@ -217242,19 +217348,20 @@ async function validateDependencyContractBoundaries(workspaceRoot, architectureC
217242
217348
  architectureContract,
217243
217349
  { canonicalizeOverlappingPaths: true }
217244
217350
  );
217245
- const canonicalWorkspacePackageAliases = await buildWorkspacePackageAliasMap(
217246
- workspaceRoot,
217247
- canonicalModuleRoots
217248
- );
217249
- const canonicalImports = await collectCrossModuleImports(
217351
+ const canonicalImports = options.precomputedImports ? [...options.precomputedImports] : await collectCrossModuleImports(
217250
217352
  workspaceRoot,
217251
217353
  modulesRoot,
217252
217354
  canonicalModuleRoots,
217253
217355
  explicitStandaloneModuleByPath,
217254
- canonicalWorkspacePackageAliases,
217356
+ await buildWorkspacePackageAliasMap(
217357
+ workspaceRoot,
217358
+ canonicalModuleRoots
217359
+ ),
217255
217360
  adapter
217256
217361
  );
217257
- const canonicalPeerImports = filterPeerModuleImports(canonicalImports);
217362
+ const canonicalPeerImports = filterPeerModuleImports(canonicalImports).filter(
217363
+ (dependencyImport) => isModuleScopedDependencyImport(architectureContract, dependencyImport)
217364
+ );
217258
217365
  const registeredModuleIds = /* @__PURE__ */ new Set([
217259
217366
  ...canonicalModuleRoots.keys(),
217260
217367
  ...explicitStandaloneModuleByPath.values()
@@ -217340,7 +217447,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
217340
217447
  workspaceRoot,
217341
217448
  moduleRoots
217342
217449
  );
217343
- const imports = await collectCrossModuleImports(
217450
+ const imports = options.precomputedImports ? [...options.precomputedImports] : await collectCrossModuleImports(
217344
217451
  workspaceRoot,
217345
217452
  modulesRootRelativePath,
217346
217453
  moduleRoots,
@@ -217349,6 +217456,9 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
217349
217456
  adapter
217350
217457
  );
217351
217458
  const peerImports = filterPeerModuleImports(imports);
217459
+ const moduleScopedPeerImports = peerImports.filter(
217460
+ (dependencyImport) => isModuleScopedDependencyImport(parsedArchitectureContract, dependencyImport)
217461
+ );
217352
217462
  const findings = [];
217353
217463
  let dependencyRules;
217354
217464
  let dependencyRulesSkipReason;
@@ -217369,7 +217479,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
217369
217479
  }
217370
217480
  }
217371
217481
  if (options.checkCircularDependencies) {
217372
- const cycles = detectCircularDependencies([...moduleRoots.keys()], peerImports);
217482
+ const cycles = detectCircularDependencies([...moduleRoots.keys()], moduleScopedPeerImports);
217373
217483
  if (cycles.length === 0) {
217374
217484
  findings.push({
217375
217485
  result: makeValidationResult(
@@ -217401,7 +217511,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
217401
217511
  })
217402
217512
  );
217403
217513
  } else {
217404
- const violations = peerImports.filter((dependencyImport) => {
217514
+ const violations = moduleScopedPeerImports.filter((dependencyImport) => {
217405
217515
  if (parsedArchitectureContract) {
217406
217516
  const dependencyMetadata = resolveImportDependencyContextMetadata(
217407
217517
  parsedArchitectureContract,
@@ -217466,7 +217576,7 @@ async function validateDependencyBoundaries(workspaceRoot, options) {
217466
217576
  })
217467
217577
  );
217468
217578
  } else {
217469
- const violations = peerImports.filter((dependencyImport) => {
217579
+ const violations = moduleScopedPeerImports.filter((dependencyImport) => {
217470
217580
  if (parsedArchitectureContract) {
217471
217581
  const dependencyMetadata = resolveImportDependencyContextMetadata(
217472
217582
  parsedArchitectureContract,
@@ -218997,7 +219107,7 @@ function hasConfiguredModuleRegistryEntries(config) {
218997
219107
  return Object.keys(config.modules).length > 0;
218998
219108
  }
218999
219109
  function hasExplicitModuleRegistry(config) {
219000
- return Boolean(config && config.modules && typeof config.modules === "object" && !Array.isArray(config.modules));
219110
+ return hasConfiguredModuleRegistryEntries(config);
219001
219111
  }
219002
219112
  function normalizePublicEntrypointList(entries) {
219003
219113
  return sortUnique3(entries.map((entry) => normalizePath6(entry.trim())).filter((entry) => entry.length > 0));
@@ -219225,6 +219335,59 @@ async function discoverModulesFromFallbackScan(workspaceRoot, modulesRoot = "src
219225
219335
  }
219226
219336
  return discovered;
219227
219337
  }
219338
+ function getComponentEntries(contract) {
219339
+ const components = contract?.components;
219340
+ if (!components) {
219341
+ return [];
219342
+ }
219343
+ const rawEntries = Array.isArray(components) ? components.map((entry) => {
219344
+ const typed = entry;
219345
+ return [typed.id, entry];
219346
+ }) : Object.entries(components);
219347
+ const entries = [];
219348
+ for (const [key, value] of rawEntries) {
219349
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
219350
+ continue;
219351
+ }
219352
+ const typed = value;
219353
+ const componentId = typeof typed.id === "string" && typed.id.trim().length > 0 ? typed.id.trim() : typeof key === "string" && key.trim().length > 0 ? key.trim() : void 0;
219354
+ const sourcePath = typeof typed.path === "string" && typed.path.trim().length > 0 ? normalizePath6(typed.path.trim()) : void 0;
219355
+ if (!componentId || !sourcePath || sourcePath === ".") {
219356
+ continue;
219357
+ }
219358
+ entries.push({ componentId, sourcePath });
219359
+ }
219360
+ return entries.sort((left, right) => left.componentId.localeCompare(right.componentId));
219361
+ }
219362
+ async function discoverModulesFromComponents(workspaceRoot, contract, contractsRoot = ".archpilot/contracts") {
219363
+ const discovered = [];
219364
+ for (const component of getComponentEntries(contract)) {
219365
+ const absoluteSourcePath = path10.join(workspaceRoot, ...component.sourcePath.split("/"));
219366
+ let sourcePathExists = false;
219367
+ let sourceIsDirectory = false;
219368
+ try {
219369
+ const sourceStats = await import_node_fs7.promises.stat(absoluteSourcePath);
219370
+ sourcePathExists = true;
219371
+ sourceIsDirectory = sourceStats.isDirectory();
219372
+ } catch {
219373
+ sourcePathExists = false;
219374
+ }
219375
+ if (!sourceIsDirectory) {
219376
+ continue;
219377
+ }
219378
+ const contractPath = `${contractsRoot}/${toSafeContractModuleFileStem2(component.componentId)}.contract.json`;
219379
+ discovered.push({
219380
+ moduleName: component.componentId,
219381
+ sourcePath: component.sourcePath,
219382
+ discoverySource: "inferred-scan",
219383
+ sourcePathExists,
219384
+ ...resolveModuleScopeForPath(workspaceRoot, component.sourcePath, contract) ? { scope: resolveModuleScopeForPath(workspaceRoot, component.sourcePath, contract) } : {},
219385
+ contractPath,
219386
+ contractExists: await pathExists7(path10.join(workspaceRoot, ...contractPath.split("/")))
219387
+ });
219388
+ }
219389
+ return discovered;
219390
+ }
219228
219391
  function toArchitectureMapEdge(edge) {
219229
219392
  return {
219230
219393
  source: edge.sourceModule,
@@ -219319,7 +219482,11 @@ async function generateArchitectureMap(workspaceRoot, options) {
219319
219482
  );
219320
219483
  const modules = await measure(
219321
219484
  "architecture-map.discovery",
219322
- () => hasArchitectureJson && !shouldUseConfiguredRootInference && !shouldUseTopLevelWorkspaceInference ? discoverModulesFromArchitectureContract(workspaceRoot, contract, modulesRoot) : discoverModulesFromFallbackScan(
219485
+ () => hasArchitectureJson && !shouldUseConfiguredRootInference && !shouldUseTopLevelWorkspaceInference ? discoverModulesFromArchitectureContract(workspaceRoot, contract, modulesRoot) : hasArchitectureJson && getComponentEntries(contract).length > 0 ? discoverModulesFromComponents(
219486
+ workspaceRoot,
219487
+ contract,
219488
+ resolveContractsRootFromContract(contract)
219489
+ ) : discoverModulesFromFallbackScan(
219323
219490
  workspaceRoot,
219324
219491
  modulesRoot,
219325
219492
  resolveContractsRootFromContract(contract),
@@ -222098,13 +222265,6 @@ var overallSetupGapWarningPenalty = 1;
222098
222265
  var overallSetupGapInfoPenalty = 1;
222099
222266
  var minScore = 0;
222100
222267
  var maxScore = 100;
222101
- var singleBlockingErrorCap = 55;
222102
- var twoBlockingErrorCap = 40;
222103
- var severalBlockingErrorCap = 30;
222104
- var manyBlockingErrorCap = 20;
222105
- var criticalStructuralCap = 5;
222106
- var architectureSmellWarningCategoryPenaltyCap = 7;
222107
- var publicSurfaceBypassRuleIds = /* @__PURE__ */ new Set(["AP-DEP-005"]);
222108
222268
  var cycleRuleIds = /* @__PURE__ */ new Set(["AP-DEP-001", "AP-DEP-008"]);
222109
222269
  var severeDependencyRuleIds = /* @__PURE__ */ new Set([
222110
222270
  "AP-DEP-001",
@@ -222153,6 +222313,9 @@ var mediumWeightHeuristicRuleIds = /* @__PURE__ */ new Set([
222153
222313
  "AP-DOM-005",
222154
222314
  "AP-API-015"
222155
222315
  ]);
222316
+ var warningFamilyPenaltyCap = 18;
222317
+ var heuristicWarningFamilyPenaltyCap = 12;
222318
+ var architectureSmellFamilyPenaltyCap = 7;
222156
222319
  function clampScore(value) {
222157
222320
  if (value < minScore) {
222158
222321
  return minScore;
@@ -222162,6 +222325,204 @@ function clampScore(value) {
222162
222325
  }
222163
222326
  return value;
222164
222327
  }
222328
+ function getSeverityRank(severity) {
222329
+ if (severity === "error") {
222330
+ return 0;
222331
+ }
222332
+ if (severity === "warning") {
222333
+ return 1;
222334
+ }
222335
+ return 2;
222336
+ }
222337
+ function getRuleFamily(ruleId) {
222338
+ if (!ruleId) {
222339
+ return "unknown";
222340
+ }
222341
+ const match = /^AP-[A-Z]+/u.exec(ruleId);
222342
+ return match?.[0] ?? ruleId;
222343
+ }
222344
+ function extractProblemIdentity(deductionKey) {
222345
+ if (!deductionKey) {
222346
+ return "";
222347
+ }
222348
+ if (deductionKey.includes("|")) {
222349
+ const parts2 = deductionKey.split("|");
222350
+ if (parts2[0] === "quality" || parts2[0] === "setup" || parts2[0] === "guidance" || parts2[0] === "passed") {
222351
+ parts2.shift();
222352
+ }
222353
+ if (parts2[0]?.startsWith("AP-")) {
222354
+ parts2.shift();
222355
+ }
222356
+ return parts2.join("|");
222357
+ }
222358
+ const parts = deductionKey.split(":");
222359
+ if (parts[0] === "quality" || parts[0] === "setup" || parts[0] === "guidance" || parts[0] === "passed") {
222360
+ parts.shift();
222361
+ }
222362
+ if (parts[0]?.startsWith("AP-")) {
222363
+ parts.shift();
222364
+ }
222365
+ return parts.join(":");
222366
+ }
222367
+ function normalizeProblemIdentity(result, identity) {
222368
+ const normalized = identity.replace(/\\/g, "/").trim().toLowerCase();
222369
+ if (!normalized) {
222370
+ return "";
222371
+ }
222372
+ if (normalized.includes("|")) {
222373
+ const parts = normalized.split("|");
222374
+ if (result.category === "dependency" && parts.length >= 4 && /^\d+$/u.test(parts[3] ?? "")) {
222375
+ return [parts[0], parts[1], parts[2], parts[4] ?? ""].join("|");
222376
+ }
222377
+ }
222378
+ return normalized;
222379
+ }
222380
+ function buildQualityProblemClusterKey(result) {
222381
+ const family = getRuleFamily(result.ruleId);
222382
+ const identity = normalizeProblemIdentity(result, extractProblemIdentity(result.deductionKey));
222383
+ if (typeof result.ruleId === "string" && cycleRuleIds.has(result.ruleId)) {
222384
+ return ["quality", result.category, family, "cycle", identity || "shared-root-cause"].join("|");
222385
+ }
222386
+ if (result.severity === "warning") {
222387
+ return ["quality", result.category, family, identity || result.ruleId || "generic"].join("|");
222388
+ }
222389
+ return ["quality", result.category, family, result.ruleId ?? "unknown", identity || "generic"].join("|");
222390
+ }
222391
+ function getClusterWeight(cluster, index) {
222392
+ if (index === 0) {
222393
+ return 1;
222394
+ }
222395
+ if (cluster.severity === "error") {
222396
+ return Math.max(0.35, 0.85 - index * 0.15);
222397
+ }
222398
+ if (cluster.isArchitectureSmell) {
222399
+ return Math.max(0.15, 0.65 - index * 0.12);
222400
+ }
222401
+ if (cluster.isHeuristic) {
222402
+ return Math.max(0.2, 0.75 - index * 0.12);
222403
+ }
222404
+ return Math.max(0.2, 0.8 - index * 0.12);
222405
+ }
222406
+ function getFamilyPenaltyCap(cluster) {
222407
+ if (cluster.severity !== "warning") {
222408
+ return void 0;
222409
+ }
222410
+ if (cluster.isArchitectureSmell) {
222411
+ return architectureSmellFamilyPenaltyCap;
222412
+ }
222413
+ if (cluster.isHeuristic) {
222414
+ return heuristicWarningFamilyPenaltyCap;
222415
+ }
222416
+ return warningFamilyPenaltyCap;
222417
+ }
222418
+ function summarizeArchitectureScoreProblems(input2) {
222419
+ const qualityResults = input2.results.filter(
222420
+ (result) => !result.passed && (result.impact ?? "quality") === "quality"
222421
+ );
222422
+ const clusterMap = /* @__PURE__ */ new Map();
222423
+ for (const result of qualityResults) {
222424
+ const key = buildQualityProblemClusterKey(result);
222425
+ const family = getRuleFamily(result.ruleId);
222426
+ const existing = clusterMap.get(key);
222427
+ const resultPenalty = getQualityPenaltyForResult(result);
222428
+ if (existing) {
222429
+ existing.resultCount += 1;
222430
+ if (getSeverityRank(result.severity) < getSeverityRank(existing.severity)) {
222431
+ existing.severity = result.severity;
222432
+ }
222433
+ existing.basePenalty = Math.max(existing.basePenalty, resultPenalty);
222434
+ if (typeof result.ruleId === "string") {
222435
+ existing.ruleIds.add(result.ruleId);
222436
+ }
222437
+ existing.isHeuristic = existing.isHeuristic || isHeuristicRuleId(result.ruleId);
222438
+ existing.isArchitectureSmell = existing.isArchitectureSmell || isArchitectureSmellWarningRuleId(result.ruleId);
222439
+ continue;
222440
+ }
222441
+ clusterMap.set(key, {
222442
+ key,
222443
+ category: result.category,
222444
+ family,
222445
+ severity: result.severity,
222446
+ impact: result.impact ?? "quality",
222447
+ ruleIds: new Set(typeof result.ruleId === "string" ? [result.ruleId] : []),
222448
+ resultCount: 1,
222449
+ basePenalty: resultPenalty,
222450
+ isHeuristic: isHeuristicRuleId(result.ruleId),
222451
+ isArchitectureSmell: isArchitectureSmellWarningRuleId(result.ruleId)
222452
+ });
222453
+ }
222454
+ const familyGroups = /* @__PURE__ */ new Map();
222455
+ const preliminaryClusters = [...clusterMap.values()].sort((left, right) => {
222456
+ const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
222457
+ if (severityCompare !== 0) {
222458
+ return severityCompare;
222459
+ }
222460
+ const categoryCompare = left.category.localeCompare(right.category);
222461
+ if (categoryCompare !== 0) {
222462
+ return categoryCompare;
222463
+ }
222464
+ const familyCompare = left.family.localeCompare(right.family);
222465
+ if (familyCompare !== 0) {
222466
+ return familyCompare;
222467
+ }
222468
+ return left.key.localeCompare(right.key);
222469
+ }).map((entry) => {
222470
+ const confidenceBonus = Math.min(3, Math.max(0, entry.ruleIds.size - 1));
222471
+ const duplicateBonus = Math.min(2, Math.max(0, Math.ceil(Math.log2(entry.resultCount)) - 1));
222472
+ return {
222473
+ key: entry.key,
222474
+ category: entry.category,
222475
+ family: entry.family,
222476
+ severity: entry.severity,
222477
+ impact: entry.impact,
222478
+ ruleIds: [...entry.ruleIds].sort((left, right) => left.localeCompare(right)),
222479
+ resultCount: entry.resultCount,
222480
+ penalty: entry.basePenalty + confidenceBonus + duplicateBonus,
222481
+ isHeuristic: entry.isHeuristic,
222482
+ isArchitectureSmell: entry.isArchitectureSmell
222483
+ };
222484
+ });
222485
+ for (const cluster of preliminaryClusters) {
222486
+ const familyKey = `${cluster.category}|${cluster.family}`;
222487
+ const existing = familyGroups.get(familyKey) ?? [];
222488
+ existing.push(cluster);
222489
+ familyGroups.set(familyKey, existing);
222490
+ }
222491
+ const clusters = [];
222492
+ for (const group of [...familyGroups.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([, value]) => value)) {
222493
+ const ordered = [...group].sort((left, right) => {
222494
+ const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
222495
+ if (severityCompare !== 0) {
222496
+ return severityCompare;
222497
+ }
222498
+ if (left.penalty !== right.penalty) {
222499
+ return right.penalty - left.penalty;
222500
+ }
222501
+ return left.key.localeCompare(right.key);
222502
+ });
222503
+ let appliedPenalty = 0;
222504
+ for (const [index, cluster] of ordered.entries()) {
222505
+ const weightedPenalty = Math.round(cluster.penalty * getClusterWeight(cluster, index));
222506
+ const familyCap = getFamilyPenaltyCap(cluster);
222507
+ const remainingCap = familyCap === void 0 ? void 0 : Math.max(0, familyCap - appliedPenalty);
222508
+ const finalPenalty = remainingCap === void 0 ? weightedPenalty : Math.min(weightedPenalty, remainingCap);
222509
+ appliedPenalty += finalPenalty;
222510
+ clusters.push({
222511
+ ...cluster,
222512
+ penalty: finalPenalty
222513
+ });
222514
+ }
222515
+ }
222516
+ const qualityClusterCount = clusters.length;
222517
+ const blockingClusterCount = clusters.filter((cluster) => cluster.severity === "error").length;
222518
+ const heuristicClusterCount = clusters.filter((cluster) => cluster.isHeuristic).length;
222519
+ return {
222520
+ clusters,
222521
+ qualityClusterCount,
222522
+ blockingClusterCount,
222523
+ heuristicClusterCount
222524
+ };
222525
+ }
222165
222526
  function getOverallSetupGapPenaltyForSeverity(severity) {
222166
222527
  if (severity === "error") {
222167
222528
  return overallSetupGapErrorPenalty;
@@ -222272,19 +222633,20 @@ function deriveGovernanceRiskLevelFromResults(results) {
222272
222633
  function applyFinalScoreAdjustments(input2) {
222273
222634
  let adjusted = input2.baseScore;
222274
222635
  const uniqueResults = dedupeResultsByDeductionKey(input2.results);
222275
- const publicSurfaceBypassViolations = uniqueResults.filter(
222276
- (result) => !result.passed && (result.impact ?? "quality") === "quality" && typeof result.ruleId === "string" && publicSurfaceBypassRuleIds.has(result.ruleId)
222636
+ const problemSummary = summarizeArchitectureScoreProblems({ results: uniqueResults });
222637
+ const publicSurfaceBypassViolations = problemSummary.clusters.filter(
222638
+ (cluster) => cluster.ruleIds.includes("AP-DEP-005")
222277
222639
  ).length;
222278
- const cycleViolations = uniqueResults.filter(
222279
- (result) => !result.passed && (result.impact ?? "quality") === "quality" && typeof result.ruleId === "string" && cycleRuleIds.has(result.ruleId)
222640
+ const cycleViolations = problemSummary.clusters.filter(
222641
+ (cluster) => cluster.ruleIds.some((ruleId) => cycleRuleIds.has(ruleId))
222280
222642
  ).length;
222281
222643
  adjusted -= Math.min(6, publicSurfaceBypassViolations * 2);
222282
222644
  adjusted -= Math.min(4, cycleViolations * 2);
222283
222645
  const governanceRiskLevel = input2.context?.governanceRiskLevel ?? deriveGovernanceRiskLevelFromResults(input2.results);
222284
222646
  if (governanceRiskLevel === "critical") {
222285
- adjusted -= 6;
222647
+ adjusted -= 4;
222286
222648
  } else if (governanceRiskLevel === "high") {
222287
- adjusted -= 3;
222649
+ adjusted -= 2;
222288
222650
  }
222289
222651
  if (input2.context?.diffContextAvailable === false) {
222290
222652
  adjusted -= 1;
@@ -222293,7 +222655,7 @@ function applyFinalScoreAdjustments(input2) {
222293
222655
  adjusted = Math.min(adjusted, 85);
222294
222656
  }
222295
222657
  if (governanceRiskLevel === "critical") {
222296
- adjusted = Math.min(adjusted, 80);
222658
+ adjusted = Math.min(adjusted, 82);
222297
222659
  }
222298
222660
  const apDep004OnlyFloor = getApDep004OnlyContractDriftFloor({
222299
222661
  categoryScores: input2.categoryScores,
@@ -222303,92 +222665,8 @@ function applyFinalScoreAdjustments(input2) {
222303
222665
  adjusted = Math.max(adjusted, apDep004OnlyFloor);
222304
222666
  return clampScore(Math.round(adjusted));
222305
222667
  }
222306
- const structuralCap = getBlockingStructuralScoreCap(uniqueResults);
222307
- if (structuralCap !== void 0) {
222308
- if (structuralCap.critical) {
222309
- adjusted = Math.min(adjusted, structuralCap.cap);
222310
- } else {
222311
- adjusted = Math.max(
222312
- adjusted,
222313
- Math.min(
222314
- structuralCap.cap,
222315
- getProportionalScoreForBlockingFailures({
222316
- categoryScores: input2.categoryScores,
222317
- results: uniqueResults,
222318
- blockingErrorCount: structuralCap.blockingErrorCount
222319
- })
222320
- )
222321
- );
222322
- }
222323
- }
222324
222668
  return clampScore(Math.round(adjusted));
222325
222669
  }
222326
- function getBlockingStructuralScoreCap(results) {
222327
- const blockingQualityErrors = results.filter(
222328
- (result) => !result.passed && result.severity === "error" && (result.impact ?? "quality") === "quality"
222329
- );
222330
- if (blockingQualityErrors.length === 0) {
222331
- return void 0;
222332
- }
222333
- const ruleCounts = /* @__PURE__ */ new Map();
222334
- for (const result of blockingQualityErrors) {
222335
- if (typeof result.ruleId !== "string") {
222336
- continue;
222337
- }
222338
- ruleCounts.set(result.ruleId, (ruleCounts.get(result.ruleId) ?? 0) + 1);
222339
- }
222340
- const hasCriticalCycleCombination = (ruleCounts.get("AP-DEP-001") ?? 0) > 0 && (ruleCounts.get("AP-DEP-008") ?? 0) > 0;
222341
- const hasForbiddenNonPublicCombination = (ruleCounts.get("AP-DEP-002") ?? 0) > 0 && (ruleCounts.get("AP-DEP-003") ?? 0) > 0;
222342
- const hasRepeatedSevereStructuralRule = [...severeDependencyRuleIds].some(
222343
- (ruleId) => (ruleCounts.get(ruleId) ?? 0) >= 5
222344
- );
222345
- if (hasCriticalCycleCombination || hasForbiddenNonPublicCombination || hasRepeatedSevereStructuralRule) {
222346
- return {
222347
- cap: criticalStructuralCap,
222348
- blockingErrorCount: blockingQualityErrors.length,
222349
- critical: true
222350
- };
222351
- }
222352
- if (blockingQualityErrors.length === 1) {
222353
- return {
222354
- cap: singleBlockingErrorCap,
222355
- blockingErrorCount: blockingQualityErrors.length,
222356
- critical: false
222357
- };
222358
- }
222359
- if (blockingQualityErrors.length === 2) {
222360
- return {
222361
- cap: twoBlockingErrorCap,
222362
- blockingErrorCount: blockingQualityErrors.length,
222363
- critical: false
222364
- };
222365
- }
222366
- if (blockingQualityErrors.length <= 4) {
222367
- return {
222368
- cap: severalBlockingErrorCap,
222369
- blockingErrorCount: blockingQualityErrors.length,
222370
- critical: false
222371
- };
222372
- }
222373
- return {
222374
- cap: manyBlockingErrorCap,
222375
- blockingErrorCount: blockingQualityErrors.length,
222376
- critical: false
222377
- };
222378
- }
222379
- function getProportionalScoreForBlockingFailures(input2) {
222380
- const categoryScores = Object.values(input2.categoryScores ?? {});
222381
- if (categoryScores.length === 0) {
222382
- return 0;
222383
- }
222384
- const categoryAverage = categoryScores.reduce((total, score) => total + score, 0) / categoryScores.length;
222385
- const warningCount = input2.results.filter(
222386
- (result) => !result.passed && result.severity === "warning" && (result.impact ?? "quality") === "quality"
222387
- ).length;
222388
- const warningVolumePenalty = Math.min(35, warningCount * 0.45);
222389
- const blockingPenalty = 8 + Math.max(0, input2.blockingErrorCount - 1) * 8;
222390
- return clampScore(Math.round(categoryAverage - warningVolumePenalty - blockingPenalty));
222391
- }
222392
222670
  function getApDep004OnlyContractDriftFloor(input2) {
222393
222671
  if (!input2.categoryScores) {
222394
222672
  return void 0;
@@ -222413,7 +222691,35 @@ function getApDep004OnlyContractDriftFloor(input2) {
222413
222691
  const categoryAverage = scores.reduce((total, score) => total + score, 0) / scores.length;
222414
222692
  return clampScore(Math.min(75, Math.max(65, Math.round(categoryAverage - 20))));
222415
222693
  }
222694
+ function calculateScopedScores(results, scopeKey, categories, context) {
222695
+ const grouped = /* @__PURE__ */ new Map();
222696
+ for (const result of results) {
222697
+ const scopeId = result[scopeKey];
222698
+ if (!scopeId) {
222699
+ continue;
222700
+ }
222701
+ const existing = grouped.get(scopeId) ?? [];
222702
+ existing.push(result);
222703
+ grouped.set(scopeId, existing);
222704
+ }
222705
+ if (grouped.size === 0) {
222706
+ return void 0;
222707
+ }
222708
+ return [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right)).reduce((accumulator, [scopeId, scopedResults]) => {
222709
+ accumulator[scopeId] = calculateArchitectureHealthScore({
222710
+ results: scopedResults,
222711
+ categories,
222712
+ context,
222713
+ includeScopedScores: false
222714
+ }).overallScore;
222715
+ return accumulator;
222716
+ }, {});
222717
+ }
222416
222718
  function calculateArchitectureHealthScore(input2) {
222719
+ const applicableResults = input2.results.filter(
222720
+ (result) => result.applicability !== "not-applicable"
222721
+ );
222722
+ const notApplicable = input2.results.length - applicableResults.length;
222417
222723
  const categoryOrder = input2.categories && input2.categories.length > 0 ? [...input2.categories] : [...defaultArchitectureHealthCategories];
222418
222724
  const categoryScoreMap = /* @__PURE__ */ new Map();
222419
222725
  for (const category of categoryOrder) {
@@ -222434,8 +222740,8 @@ function calculateArchitectureHealthScore(input2) {
222434
222740
  let readinessScore = 100;
222435
222741
  const appliedHealthDeductions = /* @__PURE__ */ new Set();
222436
222742
  const appliedReadinessDeductions = /* @__PURE__ */ new Set();
222437
- const architectureSmellWarningPenaltyByCategory = /* @__PURE__ */ new Map();
222438
- for (const result of input2.results) {
222743
+ const qualityResults = [];
222744
+ for (const result of applicableResults) {
222439
222745
  if (result.passed) {
222440
222746
  passed += 1;
222441
222747
  continue;
@@ -222465,21 +222771,7 @@ function calculateArchitectureHealthScore(input2) {
222465
222771
  readinessCategoryScoreMap.set(result.category, 100);
222466
222772
  }
222467
222773
  if (impact === "quality") {
222468
- const shouldApply = !healthDeductionKey || !appliedHealthDeductions.has(healthDeductionKey);
222469
- if (shouldApply) {
222470
- let penalty = getQualityPenaltyForResult(result);
222471
- if (result.severity === "warning" && isArchitectureSmellWarningRuleId(result.ruleId)) {
222472
- const applied = architectureSmellWarningPenaltyByCategory.get(result.category) ?? 0;
222473
- penalty = Math.min(penalty, Math.max(0, architectureSmellWarningCategoryPenaltyCap - applied));
222474
- architectureSmellWarningPenaltyByCategory.set(result.category, applied + penalty);
222475
- }
222476
- overallScore = clampScore(overallScore - penalty);
222477
- const categoryScore = categoryScoreMap.get(result.category) ?? 100;
222478
- categoryScoreMap.set(result.category, clampScore(categoryScore - penalty));
222479
- if (healthDeductionKey) {
222480
- appliedHealthDeductions.add(healthDeductionKey);
222481
- }
222482
- }
222774
+ qualityResults.push(result);
222483
222775
  }
222484
222776
  if (impact === "setup-gap") {
222485
222777
  const shouldApplyOverall = !healthDeductionKey || !appliedHealthDeductions.has(healthDeductionKey);
@@ -222513,6 +222805,11 @@ function calculateArchitectureHealthScore(input2) {
222513
222805
  }
222514
222806
  }
222515
222807
  }
222808
+ for (const cluster of summarizeArchitectureScoreProblems({ results: qualityResults }).clusters) {
222809
+ overallScore = clampScore(overallScore - cluster.penalty);
222810
+ const categoryScore = categoryScoreMap.get(cluster.category) ?? 100;
222811
+ categoryScoreMap.set(cluster.category, clampScore(categoryScore - cluster.penalty));
222812
+ }
222516
222813
  const categoryScores = [...categoryScoreMap.entries()].sort((left, right) => left[0].localeCompare(right[0])).reduce((accumulator, [category, score]) => {
222517
222814
  accumulator[category] = score;
222518
222815
  return accumulator;
@@ -222524,14 +222821,19 @@ function calculateArchitectureHealthScore(input2) {
222524
222821
  return {
222525
222822
  overallScore: applyFinalScoreAdjustments({
222526
222823
  baseScore: overallScore,
222527
- results: input2.results,
222824
+ results: applicableResults,
222528
222825
  categoryScores,
222529
222826
  context: input2.context
222530
222827
  }),
222531
222828
  categoryScores,
222829
+ ...input2.includeScopedScores === false ? {} : {
222830
+ componentScores: calculateScopedScores(applicableResults, "componentId", input2.categories, input2.context),
222831
+ moduleScores: calculateScopedScores(applicableResults, "moduleId", input2.categories, input2.context)
222832
+ },
222532
222833
  readinessScore,
222533
222834
  readinessCategoryScores,
222534
- totalChecks: input2.results.length,
222835
+ totalChecks: applicableResults.length,
222836
+ notApplicable,
222535
222837
  errors,
222536
222838
  warnings,
222537
222839
  info,
@@ -228948,6 +229250,8 @@ var ValidationRuleCatalog = {
228948
229250
  title: "Modular monolith folder structure exists",
228949
229251
  category: "architecture",
228950
229252
  defaultSeverity: "error",
229253
+ architectureScope: "module",
229254
+ applicability: { requiresArchitectureModules: true },
228951
229255
  description: "Checks whether a modular monolith has a configured module root with clear top-level module or capability directories.",
228952
229256
  recommendedFix: "Set a valid module root in architecture.json and organize code into clear top-level module or capability folders under that root.",
228953
229257
  explanation: "This rule checks that architecture code is organized into explicit module or domain boundaries.",
@@ -228988,6 +229292,8 @@ src/
228988
229292
  title: "Module baseline files exist",
228989
229293
  category: "architecture",
228990
229294
  defaultSeverity: "warning",
229295
+ architectureScope: "module",
229296
+ applicability: { requiresArchitectureModules: true },
228991
229297
  description: "Modules that expose public entrypoints or are depended on by other modules must provide an index.ts public entrypoint. Internal-only modules do not require index.ts.",
228992
229298
  recommendedFix: "Ensure each module has README.md. Add index.ts for modules with declared publicEntrypoints or inbound declared dependencies from other modules."
228993
229299
  },
@@ -228996,6 +229302,8 @@ src/
228996
229302
  title: "Module registry path exists",
228997
229303
  category: "architecture",
228998
229304
  defaultSeverity: "error",
229305
+ architectureScope: "module",
229306
+ applicability: { requiresArchitectureModules: true },
228999
229307
  description: "Checks whether each module path declared in architecture.json exists as a directory in the repository.",
229000
229308
  recommendedFix: "Update the module registry path in .archpilot/architecture.json or restore the missing module directory.",
229001
229309
  whyItMatters: "This is a setup/configuration mismatch: architecture.json points at a module path that is not present in the workspace, so validation cannot reliably connect the intended architecture to the current code.",
@@ -229014,6 +229322,8 @@ src/
229014
229322
  title: "Module contract file exists",
229015
229323
  category: "architecture",
229016
229324
  defaultSeverity: "error",
229325
+ architectureScope: "module",
229326
+ applicability: { requiresArchitectureModules: true },
229017
229327
  description: "Checks whether each module contract file referenced by the module registry exists on disk.",
229018
229328
  recommendedFix: "Generate or restore the missing module contract file, or update the module registry contract path in .archpilot/architecture.json.",
229019
229329
  whyItMatters: "This is a setup/configuration mismatch: architecture.json references a module contract file that is missing, so ArchPilot cannot validate declared dependencies, public entrypoints, or module ownership for that module.",
@@ -229030,6 +229340,8 @@ src/
229030
229340
  title: "Module contract identity matches registry",
229031
229341
  category: "architecture",
229032
229342
  defaultSeverity: "error",
229343
+ architectureScope: "module",
229344
+ applicability: { requiresArchitectureModules: true },
229033
229345
  description: "Checks whether each module contract JSON is valid, has a matching module identity, and uses valid array field shapes for core contract fields.",
229034
229346
  recommendedFix: "Fix module contract JSON syntax and ensure contract.module plus contract array fields are valid and aligned with the module registry."
229035
229347
  },
@@ -229038,6 +229350,8 @@ src/
229038
229350
  title: "Module public entrypoints are aligned",
229039
229351
  category: "architecture",
229040
229352
  defaultSeverity: "error",
229353
+ architectureScope: "module",
229354
+ applicability: { requiresArchitectureModules: true },
229041
229355
  description: "Checks whether module publicEntrypoints are consistent between architecture.json and each module contract file.",
229042
229356
  recommendedFix: "Align publicEntrypoints in .archpilot/architecture.json and the module contract file so both describe the same public surface."
229043
229357
  },
@@ -229152,6 +229466,8 @@ src/
229152
229466
  title: "Circular module dependency detected",
229153
229467
  category: "dependency",
229154
229468
  defaultSeverity: "error",
229469
+ architectureScope: "module",
229470
+ applicability: { requiresArchitectureModules: true },
229155
229471
  description: "Checks for circular dependencies between modules under src/modules based on detected cross-module imports.",
229156
229472
  recommendedFix: "Break circular dependencies between modules by introducing a shared abstraction, moving common code, or reversing the dependency direction."
229157
229473
  },
@@ -229160,6 +229476,8 @@ src/
229160
229476
  title: "Forbidden cross-module dependency detected",
229161
229477
  category: "dependency",
229162
229478
  defaultSeverity: "error",
229479
+ architectureScope: "module",
229480
+ applicability: { requiresArchitectureModules: true },
229163
229481
  description: "Checks whether cross-module imports violate the allowedDependencies declared in .archpilot/dependency-rules.json.",
229164
229482
  recommendedFix: "Update .archpilot/dependency-rules.json or refactor the import so the module depends only on explicitly allowed modules."
229165
229483
  },
@@ -229168,6 +229486,8 @@ src/
229168
229486
  title: "Non-public cross-module import detected",
229169
229487
  category: "dependency",
229170
229488
  defaultSeverity: "error",
229489
+ architectureScope: "module",
229490
+ applicability: { requiresArchitectureModules: true },
229171
229491
  description: "Checks whether cross-module imports target another module through its public entrypoints rather than internal implementation files.",
229172
229492
  recommendedFix: "Import another module through src/modules/<module>/index.ts or src/modules/<module>/public rather than reaching into internal files."
229173
229493
  },
@@ -229176,6 +229496,8 @@ src/
229176
229496
  title: "Cross-module dependency missing from module contract",
229177
229497
  category: "dependency",
229178
229498
  defaultSeverity: "error",
229499
+ architectureScope: "module",
229500
+ applicability: { requiresArchitectureModules: true },
229179
229501
  description: "Checks whether each cross-module import is declared in the source module contract dependsOn list.",
229180
229502
  recommendedFix: "Add the imported module to source-module contract dependsOn or remove the cross-module dependency.",
229181
229503
  explanation: "Cross-module imports must be declared in module contracts to keep architectural dependencies explicit.",
@@ -229193,6 +229515,8 @@ src/
229193
229515
  title: "Cross-module import bypasses public module surface",
229194
229516
  category: "dependency",
229195
229517
  defaultSeverity: "error",
229518
+ architectureScope: "module",
229519
+ applicability: { requiresArchitectureModules: true },
229196
229520
  description: "Checks whether cross-module imports resolve only through target module public entrypoints declared in module contracts.",
229197
229521
  recommendedFix: "Import only through target module public entrypoints declared in its module contract.",
229198
229522
  explanation: "Cross-module imports should use only public entrypoints exposed by the target module contract.",
@@ -229207,6 +229531,8 @@ src/
229207
229531
  title: "Declared module dependency not used",
229208
229532
  category: "dependency",
229209
229533
  defaultSeverity: "warning",
229534
+ architectureScope: "module",
229535
+ applicability: { requiresArchitectureModules: true },
229210
229536
  description: "Checks whether module contract dependsOn entries are actually used by cross-module imports from that module.",
229211
229537
  recommendedFix: "Remove unused modules from dependsOn or add a legitimate cross-module import that reflects the declared dependency.",
229212
229538
  whyItMatters: "The module contract declares an architectural dependency, but the current code does not use that dependency. That mismatch makes the contract less trustworthy for impact analysis, reviews, and dependency governance.",
@@ -229226,6 +229552,8 @@ import { reserveInventory } from "../inventory";`,
229226
229552
  title: "Potential orphan module detected",
229227
229553
  category: "dependency",
229228
229554
  defaultSeverity: "warning",
229555
+ architectureScope: "module",
229556
+ applicability: { requiresArchitectureModules: true },
229229
229557
  description: "Checks whether a registered module has no peer-module dependencies and little evidence of intentional architectural use.",
229230
229558
  recommendedFix: "Add intentional-use evidence such as a contract, README, public entrypoint, package entrypoint, or remove the module if it is stale."
229231
229559
  },
@@ -229234,6 +229562,8 @@ import { reserveInventory } from "../inventory";`,
229234
229562
  title: "Transitive circular module dependency detected",
229235
229563
  category: "dependency",
229236
229564
  defaultSeverity: "error",
229565
+ architectureScope: "module",
229566
+ applicability: { requiresArchitectureModules: true },
229237
229567
  description: "Checks whether declared and actual module dependencies form transitive dependency cycles with three or more modules.",
229238
229568
  recommendedFix: "Break the dependency chain so modules only depend on lower-level modules or shared services."
229239
229569
  },
@@ -229242,6 +229572,8 @@ import { reserveInventory } from "../inventory";`,
229242
229572
  title: "Module has high inbound dependency count",
229243
229573
  category: "dependency",
229244
229574
  defaultSeverity: "warning",
229575
+ architectureScope: "module",
229576
+ applicability: { requiresArchitectureModules: true },
229245
229577
  description: "Checks whether a module has a high inbound dependency count, which may indicate an overloaded shared module or god module risk.",
229246
229578
  recommendedFix: "Review whether this module is becoming a shared bottleneck or god module. Consider splitting responsibilities or introducing clearer boundaries."
229247
229579
  },
@@ -229250,6 +229582,8 @@ import { reserveInventory } from "../inventory";`,
229250
229582
  title: "Dependency violates architecture layer direction",
229251
229583
  category: "dependency",
229252
229584
  defaultSeverity: "error",
229585
+ architectureScope: "module",
229586
+ applicability: { requiresArchitectureModules: true },
229253
229587
  description: "Checks whether module dependencies violate configured architecture layer direction rules.",
229254
229588
  recommendedFix: "Generate .archpilot/layer-rules.json through ArchPilot initialization or add the file manually. Then refactor dependencies so modules depend only on modules in lower layers."
229255
229589
  },
@@ -229258,6 +229592,7 @@ import { reserveInventory } from "../inventory";`,
229258
229592
  title: "Cross-component dependency policy violation",
229259
229593
  category: "dependency",
229260
229594
  defaultSeverity: "error",
229595
+ architectureScope: "component",
229261
229596
  description: "Checks whether cross-component imports violate allowedDependencies declared on source components in architecture.json.",
229262
229597
  recommendedFix: "Update the source component allowedDependencies list in .archpilot/architecture.json or refactor the import so the component depends only on explicitly allowed components.",
229263
229598
  whyItMatters: "Component dependency policies make workspace boundaries explicit so applications, services, and libraries do not drift into hidden coupling.",
@@ -229390,6 +229725,8 @@ import { reserveInventory } from "../inventory";`,
229390
229725
  title: "Cross-module direct database access bypasses module boundary",
229391
229726
  category: "database",
229392
229727
  defaultSeverity: "warning",
229728
+ architectureScope: "module",
229729
+ applicability: { requiresArchitectureModules: true },
229393
229730
  description: "Detects high-confidence references from one module to another module database artifact path (for example /<module>/db/ or /<module>/sql/).",
229394
229731
  recommendedFix: "Route cross-module data access through module contracts/public surfaces instead of direct database artifact references.",
229395
229732
  whyItMatters: "Direct cross-module DB access can bypass architecture boundaries and create hidden coupling.",
@@ -229782,6 +230119,54 @@ function getValidationRuleMetadataById(id) {
229782
230119
  function getAllValidationRuleMetadata() {
229783
230120
  return Object.keys(ValidationRuleCatalog).map((key) => ValidationRuleCatalog[key]).sort((a, b) => a.id.localeCompare(b.id));
229784
230121
  }
230122
+ function getConfiguredArchitectureModuleIds(contract) {
230123
+ const moduleIds = new Set(Object.keys(contract.modules ?? {}));
230124
+ const components = contract.components;
230125
+ if (components && typeof components === "object") {
230126
+ const entries = Array.isArray(components) ? components.map((component, index) => [component.id ?? `component-${index + 1}`, component]) : Object.entries(components);
230127
+ for (const [componentId, component] of entries) {
230128
+ if (!component || typeof component !== "object" || Array.isArray(component)) {
230129
+ continue;
230130
+ }
230131
+ for (const moduleId of Object.keys(component.modules ?? {})) {
230132
+ moduleIds.add(moduleId.includes("/") ? moduleId : `${componentId}/${moduleId}`);
230133
+ }
230134
+ }
230135
+ }
230136
+ return [...moduleIds].sort((left, right) => left.localeCompare(right));
230137
+ }
230138
+ function hasConfiguredArchitectureModules(contract) {
230139
+ return getConfiguredArchitectureModuleIds(contract).length > 0;
230140
+ }
230141
+ function buildNotApplicableRuleResults(contract, validationConfig) {
230142
+ const hasModules = hasConfiguredArchitectureModules(contract);
230143
+ return getAllValidationRuleMetadata().filter((meta) => meta.applicability?.requiresArchitectureModules === true).filter((meta) => !isRuleDisabled(meta.id, validationConfig)).filter(() => !hasModules).map((meta) => ({
230144
+ id: meta.id,
230145
+ severity: meta.defaultSeverity,
230146
+ passed: true,
230147
+ applicability: "not-applicable",
230148
+ applicabilityReason: "No architecture modules are configured at the project root or under components.",
230149
+ message: `${meta.title} is not applicable because this architecture has no configured modules.`
230150
+ }));
230151
+ }
230152
+ function applyRuleApplicability(results, contract, validationConfig, hasArchitectureModulesOverride) {
230153
+ if (hasArchitectureModulesOverride ?? hasConfiguredArchitectureModules(contract)) {
230154
+ return results.map((result) => ({
230155
+ ...result,
230156
+ applicability: result.applicability ?? "applicable"
230157
+ }));
230158
+ }
230159
+ const moduleRequiredRuleIds = new Set(
230160
+ getAllValidationRuleMetadata().filter((meta) => meta.applicability?.requiresArchitectureModules === true).map((meta) => meta.id)
230161
+ );
230162
+ return [
230163
+ ...results.filter((result) => !moduleRequiredRuleIds.has(result.id)).map((result) => ({
230164
+ ...result,
230165
+ applicability: result.applicability ?? "applicable"
230166
+ })),
230167
+ ...buildNotApplicableRuleResults(contract, validationConfig)
230168
+ ];
230169
+ }
229785
230170
  function getValidationSeverityPolicyNote() {
229786
230171
  return "ArchPilot uses enforcement-oriented severities by default: core architecture, API, dependency, and database safety violations are treated as errors; documentation gaps are warnings; advisory compatibility notices are informational.";
229787
230172
  }
@@ -229811,7 +230196,7 @@ function isHeuristicArchitectureSmellRuleId(ruleId) {
229811
230196
  }
229812
230197
  function buildArchitectureSmellSummaryFromResults(results) {
229813
230198
  const failedSmells = results.filter(
229814
- (result) => !result.passed && isHeuristicArchitectureSmellRuleId(result.id)
230199
+ (result) => result.applicability !== "not-applicable" && !result.passed && isHeuristicArchitectureSmellRuleId(result.id)
229815
230200
  );
229816
230201
  const familyEntries = ["AP-APP", "AP-DOM", "AP-TXN"].map((family) => {
229817
230202
  const familyResults = failedSmells.filter((result) => result.id.startsWith(family));
@@ -229832,7 +230217,9 @@ function buildArchitectureSmellSummaryFromResults(results) {
229832
230217
  };
229833
230218
  }
229834
230219
  function buildFindingCountContext(results) {
229835
- const failedResults = results.filter((result) => !result.passed);
230220
+ const failedResults = results.filter(
230221
+ (result) => result.applicability !== "not-applicable" && !result.passed
230222
+ );
229836
230223
  const normalizedKeys = /* @__PURE__ */ new Set();
229837
230224
  for (const result of failedResults) {
229838
230225
  normalizedKeys.add(classifyValidationResult(result).deductionKey);
@@ -229844,32 +230231,47 @@ function buildFindingCountContext(results) {
229844
230231
  };
229845
230232
  }
229846
230233
  function buildScoreExplanationSummary(results, score) {
230234
+ const problemSummary = summarizeArchitectureScoreProblems({
230235
+ results: results.map((result) => {
230236
+ const classification = classifyValidationResult(result);
230237
+ return {
230238
+ ruleId: result.id,
230239
+ category: getValidationRuleMetadataById(result.id).category,
230240
+ severity: result.severity,
230241
+ passed: result.passed,
230242
+ applicability: result.applicability,
230243
+ impact: classification.scoreImpact,
230244
+ deductionKey: classification.deductionKey
230245
+ };
230246
+ })
230247
+ });
229847
230248
  const categoryEntries = Object.entries(score.categoryScores).sort((left, right) => left[1] - right[1] || left[0].localeCompare(right[0])).slice(0, 3).map(([category, categoryScore]) => ({ category, score: categoryScore }));
229848
- const heuristicWarnings = results.filter(
230249
+ const applicableResults = results.filter((result) => result.applicability !== "not-applicable");
230250
+ const heuristicWarnings = applicableResults.filter(
229849
230251
  (result) => !result.passed && result.severity === "warning" && isHeuristicArchitectureSmellRuleId(result.id)
229850
230252
  ).length;
229851
- const dependencyWarnings = results.filter(
230253
+ const dependencyWarnings = applicableResults.filter(
229852
230254
  (result) => !result.passed && result.severity === "warning" && result.id.startsWith("AP-DEP-")
229853
230255
  ).length;
229854
- const databaseWarnings = results.filter(
230256
+ const databaseWarnings = applicableResults.filter(
229855
230257
  (result) => !result.passed && result.severity === "warning" && getValidationRuleMetadataById(result.id).category === "database"
229856
230258
  ).length;
229857
- const documentationWarnings = results.filter(
230259
+ const documentationWarnings = applicableResults.filter(
229858
230260
  (result) => !result.passed && result.severity === "warning" && getValidationRuleMetadataById(result.id).category === "documentation"
229859
230261
  ).length;
229860
- const blockingDependencyErrors = results.filter(
229861
- (result) => !result.passed && result.severity === "error" && (result.id === "AP-DEP-001" || result.id === "AP-DEP-008")
230262
+ const blockingDependencyErrors = problemSummary.clusters.filter(
230263
+ (cluster) => cluster.severity === "error" && cluster.ruleIds.some((ruleId) => ruleId === "AP-DEP-001" || ruleId === "AP-DEP-008")
229862
230264
  ).length;
229863
- const whyScoreChanged = score.errors > 0 ? "Error-level findings carry the highest score impact." : heuristicWarnings > 0 && dependencyWarnings > 0 ? "The score reflects mostly modest architecture-smell warnings plus remaining dependency governance warnings." : heuristicWarnings > 0 ? "The score reflects modest cumulative impact from heuristic architecture-smell warnings." : dependencyWarnings > 0 ? "The score reflects dependency governance warning impact." : score.warnings > 0 ? "The score reflects warning-level findings with no errors." : "No active findings reduced the score.";
230265
+ const whyScoreChanged = score.errors > 0 ? "Error-level findings carry the highest score impact, but repeated root-cause clusters are normalized before scoring." : heuristicWarnings > 0 && dependencyWarnings > 0 ? "The score reflects normalized warning clusters: capped architecture-review signals plus remaining dependency governance warnings." : heuristicWarnings > 0 ? "The score reflects capped cumulative impact from heuristic architecture-smell warning clusters." : dependencyWarnings > 0 ? "The score reflects dependency governance warning clusters after repeated-surface normalization." : score.warnings > 0 ? "The score reflects warning-level findings with volume normalization and diminishing returns for repeated problem families." : "No active findings reduced the score.";
229864
230266
  const largestScoreContributors = [];
229865
230267
  if (blockingDependencyErrors > 0) {
229866
230268
  largestScoreContributors.push(
229867
- "Circular dependency / blocking dependency violations have high score impact and can activate proportional scoring."
230269
+ `Circular dependency / blocking dependency problems remain high-impact, but related findings are grouped into ${blockingDependencyErrors} blocking cluster${blockingDependencyErrors === 1 ? "" : "s"}.`
229868
230270
  );
229869
230271
  }
229870
230272
  if (databaseWarnings > 0) {
229871
230273
  largestScoreContributors.push(
229872
- `Database/query risk volume is a major contributor (${databaseWarnings} warning${databaseWarnings === 1 ? "" : "s"}; database score ${score.categoryScores.database ?? 100}/100).`
230274
+ `Database/query risk volume is a major contributor (${databaseWarnings} warning${databaseWarnings === 1 ? "" : "s"} across ${problemSummary.clusters.filter((cluster) => cluster.category === "database").length} score cluster${problemSummary.clusters.filter((cluster) => cluster.category === "database").length === 1 ? "" : "s"}; database score ${score.categoryScores.database ?? 100}/100).`
229873
230275
  );
229874
230276
  }
229875
230277
  if (documentationWarnings > 0) {
@@ -229879,12 +230281,12 @@ function buildScoreExplanationSummary(results, score) {
229879
230281
  }
229880
230282
  if (heuristicWarnings > 0) {
229881
230283
  largestScoreContributors.push(
229882
- `Heuristic AP-APP/AP-DOM/AP-TXN findings are capped and did not dominate the score (${heuristicWarnings} warning${heuristicWarnings === 1 ? "" : "s"}).`
230284
+ `Heuristic AP-APP/AP-DOM/AP-TXN findings are capped and did not dominate the score (${heuristicWarnings} warning${heuristicWarnings === 1 ? "" : "s"} across ${problemSummary.heuristicClusterCount} heuristic cluster${problemSummary.heuristicClusterCount === 1 ? "" : "s"}).`
229883
230285
  );
229884
230286
  }
229885
230287
  if (score.warnings > 1) {
229886
230288
  largestScoreContributors.push(
229887
- "Fixing one warning may not visibly move the rounded score; grouped fixes such as dependency cycles or repeated DQR findings usually move it more."
230289
+ `Fixing one warning may not visibly move the rounded score; grouped fixes that clear repeated problem clusters (${problemSummary.qualityClusterCount} quality cluster${problemSummary.qualityClusterCount === 1 ? "" : "s"}) usually move it more.`
229888
230290
  );
229889
230291
  }
229890
230292
  return {
@@ -229954,6 +230356,13 @@ function buildDependencyDeductionKey(result) {
229954
230356
  ].join("|");
229955
230357
  }
229956
230358
  function classifyValidationResult(result) {
230359
+ if (result.applicability === "not-applicable") {
230360
+ return {
230361
+ kind: "guidance",
230362
+ scoreImpact: "guidance",
230363
+ deductionKey: `not-applicable:${result.id}`
230364
+ };
230365
+ }
229957
230366
  if (result.passed) {
229958
230367
  return {
229959
230368
  kind: "guidance",
@@ -230059,6 +230468,9 @@ function buildValidationStatusSummary(results, disabledCount = 0, suppressedCoun
230059
230468
  category: getValidationRuleMetadataById(result.id).category,
230060
230469
  severity: result.severity,
230061
230470
  passed: result.passed,
230471
+ applicability: result.applicability,
230472
+ ...result.sourceComponentId ? { componentId: result.sourceComponentId } : {},
230473
+ ...result.module ? { moduleId: result.module } : {},
230062
230474
  impact: classification.scoreImpact,
230063
230475
  deductionKey: classification.deductionKey
230064
230476
  };
@@ -230074,6 +230486,7 @@ function buildValidationStatusSummary(results, disabledCount = 0, suppressedCoun
230074
230486
  const scoreExplanation = buildScoreExplanationSummary(results, score);
230075
230487
  const base = {
230076
230488
  totalRulesEvaluated,
230489
+ notApplicable: score.notApplicable,
230077
230490
  disabled: disabledCount,
230078
230491
  passed,
230079
230492
  failed,
@@ -230089,6 +230502,8 @@ function buildValidationStatusSummary(results, disabledCount = 0, suppressedCoun
230089
230502
  info,
230090
230503
  healthScore: score.overallScore,
230091
230504
  categoryScores: score.categoryScores,
230505
+ ...score.componentScores ? { componentScores: score.componentScores } : {},
230506
+ ...score.moduleScores ? { moduleScores: score.moduleScores } : {},
230092
230507
  readinessScore: score.readinessScore,
230093
230508
  readinessCategoryScores: score.readinessCategoryScores,
230094
230509
  setupGaps: score.setupGaps,
@@ -230386,6 +230801,8 @@ function buildValidationStatusExport(config, results, validationConfig, suppress
230386
230801
  category: meta.category,
230387
230802
  severity: r.severity,
230388
230803
  passed: r.passed,
230804
+ ...r.applicability ? { applicability: r.applicability } : {},
230805
+ ...r.applicabilityReason ? { applicabilityReason: r.applicabilityReason } : {},
230389
230806
  message: r.message,
230390
230807
  kind: classification.kind,
230391
230808
  ...r.findingType ? { findingType: r.findingType } : {},
@@ -230403,7 +230820,14 @@ function buildValidationStatusExport(config, results, validationConfig, suppress
230403
230820
  ...r.exceptionTarget ? { exceptionTarget: r.exceptionTarget } : {},
230404
230821
  ...r.sourceFile ? { sourceFile: r.sourceFile } : {},
230405
230822
  ...r.sourceLine !== void 0 ? { sourceLine: r.sourceLine } : {},
230406
- ...r.sourceColumn !== void 0 ? { sourceColumn: r.sourceColumn } : {}
230823
+ ...r.sourceColumn !== void 0 ? { sourceColumn: r.sourceColumn } : {},
230824
+ ...r.dependencyType ? { dependencyType: r.dependencyType } : {},
230825
+ ...r.sourceComponentId ? { sourceComponentId: r.sourceComponentId } : {},
230826
+ ...r.sourceComponentName ? { sourceComponentName: r.sourceComponentName } : {},
230827
+ ...r.targetComponentId !== void 0 ? { targetComponentId: r.targetComponentId } : {},
230828
+ ...r.targetComponentName !== void 0 ? { targetComponentName: r.targetComponentName } : {},
230829
+ ...r.resolvedTargetPath !== void 0 ? { resolvedTargetPath: r.resolvedTargetPath } : {},
230830
+ ...r.isExternal !== void 0 ? { isExternal: r.isExternal } : {}
230407
230831
  };
230408
230832
  });
230409
230833
  return {
@@ -231000,37 +231424,38 @@ function dedupeDependencyFindings(findings) {
231000
231424
  }
231001
231425
  return deduped;
231002
231426
  }
231003
- async function validateModuleDependencies(workspaceRoot, contract, validationConfig, onDependencyConfigError, dependencyRulesConfigOverride) {
231427
+ async function validateModuleDependencies(workspaceRoot, contract, validationConfig, onDependencyConfigError, dependencyRulesConfigOverride, precomputedImports, hasArchitectureModulesOverride) {
231004
231428
  const modulesRootRelativePath = await getConfiguredModulesRoot(workspaceRoot, contract);
231429
+ const hasArchitectureModules = hasArchitectureModulesOverride ?? hasConfiguredArchitectureModules(contract);
231005
231430
  const dependencyRuleChecks = {
231006
231431
  modulesRootRelativePath,
231007
- checkCircularDependencies: !isRuleDisabled(
231432
+ checkCircularDependencies: hasArchitectureModules && !isRuleDisabled(
231008
231433
  ValidationRuleIds.DEP_CIRCULAR_MODULE_DEPENDENCY,
231009
231434
  validationConfig
231010
231435
  ),
231011
- checkForbiddenDependencies: !isRuleDisabled(
231436
+ checkForbiddenDependencies: hasArchitectureModules && !isRuleDisabled(
231012
231437
  ValidationRuleIds.DEP_FORBIDDEN_CROSS_MODULE_DEPENDENCY,
231013
231438
  validationConfig
231014
231439
  ),
231015
- checkNonPublicImports: !isRuleDisabled(
231440
+ checkNonPublicImports: hasArchitectureModules && !isRuleDisabled(
231016
231441
  ValidationRuleIds.DEP_NON_PUBLIC_CROSS_MODULE_IMPORT,
231017
231442
  validationConfig
231018
231443
  ),
231019
231444
  checkComponentDependencyPolicies: !isRuleDisabled(
231020
- ValidationRuleIds.DEP_LAYER_DIRECTION_VIOLATION,
231445
+ ValidationRuleIds.DEP_CROSS_COMPONENT_POLICY_VIOLATION,
231021
231446
  validationConfig
231022
231447
  )
231023
231448
  };
231024
231449
  const contractDependencyRuleChecks = {
231025
- checkDeclaredDependencies: !isRuleDisabled(
231450
+ checkDeclaredDependencies: hasArchitectureModules && !isRuleDisabled(
231026
231451
  ValidationRuleIds.DEP_CROSS_MODULE_DEPENDENCY_DECLARED_IN_CONTRACT,
231027
231452
  validationConfig
231028
231453
  ),
231029
- checkPublicSurfaceImports: !isRuleDisabled(
231454
+ checkPublicSurfaceImports: hasArchitectureModules && !isRuleDisabled(
231030
231455
  ValidationRuleIds.DEP_CROSS_MODULE_IMPORT_USES_PUBLIC_SURFACE_ONLY,
231031
231456
  validationConfig
231032
231457
  ),
231033
- checkDeclaredDependenciesUsedByCode: !isRuleDisabled(
231458
+ checkDeclaredDependenciesUsedByCode: hasArchitectureModules && !isRuleDisabled(
231034
231459
  ValidationRuleIds.DEP_DECLARED_MODULE_DEPENDENCY_NOT_USED,
231035
231460
  validationConfig
231036
231461
  )
@@ -231039,12 +231464,14 @@ async function validateModuleDependencies(workspaceRoot, contract, validationCon
231039
231464
  const [legacyFindings, contractFindings] = await Promise.all([
231040
231465
  validateDependencyBoundaries(workspaceRoot, {
231041
231466
  ...dependencyRuleChecks,
231042
- ...dependencyRulesConfigOverride ? { dependencyRulesConfigOverride } : {}
231467
+ ...dependencyRulesConfigOverride ? { dependencyRulesConfigOverride } : {},
231468
+ ...precomputedImports ? { precomputedImports } : {}
231043
231469
  }),
231044
231470
  validateDependencyContractBoundaries(workspaceRoot, contract, {
231045
231471
  ...contractDependencyRuleChecks,
231046
231472
  modulesRootRelativePath,
231047
- suppressPublicSurfaceFailuresForNonPublicImports: dependencyRuleChecks.checkNonPublicImports
231473
+ suppressPublicSurfaceFailuresForNonPublicImports: dependencyRuleChecks.checkNonPublicImports,
231474
+ ...precomputedImports ? { precomputedImports } : {}
231048
231475
  })
231049
231476
  ]);
231050
231477
  const findings = dedupeDependencyFindings([...legacyFindings, ...contractFindings]);
@@ -232365,6 +232792,7 @@ async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
232365
232792
  "Loading suppressions",
232366
232793
  () => readValidationSuppressions(workspaceRoot, knownRuleIds)
232367
232794
  );
232795
+ let hasArchitectureModules = hasConfiguredArchitectureModules(architectureContract);
232368
232796
  const results = [];
232369
232797
  results.push(...validationConfigLoad.configResults);
232370
232798
  if (projectConfigLoad.invalidJson) {
@@ -232384,6 +232812,32 @@ async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
232384
232812
  });
232385
232813
  }
232386
232814
  results.push(...suppressionsConfig.configResults);
232815
+ const configuredModulesRoot = await phase(
232816
+ "Discovering project modules",
232817
+ () => getConfiguredModulesRoot(
232818
+ workspaceRoot,
232819
+ architectureContract
232820
+ )
232821
+ );
232822
+ let precomputedDependencyImports;
232823
+ const dependencyGraphSummary = await phase(
232824
+ "Building dependency graph",
232825
+ () => buildDependencyGraphSummary(workspaceRoot, architectureContract, {
232826
+ modulesRootRelativePath: configuredModulesRoot,
232827
+ onCollectedImports: (imports) => {
232828
+ precomputedDependencyImports = imports;
232829
+ }
232830
+ })
232831
+ );
232832
+ hasArchitectureModules = hasArchitectureModules || dependencyGraphSummary.modules.length > 0;
232833
+ emitOperationalMessage(
232834
+ options?.onOperationalMessage,
232835
+ "info",
232836
+ `Dependency scan summary: modules=${dependencyGraphSummary.modules.length}, importEdges=${dependencyGraphSummary.modules.reduce(
232837
+ (total, moduleEntry) => total + moduleEntry.actualDependencies.length,
232838
+ 0
232839
+ )}, ignoredDefaults=node_modules,.git,.archpilot,dist,build,out,coverage,fixtures.`
232840
+ );
232387
232841
  const ruleExecutionResults = await phase(
232388
232842
  "Executing validation rules",
232389
232843
  () => Promise.all([
@@ -232397,11 +232851,11 @@ async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
232397
232851
  ),
232398
232852
  profileValidationPhase(
232399
232853
  "module contract registry analyzer",
232400
- () => validateModuleContractRegistryIntegrity(
232854
+ () => hasArchitectureModules ? validateModuleContractRegistryIntegrity(
232401
232855
  workspaceRoot,
232402
232856
  architectureContract,
232403
232857
  validationConfig
232404
- )
232858
+ ) : Promise.resolve([])
232405
232859
  ),
232406
232860
  profileValidationPhase(
232407
232861
  "module dependency analyzer",
@@ -232410,7 +232864,9 @@ async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
232410
232864
  architectureContract,
232411
232865
  validationConfig,
232412
232866
  options?.onDependencyConfigError,
232413
- projectConfigLoad.config?.policySource ? effectivePolicy.effectiveDependencyRulesConfig : void 0
232867
+ projectConfigLoad.config?.policySource ? effectivePolicy.effectiveDependencyRulesConfig : void 0,
232868
+ precomputedDependencyImports,
232869
+ hasArchitectureModules
232414
232870
  )
232415
232871
  ),
232416
232872
  profileValidationPhase(
@@ -232494,54 +232950,41 @@ async function runArchitectureValidationForWorkspace(workspaceRoot, options) {
232494
232950
  ])
232495
232951
  );
232496
232952
  results.push(...ruleExecutionResults.flat(), ...adrValidation.results);
232497
- const configuredModulesRoot = await phase(
232498
- "Discovering project modules",
232499
- () => getConfiguredModulesRoot(
232500
- workspaceRoot,
232501
- architectureContract
232502
- )
232503
- );
232504
- const dependencyGraphSummary = await phase(
232505
- "Building dependency graph",
232506
- () => buildDependencyGraphSummary(workspaceRoot, architectureContract, {
232507
- modulesRootRelativePath: configuredModulesRoot
232508
- })
232509
- );
232510
- emitOperationalMessage(
232511
- options?.onOperationalMessage,
232512
- "info",
232513
- `Dependency scan summary: modules=${dependencyGraphSummary.modules.length}, importEdges=${dependencyGraphSummary.modules.reduce(
232514
- (total, moduleEntry) => total + moduleEntry.actualDependencies.length,
232515
- 0
232516
- )}, ignoredDefaults=node_modules,.git,.archpilot,dist,build,out,coverage,fixtures.`
232517
- );
232518
- results.push(
232519
- ...await phase(
232520
- "Analyzing dependency graph rules",
232521
- () => validateDependencyLayerDirection(
232953
+ if (hasArchitectureModules) {
232954
+ results.push(
232955
+ ...await phase(
232956
+ "Analyzing dependency graph rules",
232957
+ () => validateDependencyLayerDirection(
232958
+ workspaceRoot,
232959
+ dependencyGraphSummary,
232960
+ validationConfig
232961
+ )
232962
+ ),
232963
+ ...validateTransitiveModuleDependencyCycles(
232964
+ dependencyGraphSummary,
232965
+ validationConfig
232966
+ ),
232967
+ ...validateHighInboundDependencyCount(
232968
+ architectureContract,
232969
+ dependencyGraphSummary,
232970
+ validationConfig
232971
+ ),
232972
+ ...await validateDependencyGraphIsolation(
232522
232973
  workspaceRoot,
232974
+ architectureContract,
232523
232975
  dependencyGraphSummary,
232524
232976
  validationConfig
232525
232977
  )
232526
- ),
232527
- ...validateTransitiveModuleDependencyCycles(
232528
- dependencyGraphSummary,
232529
- validationConfig
232530
- ),
232531
- ...validateHighInboundDependencyCount(
232532
- architectureContract,
232533
- dependencyGraphSummary,
232534
- validationConfig
232535
- ),
232536
- ...await validateDependencyGraphIsolation(
232537
- workspaceRoot,
232538
- architectureContract,
232539
- dependencyGraphSummary,
232540
- validationConfig
232541
- )
232542
- );
232978
+ );
232979
+ }
232543
232980
  const scopeModules = options?.moduleScope ? [...new Set(options.moduleScope)].sort((left, right) => left.localeCompare(right)) : [];
232544
- const effectiveResults = applyEffectiveSeverities(results, validationConfig.config);
232981
+ const applicableResults = applyRuleApplicability(
232982
+ results,
232983
+ architectureContract,
232984
+ validationConfig,
232985
+ hasArchitectureModules
232986
+ );
232987
+ const effectiveResults = applyEffectiveSeverities(applicableResults, validationConfig.config);
232545
232988
  const scopedResults = filterResultsByModuleScope(effectiveResults, scopeModules);
232546
232989
  const scopedResultsWithQuickFix = attachQuickFixMetadataToResults(scopedResults);
232547
232990
  const scopedDependencyGraphSummary = filterDependencyGraphSummaryByModuleScope(
@@ -232958,7 +233401,7 @@ function buildDefaultImpactAnalysisFromSignals(input2) {
232958
233401
  }
232959
233402
  };
232960
233403
  }
232961
- function getSeverityRank(severity) {
233404
+ function getSeverityRank2(severity) {
232962
233405
  switch (severity) {
232963
233406
  case "error":
232964
233407
  return 1;
@@ -233139,7 +233582,7 @@ function groupReviewFindingsByRule(findings) {
233139
233582
  return [...groups.values()];
233140
233583
  }
233141
233584
  function compareReviewFinding(left, right) {
233142
- const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
233585
+ const severityCompare = getSeverityRank2(left.severity) - getSeverityRank2(right.severity);
233143
233586
  if (severityCompare !== 0) {
233144
233587
  return severityCompare;
233145
233588
  }
@@ -233160,7 +233603,7 @@ function compareReviewFinding(left, right) {
233160
233603
  return left.message.localeCompare(right.message);
233161
233604
  }
233162
233605
  function compareSuppressedFinding(left, right) {
233163
- const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
233606
+ const severityCompare = getSeverityRank2(left.severity) - getSeverityRank2(right.severity);
233164
233607
  if (severityCompare !== 0) {
233165
233608
  return severityCompare;
233166
233609
  }
@@ -233476,6 +233919,19 @@ function buildArchitectureSmellSummary(findings) {
233476
233919
  };
233477
233920
  }
233478
233921
  function buildScoreExplanation(results, healthScore) {
233922
+ const problemSummary = summarizeArchitectureScoreProblems({
233923
+ results: results.map((result) => {
233924
+ const classification = classifyValidationResult(result);
233925
+ return {
233926
+ ruleId: result.id,
233927
+ category: getValidationRuleMetadataById(result.id).category,
233928
+ severity: result.severity,
233929
+ passed: result.passed,
233930
+ impact: classification.scoreImpact,
233931
+ deductionKey: classification.deductionKey
233932
+ };
233933
+ })
233934
+ });
233479
233935
  const heuristicWarnings = results.filter(
233480
233936
  (result) => !result.passed && result.severity === "warning" && isHeuristicArchitectureSmellRuleId2(result.id)
233481
233937
  ).length;
@@ -233488,20 +233944,21 @@ function buildScoreExplanation(results, healthScore) {
233488
233944
  const documentationWarnings = results.filter(
233489
233945
  (result) => !result.passed && result.severity === "warning" && getValidationRuleMetadataById(result.id).category === "documentation"
233490
233946
  ).length;
233491
- const blockingDependencyErrors = results.filter(
233492
- (result) => !result.passed && result.severity === "error" && (result.id === "AP-DEP-001" || result.id === "AP-DEP-008")
233947
+ const blockingDependencyErrors = problemSummary.clusters.filter(
233948
+ (cluster) => cluster.severity === "error" && cluster.ruleIds.some((ruleId) => ruleId === "AP-DEP-001" || ruleId === "AP-DEP-008")
233493
233949
  ).length;
233494
233950
  const highestImpactCategories = Object.entries(healthScore.categoryScores).sort((left, right) => left[1] - right[1] || left[0].localeCompare(right[0])).slice(0, 3).map(([category, score]) => ({ category, score }));
233495
- const whyScoreChanged = healthScore.errors > 0 ? "Error-level findings carry the highest score impact." : heuristicWarnings > 0 && dependencyWarnings > 0 ? "The score reflects mostly modest architecture-smell warnings plus remaining dependency governance warnings." : heuristicWarnings > 0 ? "The score reflects modest cumulative impact from heuristic architecture-smell warnings." : dependencyWarnings > 0 ? "The score reflects dependency governance warning impact." : healthScore.warnings > 0 ? "The score reflects warning-level findings with no errors." : "No active findings reduced the score.";
233951
+ const whyScoreChanged = healthScore.errors > 0 ? "Error-level findings carry the highest score impact, but repeated root-cause clusters are normalized before scoring." : heuristicWarnings > 0 && dependencyWarnings > 0 ? "The score reflects normalized warning clusters: capped architecture-review signals plus remaining dependency governance warnings." : heuristicWarnings > 0 ? "The score reflects capped cumulative impact from heuristic architecture-smell warning clusters." : dependencyWarnings > 0 ? "The score reflects dependency governance warning clusters after repeated-surface normalization." : healthScore.warnings > 0 ? "The score reflects warning-level findings with volume normalization and diminishing returns for repeated problem families." : "No active findings reduced the score.";
233496
233952
  const largestScoreContributors = [];
233497
233953
  if (blockingDependencyErrors > 0) {
233498
233954
  largestScoreContributors.push(
233499
- "Circular dependency / blocking dependency violations have high score impact and can activate proportional scoring."
233955
+ `Circular dependency / blocking dependency problems remain high-impact, but related findings are grouped into ${blockingDependencyErrors} blocking cluster${blockingDependencyErrors === 1 ? "" : "s"}.`
233500
233956
  );
233501
233957
  }
233502
233958
  if (databaseWarnings > 0) {
233959
+ const databaseClusters = problemSummary.clusters.filter((cluster) => cluster.category === "database").length;
233503
233960
  largestScoreContributors.push(
233504
- `Database/query risk volume is a major contributor (${databaseWarnings} warning${databaseWarnings === 1 ? "" : "s"}; database score ${healthScore.categoryScores.database ?? 100}/100).`
233961
+ `Database/query risk volume is a major contributor (${databaseWarnings} warning${databaseWarnings === 1 ? "" : "s"} across ${databaseClusters} score cluster${databaseClusters === 1 ? "" : "s"}; database score ${healthScore.categoryScores.database ?? 100}/100).`
233505
233962
  );
233506
233963
  }
233507
233964
  if (documentationWarnings > 0) {
@@ -233511,12 +233968,12 @@ function buildScoreExplanation(results, healthScore) {
233511
233968
  }
233512
233969
  if (heuristicWarnings > 0) {
233513
233970
  largestScoreContributors.push(
233514
- `Heuristic AP-APP/AP-DOM/AP-TXN findings are capped and did not dominate the score (${heuristicWarnings} warning${heuristicWarnings === 1 ? "" : "s"}).`
233971
+ `Heuristic AP-APP/AP-DOM/AP-TXN findings are capped and did not dominate the score (${heuristicWarnings} warning${heuristicWarnings === 1 ? "" : "s"} across ${problemSummary.heuristicClusterCount} heuristic cluster${problemSummary.heuristicClusterCount === 1 ? "" : "s"}).`
233515
233972
  );
233516
233973
  }
233517
233974
  if (healthScore.warnings > 1) {
233518
233975
  largestScoreContributors.push(
233519
- "Fixing one warning may not visibly move the rounded score; grouped fixes such as dependency cycles or repeated DQR findings usually move it more."
233976
+ `Fixing one warning may not visibly move the rounded score; grouped fixes that clear repeated problem clusters (${problemSummary.qualityClusterCount} quality cluster${problemSummary.qualityClusterCount === 1 ? "" : "s"}) usually move it more.`
233520
233977
  );
233521
233978
  }
233522
233979
  return {