@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.
- package/dist/archpilot-cli.js +683 -226
- package/package.json +1 -1
package/dist/archpilot-cli.js
CHANGED
|
@@ -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
|
-
|
|
214122
|
-
|
|
214123
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
216335
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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()],
|
|
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 =
|
|
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 =
|
|
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
|
|
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) :
|
|
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
|
|
222276
|
-
|
|
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 =
|
|
222279
|
-
(
|
|
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 -=
|
|
222647
|
+
adjusted -= 4;
|
|
222286
222648
|
} else if (governanceRiskLevel === "high") {
|
|
222287
|
-
adjusted -=
|
|
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,
|
|
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
|
|
222438
|
-
for (const result of
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
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 =
|
|
230253
|
+
const dependencyWarnings = applicableResults.filter(
|
|
229852
230254
|
(result) => !result.passed && result.severity === "warning" && result.id.startsWith("AP-DEP-")
|
|
229853
230255
|
).length;
|
|
229854
|
-
const databaseWarnings =
|
|
230256
|
+
const databaseWarnings = applicableResults.filter(
|
|
229855
230257
|
(result) => !result.passed && result.severity === "warning" && getValidationRuleMetadataById(result.id).category === "database"
|
|
229856
230258
|
).length;
|
|
229857
|
-
const documentationWarnings =
|
|
230259
|
+
const documentationWarnings = applicableResults.filter(
|
|
229858
230260
|
(result) => !result.passed && result.severity === "warning" && getValidationRuleMetadataById(result.id).category === "documentation"
|
|
229859
230261
|
).length;
|
|
229860
|
-
const blockingDependencyErrors =
|
|
229861
|
-
(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
232498
|
-
|
|
232499
|
-
|
|
232500
|
-
|
|
232501
|
-
|
|
232502
|
-
|
|
232503
|
-
|
|
232504
|
-
|
|
232505
|
-
|
|
232506
|
-
|
|
232507
|
-
|
|
232508
|
-
|
|
232509
|
-
|
|
232510
|
-
|
|
232511
|
-
|
|
232512
|
-
|
|
232513
|
-
|
|
232514
|
-
|
|
232515
|
-
|
|
232516
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
233492
|
-
(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|