@archpilotlabs/archpilot 0.2.2 → 0.2.3
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 +516 -105
- package/dist/cli/src/archpilot-cli.js +5523 -0
- package/dist/cli/src/archpilot-cli.js.map +1 -0
- package/dist/core/src/adr/adrLoader.js +97 -0
- package/dist/core/src/adr/adrLoader.js.map +1 -0
- package/dist/core/src/adr/adrParser.js +77 -0
- package/dist/core/src/adr/adrParser.js.map +1 -0
- package/dist/core/src/adr/adrTemplate.js +45 -0
- package/dist/core/src/adr/adrTemplate.js.map +1 -0
- package/dist/core/src/adr/adrTypes.js +3 -0
- package/dist/core/src/adr/adrTypes.js.map +1 -0
- package/dist/core/src/adr/adrValidation.js +160 -0
- package/dist/core/src/adr/adrValidation.js.map +1 -0
- package/dist/core/src/adr/index.js +9 -0
- package/dist/core/src/adr/index.js.map +1 -0
- package/dist/core/src/adrManagement.js +174 -0
- package/dist/core/src/adrManagement.js.map +1 -0
- package/dist/core/src/adrRequirementStatus.js +62 -0
- package/dist/core/src/adrRequirementStatus.js.map +1 -0
- package/dist/core/src/adrScaffoldGeneration.js +325 -0
- package/dist/core/src/adrScaffoldGeneration.js.map +1 -0
- package/dist/core/src/apiDesignAnalysis.js +869 -0
- package/dist/core/src/apiDesignAnalysis.js.map +1 -0
- package/dist/core/src/applicationLayeringAnalysis.js +920 -0
- package/dist/core/src/applicationLayeringAnalysis.js.map +1 -0
- package/dist/core/src/applyRulePack.js +117 -0
- package/dist/core/src/applyRulePack.js.map +1 -0
- package/dist/core/src/architectureBaseline.js +271 -0
- package/dist/core/src/architectureBaseline.js.map +1 -0
- package/dist/core/src/architectureComponentDiscoveryDefaults.js +139 -0
- package/dist/core/src/architectureComponentDiscoveryDefaults.js.map +1 -0
- package/dist/core/src/architectureDependencyContext.js +316 -0
- package/dist/core/src/architectureDependencyContext.js.map +1 -0
- package/dist/core/src/architectureDriftDetection.js +96 -0
- package/dist/core/src/architectureDriftDetection.js.map +1 -0
- package/dist/core/src/architectureDriftTimeline.js +221 -0
- package/dist/core/src/architectureDriftTimeline.js.map +1 -0
- package/dist/core/src/architectureGovernanceBaseline.js +469 -0
- package/dist/core/src/architectureGovernanceBaseline.js.map +1 -0
- package/dist/core/src/architectureHealthHistory.js +208 -0
- package/dist/core/src/architectureHealthHistory.js.map +1 -0
- package/dist/core/src/architectureHealthScore.js +657 -0
- package/dist/core/src/architectureHealthScore.js.map +1 -0
- package/dist/core/src/architectureInit.js +826 -0
- package/dist/core/src/architectureInit.js.map +1 -0
- package/dist/core/src/architectureMap.js +727 -0
- package/dist/core/src/architectureMap.js.map +1 -0
- package/dist/core/src/architectureMigrationPreview.js +467 -0
- package/dist/core/src/architectureMigrationPreview.js.map +1 -0
- package/dist/core/src/architectureModelRefresh.js +209 -0
- package/dist/core/src/architectureModelRefresh.js.map +1 -0
- package/dist/core/src/architectureReportWorkflow.js +139 -0
- package/dist/core/src/architectureReportWorkflow.js.map +1 -0
- package/dist/core/src/architectureValidation.js +3879 -0
- package/dist/core/src/architectureValidation.js.map +1 -0
- package/dist/core/src/architectureWorkspaceModel.js +166 -0
- package/dist/core/src/architectureWorkspaceModel.js.map +1 -0
- package/dist/core/src/authorizationTenantAnalysis.js +652 -0
- package/dist/core/src/authorizationTenantAnalysis.js.map +1 -0
- package/dist/core/src/bootstrapArchitectureConfig.js +150 -0
- package/dist/core/src/bootstrapArchitectureConfig.js.map +1 -0
- package/dist/core/src/changeRisk/changeRiskAssessment.js +251 -0
- package/dist/core/src/changeRisk/changeRiskAssessment.js.map +1 -0
- package/dist/core/src/changeRisk/changeRiskTypes.js +3 -0
- package/dist/core/src/changeRisk/changeRiskTypes.js.map +1 -0
- package/dist/core/src/changeRisk/index.js +6 -0
- package/dist/core/src/changeRisk/index.js.map +1 -0
- package/dist/core/src/ci/githubInlineAnnotations.js +245 -0
- package/dist/core/src/ci/githubInlineAnnotations.js.map +1 -0
- package/dist/core/src/ci/githubPrCommentAdapter.js +232 -0
- package/dist/core/src/ci/githubPrCommentAdapter.js.map +1 -0
- package/dist/core/src/ciWorkflowTemplates.js +1042 -0
- package/dist/core/src/ciWorkflowTemplates.js.map +1 -0
- package/dist/core/src/compliance/complianceExportBuilder.js +122 -0
- package/dist/core/src/compliance/complianceExportBuilder.js.map +1 -0
- package/dist/core/src/compliance/complianceExportTypes.js +3 -0
- package/dist/core/src/compliance/complianceExportTypes.js.map +1 -0
- package/dist/core/src/compliance/complianceExportWriter.js +48 -0
- package/dist/core/src/compliance/complianceExportWriter.js.map +1 -0
- package/dist/core/src/compliance/complianceRulePackResolver.js +62 -0
- package/dist/core/src/compliance/complianceRulePackResolver.js.map +1 -0
- package/dist/core/src/config/index.js +17 -0
- package/dist/core/src/config/index.js.map +1 -0
- package/dist/core/src/config/projectConfig.js +224 -0
- package/dist/core/src/config/projectConfig.js.map +1 -0
- package/dist/core/src/config/projectIdentity.js +160 -0
- package/dist/core/src/config/projectIdentity.js.map +1 -0
- package/dist/core/src/dataQueryRiskDetection.js +1203 -0
- package/dist/core/src/dataQueryRiskDetection.js.map +1 -0
- package/dist/core/src/dependencyValidation.js +2557 -0
- package/dist/core/src/dependencyValidation.js.map +1 -0
- package/dist/core/src/discoverAdrs.js +15 -0
- package/dist/core/src/discoverAdrs.js.map +1 -0
- package/dist/core/src/drift/driftComparator.js +49 -0
- package/dist/core/src/drift/driftComparator.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotLoader.js +136 -0
- package/dist/core/src/drift/driftSnapshotLoader.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotTypes.js +3 -0
- package/dist/core/src/drift/driftSnapshotTypes.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotWriter.js +48 -0
- package/dist/core/src/drift/driftSnapshotWriter.js.map +1 -0
- package/dist/core/src/drift/index.js +6 -0
- package/dist/core/src/drift/index.js.map +1 -0
- package/dist/core/src/eventArchitectureAnalysis.js +925 -0
- package/dist/core/src/eventArchitectureAnalysis.js.map +1 -0
- package/dist/core/src/findingTargetResolution.js +117 -0
- package/dist/core/src/findingTargetResolution.js.map +1 -0
- package/dist/core/src/generateArchitectureDiagrams.js +57 -0
- package/dist/core/src/generateArchitectureDiagrams.js.map +1 -0
- package/dist/core/src/generateImpactAnalysis.js +366 -0
- package/dist/core/src/generateImpactAnalysis.js.map +1 -0
- package/dist/core/src/generateOnboardingDocs.js +262 -0
- package/dist/core/src/generateOnboardingDocs.js.map +1 -0
- package/dist/core/src/governance/cloudConnection.js +203 -0
- package/dist/core/src/governance/cloudConnection.js.map +1 -0
- package/dist/core/src/governance/governanceContracts.js +129 -0
- package/dist/core/src/governance/governanceContracts.js.map +1 -0
- package/dist/core/src/governance/governanceGitMetadata.js +78 -0
- package/dist/core/src/governance/governanceGitMetadata.js.map +1 -0
- package/dist/core/src/governance/governanceUploadClient.js +119 -0
- package/dist/core/src/governance/governanceUploadClient.js.map +1 -0
- package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js +155 -0
- package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js.map +1 -0
- package/dist/core/src/governance/governanceUploadPayload.js +439 -0
- package/dist/core/src/governance/governanceUploadPayload.js.map +1 -0
- package/dist/core/src/governance/runGovernanceUpload.js +103 -0
- package/dist/core/src/governance/runGovernanceUpload.js.map +1 -0
- package/dist/core/src/impactAnalysisEngine.js +376 -0
- package/dist/core/src/impactAnalysisEngine.js.map +1 -0
- package/dist/core/src/initializationState.js +99 -0
- package/dist/core/src/initializationState.js.map +1 -0
- package/dist/core/src/managedArtifacts.js +151 -0
- package/dist/core/src/managedArtifacts.js.map +1 -0
- package/dist/core/src/moduleContractValidation.js +266 -0
- package/dist/core/src/moduleContractValidation.js.map +1 -0
- package/dist/core/src/moduleContractsGenerator.js +337 -0
- package/dist/core/src/moduleContractsGenerator.js.map +1 -0
- package/dist/core/src/moduleDiscovery.js +416 -0
- package/dist/core/src/moduleDiscovery.js.map +1 -0
- package/dist/core/src/moduleScope.js +426 -0
- package/dist/core/src/moduleScope.js.map +1 -0
- package/dist/core/src/onboardingGuidance.js +74 -0
- package/dist/core/src/onboardingGuidance.js.map +1 -0
- package/dist/core/src/operationalLogging.js +7 -0
- package/dist/core/src/operationalLogging.js.map +1 -0
- package/dist/core/src/packageVersion.js +52 -0
- package/dist/core/src/packageVersion.js.map +1 -0
- package/dist/core/src/policy/effectivePolicyResolver.js +91 -0
- package/dist/core/src/policy/effectivePolicyResolver.js.map +1 -0
- package/dist/core/src/policy/exceptionLoader.js +285 -0
- package/dist/core/src/policy/exceptionLoader.js.map +1 -0
- package/dist/core/src/policy/remotePolicyLoader.js +227 -0
- package/dist/core/src/policy/remotePolicyLoader.js.map +1 -0
- package/dist/core/src/policy/remotePolicyTypes.js +3 -0
- package/dist/core/src/policy/remotePolicyTypes.js.map +1 -0
- package/dist/core/src/policy/riskPolicyConfig.js +206 -0
- package/dist/core/src/policy/riskPolicyConfig.js.map +1 -0
- package/dist/core/src/policy/riskPolicyEnforcement.js +203 -0
- package/dist/core/src/policy/riskPolicyEnforcement.js.map +1 -0
- package/dist/core/src/policy/riskPolicyExitCode.js +16 -0
- package/dist/core/src/policy/riskPolicyExitCode.js.map +1 -0
- package/dist/core/src/policy/riskPolicyInitializer.js +90 -0
- package/dist/core/src/policy/riskPolicyInitializer.js.map +1 -0
- package/dist/core/src/quickFix/quickFixEngine.js +132 -0
- package/dist/core/src/quickFix/quickFixEngine.js.map +1 -0
- package/dist/core/src/quickFix/quickFixMessaging.js +15 -0
- package/dist/core/src/quickFix/quickFixMessaging.js.map +1 -0
- package/dist/core/src/quickFix/quickFixProviders.js +87 -0
- package/dist/core/src/quickFix/quickFixProviders.js.map +1 -0
- package/dist/core/src/quickFix/quickFixTypes.js +3 -0
- package/dist/core/src/quickFix/quickFixTypes.js.map +1 -0
- package/dist/core/src/remediation/fileHelpers.js +86 -0
- package/dist/core/src/remediation/fileHelpers.js.map +1 -0
- package/dist/core/src/remediation/fixRegistry.js +645 -0
- package/dist/core/src/remediation/fixRegistry.js.map +1 -0
- package/dist/core/src/remediation/index.js +8 -0
- package/dist/core/src/remediation/index.js.map +1 -0
- package/dist/core/src/remediation/remediationEngine.js +207 -0
- package/dist/core/src/remediation/remediationEngine.js.map +1 -0
- package/dist/core/src/reporting/adrReporter.js +59 -0
- package/dist/core/src/reporting/adrReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureBaselineReporter.js +64 -0
- package/dist/core/src/reporting/architectureBaselineReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureDiagramReporter.js +148 -0
- package/dist/core/src/reporting/architectureDiagramReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureDriftTimelineReporter.js +44 -0
- package/dist/core/src/reporting/architectureDriftTimelineReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureHealthHistoryReporter.js +51 -0
- package/dist/core/src/reporting/architectureHealthHistoryReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureMapReporter.js +228 -0
- package/dist/core/src/reporting/architectureMapReporter.js.map +1 -0
- package/dist/core/src/reporting/architecturePrCommentReporter.js +324 -0
- package/dist/core/src/reporting/architecturePrCommentReporter.js.map +1 -0
- package/dist/core/src/reporting/architecturePrReviewReporter.js +389 -0
- package/dist/core/src/reporting/architecturePrReviewReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureReportArtifacts.js +166 -0
- package/dist/core/src/reporting/architectureReportArtifacts.js.map +1 -0
- package/dist/core/src/reporting/architectureReviewReportGenerator.js +382 -0
- package/dist/core/src/reporting/architectureReviewReportGenerator.js.map +1 -0
- package/dist/core/src/reporting/architectureReviewReporter.js +1633 -0
- package/dist/core/src/reporting/architectureReviewReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureRiskAssessment.js +101 -0
- package/dist/core/src/reporting/architectureRiskAssessment.js.map +1 -0
- package/dist/core/src/reporting/architectureScoreBadge.js +177 -0
- package/dist/core/src/reporting/architectureScoreBadge.js.map +1 -0
- package/dist/core/src/reporting/architectureSupplementalReporters.js +124 -0
- package/dist/core/src/reporting/architectureSupplementalReporters.js.map +1 -0
- package/dist/core/src/reporting/changeRiskAssessment.js +6 -0
- package/dist/core/src/reporting/changeRiskAssessment.js.map +1 -0
- package/dist/core/src/reporting/changeRiskTypes.js +3 -0
- package/dist/core/src/reporting/changeRiskTypes.js.map +1 -0
- package/dist/core/src/reporting/impactAnalysisReporter.js +111 -0
- package/dist/core/src/reporting/impactAnalysisReporter.js.map +1 -0
- package/dist/core/src/reporting/onboardingDocsReporter.js +113 -0
- package/dist/core/src/reporting/onboardingDocsReporter.js.map +1 -0
- package/dist/core/src/reporting/rulePackReporter.js +61 -0
- package/dist/core/src/reporting/rulePackReporter.js.map +1 -0
- package/dist/core/src/reporting/safetyControlsDetection.js +343 -0
- package/dist/core/src/reporting/safetyControlsDetection.js.map +1 -0
- package/dist/core/src/resolveRuleAdrs.js +31 -0
- package/dist/core/src/resolveRuleAdrs.js.map +1 -0
- package/dist/core/src/reviewGenerationWorkflow.js +220 -0
- package/dist/core/src/reviewGenerationWorkflow.js.map +1 -0
- package/dist/core/src/rulePacks/index.js +17 -0
- package/dist/core/src/rulePacks/index.js.map +1 -0
- package/dist/core/src/rulePacks/layeredMonolithRulePack.js +45 -0
- package/dist/core/src/rulePacks/layeredMonolithRulePack.js.map +1 -0
- package/dist/core/src/rulePacks/modularMonolithRulePack.js +41 -0
- package/dist/core/src/rulePacks/modularMonolithRulePack.js.map +1 -0
- package/dist/core/src/rulePacks/types.js +3 -0
- package/dist/core/src/rulePacks/types.js.map +1 -0
- package/dist/core/src/setupNextSteps.js +939 -0
- package/dist/core/src/setupNextSteps.js.map +1 -0
- package/dist/core/src/setupRefresh.js +152 -0
- package/dist/core/src/setupRefresh.js.map +1 -0
- package/dist/core/src/smartInit/apiDbHintDetection.js +836 -0
- package/dist/core/src/smartInit/apiDbHintDetection.js.map +1 -0
- package/dist/core/src/smartInit/architectureProposalBuilder.js +1215 -0
- package/dist/core/src/smartInit/architectureProposalBuilder.js.map +1 -0
- package/dist/core/src/smartInit/architectureStyleDetection.js +472 -0
- package/dist/core/src/smartInit/architectureStyleDetection.js.map +1 -0
- package/dist/core/src/smartInit/archpilotIgnore.js +139 -0
- package/dist/core/src/smartInit/archpilotIgnore.js.map +1 -0
- package/dist/core/src/smartInit/componentDetection.js +1084 -0
- package/dist/core/src/smartInit/componentDetection.js.map +1 -0
- package/dist/core/src/smartInit/implementationProfileResolution.js +53 -0
- package/dist/core/src/smartInit/implementationProfileResolution.js.map +1 -0
- package/dist/core/src/smartInit/index.js +31 -0
- package/dist/core/src/smartInit/index.js.map +1 -0
- package/dist/core/src/smartInit/moduleDetection.js +982 -0
- package/dist/core/src/smartInit/moduleDetection.js.map +1 -0
- package/dist/core/src/smartInit/projectKindDetection.js +437 -0
- package/dist/core/src/smartInit/projectKindDetection.js.map +1 -0
- package/dist/core/src/smartInit/repoTopologyDetection.js +470 -0
- package/dist/core/src/smartInit/repoTopologyDetection.js.map +1 -0
- package/dist/core/src/smartInit/runContext.js +112 -0
- package/dist/core/src/smartInit/runContext.js.map +1 -0
- package/dist/core/src/smartInit/smartInitEngine.js +94 -0
- package/dist/core/src/smartInit/smartInitEngine.js.map +1 -0
- package/dist/core/src/smartInit/stackDetection.js +503 -0
- package/dist/core/src/smartInit/stackDetection.js.map +1 -0
- package/dist/core/src/smartInit/toolingHintDetection.js +345 -0
- package/dist/core/src/smartInit/toolingHintDetection.js.map +1 -0
- package/dist/core/src/sourceScopeClassification.js +83 -0
- package/dist/core/src/sourceScopeClassification.js.map +1 -0
- package/dist/core/src/stackAdapters.js +2475 -0
- package/dist/core/src/stackAdapters.js.map +1 -0
- package/dist/core/src/stackMetadata.js +157 -0
- package/dist/core/src/stackMetadata.js.map +1 -0
- package/dist/core/src/suppressions/index.js +3 -0
- package/dist/core/src/suppressions/index.js.map +1 -0
- package/dist/core/src/suppressions/suppressionReporter.js +38 -0
- package/dist/core/src/suppressions/suppressionReporter.js.map +1 -0
- package/dist/core/src/suppressions/suppressionTypes.js +3 -0
- package/dist/core/src/suppressions/suppressionTypes.js.map +1 -0
- package/dist/core/src/suppressions/suppressionValidator.js +119 -0
- package/dist/core/src/suppressions/suppressionValidator.js.map +1 -0
- package/dist/core/src/transactionDomainAnalysis.js +1416 -0
- package/dist/core/src/transactionDomainAnalysis.js.map +1 -0
- package/dist/core/src/validation/changedFilesResolver.js +165 -0
- package/dist/core/src/validation/changedFilesResolver.js.map +1 -0
- package/dist/core/src/validationProfiling.js +33 -0
- package/dist/core/src/validationProfiling.js.map +1 -0
- package/dist/core/src/validationRuleExplanations.js +69 -0
- package/dist/core/src/validationRuleExplanations.js.map +1 -0
- package/dist/core/src/validationSourceInventory.js +200 -0
- package/dist/core/src/validationSourceInventory.js.map +1 -0
- package/dist/core/src/validationSuppressions.js +231 -0
- package/dist/core/src/validationSuppressions.js.map +1 -0
- package/dist/core/src/validationTypeScriptAstCache.js +82 -0
- package/dist/core/src/validationTypeScriptAstCache.js.map +1 -0
- package/dist/core/src/validationWorkflow.js +222 -0
- package/dist/core/src/validationWorkflow.js.map +1 -0
- package/dist/core/src/workspaceRoot.js +68 -0
- package/dist/core/src/workspaceRoot.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1633 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAdrRequirementDisplay = resolveAdrRequirementDisplay;
|
|
4
|
+
exports.buildArchitectureReviewComponentSummary = buildArchitectureReviewComponentSummary;
|
|
5
|
+
exports.buildArchitectureReviewSummary = buildArchitectureReviewSummary;
|
|
6
|
+
exports.renderArchitectureReviewConsole = renderArchitectureReviewConsole;
|
|
7
|
+
exports.renderArchitectureReviewMarkdown = renderArchitectureReviewMarkdown;
|
|
8
|
+
exports.renderArchitectureReviewJson = renderArchitectureReviewJson;
|
|
9
|
+
exports.renderArchitectureReviewCompactMarkdown = renderArchitectureReviewCompactMarkdown;
|
|
10
|
+
const architectureValidation_1 = require("../architectureValidation");
|
|
11
|
+
const validationRuleExplanations_1 = require("../validationRuleExplanations");
|
|
12
|
+
const resolveRuleAdrs_1 = require("../resolveRuleAdrs");
|
|
13
|
+
const architectureHealthScore_1 = require("../architectureHealthScore");
|
|
14
|
+
const architectureHealthHistoryReporter_1 = require("./architectureHealthHistoryReporter");
|
|
15
|
+
const changeRiskAssessment_1 = require("./changeRiskAssessment");
|
|
16
|
+
const architectureRiskAssessment_1 = require("./architectureRiskAssessment");
|
|
17
|
+
function describeModuleRole(modulePath) {
|
|
18
|
+
const normalized = (modulePath ?? '').replace(/\\/g, '/').toLowerCase();
|
|
19
|
+
if (normalized.startsWith('apps/') ||
|
|
20
|
+
normalized.includes('/apps/') ||
|
|
21
|
+
normalized.startsWith('app/') ||
|
|
22
|
+
normalized.includes('/app/')) {
|
|
23
|
+
return 'application boundary';
|
|
24
|
+
}
|
|
25
|
+
if (normalized.startsWith('services/') ||
|
|
26
|
+
normalized.includes('/services/') ||
|
|
27
|
+
normalized.startsWith('service/') ||
|
|
28
|
+
normalized.includes('/service/')) {
|
|
29
|
+
return 'service boundary';
|
|
30
|
+
}
|
|
31
|
+
if (normalized.startsWith('packages/') ||
|
|
32
|
+
normalized.includes('/packages/') ||
|
|
33
|
+
normalized.startsWith('libs/') ||
|
|
34
|
+
normalized.includes('/libs/') ||
|
|
35
|
+
normalized.includes('/shared/') ||
|
|
36
|
+
normalized.includes('/platform/')) {
|
|
37
|
+
return 'library boundary';
|
|
38
|
+
}
|
|
39
|
+
if (normalized.includes('/features/') || normalized.includes('/modules/')) {
|
|
40
|
+
return 'feature boundary';
|
|
41
|
+
}
|
|
42
|
+
return 'architecture boundary';
|
|
43
|
+
}
|
|
44
|
+
function buildModuleCatalog(dependencyGraphSummary, modulePaths) {
|
|
45
|
+
if (!dependencyGraphSummary) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return dependencyGraphSummary.modules
|
|
49
|
+
.map((moduleSummary) => {
|
|
50
|
+
const modulePath = modulePaths?.[moduleSummary.module];
|
|
51
|
+
return {
|
|
52
|
+
moduleName: moduleSummary.module,
|
|
53
|
+
...(modulePath ? { path: modulePath } : {}),
|
|
54
|
+
role: describeModuleRole(modulePath),
|
|
55
|
+
declaredDependencyCount: moduleSummary.declaredDependencies.length,
|
|
56
|
+
actualDependencyCount: moduleSummary.actualDependencies.length,
|
|
57
|
+
inboundDependencyCount: moduleSummary.inboundActualFrom.length,
|
|
58
|
+
hasContract: moduleSummary.hasContract,
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
.sort((left, right) => left.moduleName.localeCompare(right.moduleName));
|
|
62
|
+
}
|
|
63
|
+
function buildRecommendedNextActions(summary) {
|
|
64
|
+
const actions = [];
|
|
65
|
+
const topBlocking = summary.findings.find((finding) => finding.severity === 'error');
|
|
66
|
+
if (topBlocking) {
|
|
67
|
+
actions.push(`Resolve ${topBlocking.id} first to reduce blocking architecture risk.`);
|
|
68
|
+
}
|
|
69
|
+
if (summary.setupGaps > 0) {
|
|
70
|
+
actions.push('Address setup gaps before deeper governance review so the architecture contract is complete.');
|
|
71
|
+
}
|
|
72
|
+
if (summary.adrRequirement.required) {
|
|
73
|
+
actions.push(summary.adrRequirement.suggestedCommand
|
|
74
|
+
? `Document the change with ADR coverage: ${summary.adrRequirement.suggestedCommand}`
|
|
75
|
+
: 'Document the change with ADR coverage for the current policy outcome.');
|
|
76
|
+
}
|
|
77
|
+
if ((summary.safetyControls?.findings.length ?? 0) > 0) {
|
|
78
|
+
actions.push('Add the missing safety-control evidence before final governance review.');
|
|
79
|
+
}
|
|
80
|
+
if (actions.length === 0) {
|
|
81
|
+
actions.push('No immediate architecture remediation actions are required.');
|
|
82
|
+
}
|
|
83
|
+
return actions.slice(0, 4);
|
|
84
|
+
}
|
|
85
|
+
const reviewReportVersion = 1;
|
|
86
|
+
const compactTopFindingsLimit = 5;
|
|
87
|
+
const topIssuesLimit = 10;
|
|
88
|
+
const validationSummaryDivider = '----------------------------------------';
|
|
89
|
+
const architectureReviewSignalRulePrefixes = ['AP-APP-', 'AP-DOM-', 'AP-TXN-'];
|
|
90
|
+
const reviewCategoryLabels = {
|
|
91
|
+
architecture: 'Architecture',
|
|
92
|
+
api: 'API',
|
|
93
|
+
database: 'Database',
|
|
94
|
+
dependency: 'Dependency',
|
|
95
|
+
documentation: 'Documentation',
|
|
96
|
+
security: 'Security',
|
|
97
|
+
};
|
|
98
|
+
function buildDefaultImpactAnalysisFromSignals(input) {
|
|
99
|
+
const changedModules = [...(input?.changedModules ?? [])].sort((left, right) => left.localeCompare(right));
|
|
100
|
+
const directDependents = [...(input?.directlyAffectedModules ?? [])].sort((left, right) => left.localeCompare(right));
|
|
101
|
+
const transitiveDependents = [...(input?.transitivelyAffectedModules ?? [])].sort((left, right) => left.localeCompare(right));
|
|
102
|
+
const impactedContracts = [...(input?.touchedContracts ?? [])].sort((left, right) => left.localeCompare(right));
|
|
103
|
+
const impactedPublicEntrypoints = [...(input?.touchedPublicEntrypoints ?? [])].sort((left, right) => left.localeCompare(right));
|
|
104
|
+
const impactedAdrs = [...(input?.touchedAdrs ?? [])].sort((left, right) => left.localeCompare(right));
|
|
105
|
+
const impactedArchitectureArtifacts = [...(input?.touchedArchitectureArtifacts ?? [])].sort((left, right) => left.localeCompare(right));
|
|
106
|
+
const totalAffectedModules = [...new Set([...directDependents, ...transitiveDependents])].length;
|
|
107
|
+
const governanceSurfaceCount = impactedContracts.length +
|
|
108
|
+
impactedPublicEntrypoints.length +
|
|
109
|
+
impactedAdrs.length +
|
|
110
|
+
impactedArchitectureArtifacts.length;
|
|
111
|
+
const maxTransitiveChainDepth = transitiveDependents.length > 0 ? 2 : 0;
|
|
112
|
+
const impactRadiusScore = changedModules.length * 2 +
|
|
113
|
+
directDependents.length * 3 +
|
|
114
|
+
transitiveDependents.length * 2 +
|
|
115
|
+
governanceSurfaceCount * 2 +
|
|
116
|
+
Math.max(0, maxTransitiveChainDepth - 2);
|
|
117
|
+
return {
|
|
118
|
+
changedModules,
|
|
119
|
+
directDependents,
|
|
120
|
+
transitiveDependents,
|
|
121
|
+
totalAffectedModules,
|
|
122
|
+
impactRadiusScore,
|
|
123
|
+
impactLevel: impactRadiusScore <= 6 ? 'LOW' : impactRadiusScore <= 14 ? 'MEDIUM' : 'HIGH',
|
|
124
|
+
summary: `Changed=${changedModules.length}, Direct=${directDependents.length}, ` +
|
|
125
|
+
`Transitive=${transitiveDependents.length}, GovernanceSurfaces=${governanceSurfaceCount}.`,
|
|
126
|
+
affectedSurface: {
|
|
127
|
+
changedModules,
|
|
128
|
+
directlyAffectedModules: directDependents,
|
|
129
|
+
transitivelyAffectedModules: transitiveDependents,
|
|
130
|
+
impactedContracts,
|
|
131
|
+
impactedPublicEntrypoints,
|
|
132
|
+
impactedAdrs,
|
|
133
|
+
impactedArchitectureArtifacts,
|
|
134
|
+
},
|
|
135
|
+
directDependencyChains: directDependents.map((moduleName) => ({
|
|
136
|
+
module: moduleName,
|
|
137
|
+
chain: ['(changed-module)', moduleName],
|
|
138
|
+
})),
|
|
139
|
+
transitiveDependencyChains: transitiveDependents.map((moduleName) => ({
|
|
140
|
+
module: moduleName,
|
|
141
|
+
chain: ['(changed-module)', '(direct-dependent)', moduleName],
|
|
142
|
+
})),
|
|
143
|
+
factors: [],
|
|
144
|
+
reasoning: [],
|
|
145
|
+
scoring: {
|
|
146
|
+
modelVersion: 1,
|
|
147
|
+
formula: 'score = (changed*2) + (direct*3) + (transitive*2) + (governance*2) + max(0, depth-2)',
|
|
148
|
+
components: {
|
|
149
|
+
changedModules: changedModules.length,
|
|
150
|
+
directlyAffectedModules: directDependents.length,
|
|
151
|
+
transitivelyAffectedModules: transitiveDependents.length,
|
|
152
|
+
governanceSurfaceItems: governanceSurfaceCount,
|
|
153
|
+
maxTransitiveChainDepth,
|
|
154
|
+
},
|
|
155
|
+
computedScore: impactRadiusScore,
|
|
156
|
+
thresholds: {
|
|
157
|
+
lowMax: 6,
|
|
158
|
+
mediumMax: 14,
|
|
159
|
+
highMin: 15,
|
|
160
|
+
},
|
|
161
|
+
rationale: [
|
|
162
|
+
'Fallback impact scoring was derived from diff signals when detailed dependency chains were unavailable.',
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function getSeverityRank(severity) {
|
|
168
|
+
switch (severity) {
|
|
169
|
+
case 'error':
|
|
170
|
+
return 1;
|
|
171
|
+
case 'warning':
|
|
172
|
+
return 2;
|
|
173
|
+
case 'info':
|
|
174
|
+
return 3;
|
|
175
|
+
default:
|
|
176
|
+
return 99;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function normalizeSortPath(filePath) {
|
|
180
|
+
return (filePath ?? '').replace(/\\/g, '/');
|
|
181
|
+
}
|
|
182
|
+
function isArchitectureReviewSignalRule(ruleId) {
|
|
183
|
+
return architectureReviewSignalRulePrefixes.some((prefix) => ruleId.startsWith(prefix));
|
|
184
|
+
}
|
|
185
|
+
function formatReviewFindingMessageSections(finding, indent = ' ') {
|
|
186
|
+
const nPlusOneMatch = /^(.+?without clear batching evidence\.)\s+Affected methods:\s+(.+?)\.\s+Repository\/model families:\s+(.+?)\.\s+Loop query evidence:\s+(.+?)\.\s+Risk instances:\s+(\d+)\.?$/u.exec(finding.message);
|
|
187
|
+
if (finding.id === 'AP-DQR-005' && nPlusOneMatch) {
|
|
188
|
+
const [, summary, methods, repositories, evidence, instances] = nPlusOneMatch;
|
|
189
|
+
return [
|
|
190
|
+
`${indent}- Message: ${summary}`,
|
|
191
|
+
`${indent}- Affected methods:`,
|
|
192
|
+
...methods.split(/,\s*/u).filter(Boolean).map((value) => `${indent} - ${value}`),
|
|
193
|
+
`${indent}- Repository/model families:`,
|
|
194
|
+
...repositories
|
|
195
|
+
.split(/,\s*/u)
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.map((value) => `${indent} - ${value}`),
|
|
198
|
+
`${indent}- Risk instances: ${instances}`,
|
|
199
|
+
`${indent}- Loop query evidence:`,
|
|
200
|
+
...evidence.split(/,\s*/u).filter(Boolean).map((value) => `${indent} - ${value}`),
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
const repeatedQueryMatch = /^Repeated query pattern:\s+method '(.+?)' performs the same '(.+?)' query (\d+) times in one flow\.$/u.exec(finding.message);
|
|
204
|
+
if (finding.id === 'AP-DQR-008' && repeatedQueryMatch) {
|
|
205
|
+
const [, methodName, queryLabel, duplicateReads] = repeatedQueryMatch;
|
|
206
|
+
return [
|
|
207
|
+
`${indent}- Message: Repeated query detected in one request flow.`,
|
|
208
|
+
`${indent}- Repeated query:`,
|
|
209
|
+
`${indent} - Method: ${methodName}`,
|
|
210
|
+
`${indent} - Query: ${queryLabel}`,
|
|
211
|
+
`${indent} - Duplicate reads: ${duplicateReads}`,
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
const oversizedMatch = /^(.*?)\s+\((loc=\d+.*)\)\.?$/u.exec(finding.message);
|
|
215
|
+
if (oversizedMatch &&
|
|
216
|
+
(finding.id === 'AP-APP-005' || finding.id === 'AP-DOM-003')) {
|
|
217
|
+
const [, summary, metrics] = oversizedMatch;
|
|
218
|
+
const metricMap = new Map(metrics.split(/,\s*/u).map((entry) => {
|
|
219
|
+
const [key, value] = entry.split('=', 2);
|
|
220
|
+
return [key?.trim() ?? '', value?.trim() ?? ''];
|
|
221
|
+
}));
|
|
222
|
+
const metricLines = [
|
|
223
|
+
['Lines of code', formatMetricNumber(metricMap.get('loc'))],
|
|
224
|
+
['Methods', metricMap.get('methods')],
|
|
225
|
+
['Collaborators', metricMap.get('collaborators')],
|
|
226
|
+
['Repository collaborators', metricMap.get('repositories')],
|
|
227
|
+
['Persistence calls', metricMap.get('persistence')],
|
|
228
|
+
['Database signals', metricMap.get('db')?.split('|').filter(Boolean).join(', ')],
|
|
229
|
+
['External systems', metricMap.get('external')?.split('|').filter(Boolean).join(', ')],
|
|
230
|
+
].filter(([, value]) => value && value.length > 0);
|
|
231
|
+
return [
|
|
232
|
+
`${indent}- Message: ${summary}`,
|
|
233
|
+
`${indent}- Metrics:`,
|
|
234
|
+
...metricLines.map(([label, value]) => `${indent} - ${label}: ${value}`),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
return [`${indent}- Message: ${finding.message}`];
|
|
238
|
+
}
|
|
239
|
+
function formatMetricNumber(value) {
|
|
240
|
+
if (!value || !/^\d+$/u.test(value)) {
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
return Number(value).toLocaleString('en-US');
|
|
244
|
+
}
|
|
245
|
+
function collectReviewGroupDetails(entries) {
|
|
246
|
+
const files = [...new Set(entries
|
|
247
|
+
.map((entry) => entry.sourceFile)
|
|
248
|
+
.filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
249
|
+
const tables = [...new Set(entries
|
|
250
|
+
.map((entry) => entry.table)
|
|
251
|
+
.filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
252
|
+
const apis = [...new Set(entries
|
|
253
|
+
.map((entry) => entry.api)
|
|
254
|
+
.filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
255
|
+
const modules = [...new Set(entries
|
|
256
|
+
.map((entry) => entry.module)
|
|
257
|
+
.filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
258
|
+
const targets = [...new Set(entries
|
|
259
|
+
.map((entry) => entry.target)
|
|
260
|
+
.filter((value) => typeof value === 'string' && value.length > 0))].sort((left, right) => left.localeCompare(right));
|
|
261
|
+
return { files, tables, apis, modules, targets };
|
|
262
|
+
}
|
|
263
|
+
function formatReviewMessageGroup(entries, indent = ' ') {
|
|
264
|
+
const first = entries[0];
|
|
265
|
+
if (!first) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
const lines = formatReviewFindingMessageSections(first, indent);
|
|
269
|
+
if (entries.length > 1) {
|
|
270
|
+
lines.push(`${indent}- Occurrences: ${entries.length}`);
|
|
271
|
+
}
|
|
272
|
+
const details = collectReviewGroupDetails(entries);
|
|
273
|
+
if (details.files.length > 0) {
|
|
274
|
+
lines.push(`${indent}- Affected files:`);
|
|
275
|
+
for (const file of details.files) {
|
|
276
|
+
lines.push(`${indent} - ${file}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (details.tables.length > 0) {
|
|
280
|
+
lines.push(`${indent}- Affected tables:`);
|
|
281
|
+
for (const table of details.tables) {
|
|
282
|
+
lines.push(`${indent} - ${table}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (details.apis.length > 0) {
|
|
286
|
+
lines.push(`${indent}- Affected APIs:`);
|
|
287
|
+
for (const api of details.apis) {
|
|
288
|
+
lines.push(`${indent} - ${api}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (details.modules.length > 0) {
|
|
292
|
+
lines.push(`${indent}- Affected modules:`);
|
|
293
|
+
for (const moduleName of details.modules) {
|
|
294
|
+
lines.push(`${indent} - ${moduleName}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (details.targets.length > 0) {
|
|
298
|
+
lines.push(`${indent}- Affected targets:`);
|
|
299
|
+
for (const targetName of details.targets) {
|
|
300
|
+
lines.push(`${indent} - ${targetName}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return lines;
|
|
304
|
+
}
|
|
305
|
+
function summarizeReviewFindingForInlineDisplay(finding) {
|
|
306
|
+
const oversizedMatch = /^(.*?)\s+\((loc=\d+.*)\)\.?$/u.exec(finding.message);
|
|
307
|
+
if (oversizedMatch && (finding.id === 'AP-APP-005' || finding.id === 'AP-DOM-003')) {
|
|
308
|
+
const [, summary, metrics] = oversizedMatch;
|
|
309
|
+
const metricMap = new Map(metrics.split(/,\s*/u).map((entry) => {
|
|
310
|
+
const [key, value] = entry.split('=', 2);
|
|
311
|
+
return [key?.trim() ?? '', value?.trim() ?? ''];
|
|
312
|
+
}));
|
|
313
|
+
return `${summary}. Lines of code: ${formatMetricNumber(metricMap.get('loc')) ?? 'n/a'}, Methods: ${metricMap.get('methods') ?? 'n/a'}, Collaborators: ${metricMap.get('collaborators') ?? 'n/a'}.`;
|
|
314
|
+
}
|
|
315
|
+
const nPlusOneMatch = /^(.+?without clear batching evidence\.)\s+Affected methods:\s+(.+?)\.\s+Repository\/model families:\s+(.+?)\.\s+Loop query evidence:\s+(.+?)\.\s+Risk instances:\s+(\d+)\.?$/u.exec(finding.message);
|
|
316
|
+
if (finding.id === 'AP-DQR-005' && nPlusOneMatch) {
|
|
317
|
+
const [, summary, methods, repositories, , instances] = nPlusOneMatch;
|
|
318
|
+
return `${summary} Affected methods: ${methods}. Repository/model families: ${repositories}. Risk instances: ${instances}.`;
|
|
319
|
+
}
|
|
320
|
+
const repeatedQueryMatch = /^Repeated query pattern:\s+method '(.+?)' performs the same '(.+?)' query (\d+) times in one flow\.$/u.exec(finding.message);
|
|
321
|
+
if (finding.id === 'AP-DQR-008' && repeatedQueryMatch) {
|
|
322
|
+
const [, methodName, queryLabel, duplicateReads] = repeatedQueryMatch;
|
|
323
|
+
return `Repeated query: ${queryLabel} in method '${methodName}' (${duplicateReads} reads).`;
|
|
324
|
+
}
|
|
325
|
+
return finding.message;
|
|
326
|
+
}
|
|
327
|
+
function groupReviewFindingsByRule(findings) {
|
|
328
|
+
const groups = new Map();
|
|
329
|
+
for (const finding of findings) {
|
|
330
|
+
const group = groups.get(finding.id);
|
|
331
|
+
if (group) {
|
|
332
|
+
group.push(finding);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
groups.set(finding.id, [finding]);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return [...groups.values()];
|
|
339
|
+
}
|
|
340
|
+
function compareReviewFinding(left, right) {
|
|
341
|
+
const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
|
|
342
|
+
if (severityCompare !== 0) {
|
|
343
|
+
return severityCompare;
|
|
344
|
+
}
|
|
345
|
+
const idCompare = left.id.localeCompare(right.id);
|
|
346
|
+
if (idCompare !== 0) {
|
|
347
|
+
return idCompare;
|
|
348
|
+
}
|
|
349
|
+
const pathCompare = normalizeSortPath(left.sourceFile).localeCompare(normalizeSortPath(right.sourceFile));
|
|
350
|
+
if (pathCompare !== 0) {
|
|
351
|
+
return pathCompare;
|
|
352
|
+
}
|
|
353
|
+
const lineCompare = (left.sourceLine ?? 0) - (right.sourceLine ?? 0);
|
|
354
|
+
if (lineCompare !== 0) {
|
|
355
|
+
return lineCompare;
|
|
356
|
+
}
|
|
357
|
+
return left.message.localeCompare(right.message);
|
|
358
|
+
}
|
|
359
|
+
function compareSuppressedFinding(left, right) {
|
|
360
|
+
const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
|
|
361
|
+
if (severityCompare !== 0) {
|
|
362
|
+
return severityCompare;
|
|
363
|
+
}
|
|
364
|
+
const idCompare = left.id.localeCompare(right.id);
|
|
365
|
+
if (idCompare !== 0) {
|
|
366
|
+
return idCompare;
|
|
367
|
+
}
|
|
368
|
+
const pathCompare = normalizeSortPath(left.filePath).localeCompare(normalizeSortPath(right.filePath));
|
|
369
|
+
if (pathCompare !== 0) {
|
|
370
|
+
return pathCompare;
|
|
371
|
+
}
|
|
372
|
+
const lineCompare = (left.line ?? 0) - (right.line ?? 0);
|
|
373
|
+
if (lineCompare !== 0) {
|
|
374
|
+
return lineCompare;
|
|
375
|
+
}
|
|
376
|
+
return left.message.localeCompare(right.message);
|
|
377
|
+
}
|
|
378
|
+
function parseFindingKind(value) {
|
|
379
|
+
return value === 'violation' || value === 'setup-gap' || value === 'guidance'
|
|
380
|
+
? value
|
|
381
|
+
: undefined;
|
|
382
|
+
}
|
|
383
|
+
function hasBlockingArchitectureViolation(findings) {
|
|
384
|
+
return findings.some((finding) => {
|
|
385
|
+
const kind = parseFindingKind(finding.kind) ??
|
|
386
|
+
parseFindingKind(finding.classification) ??
|
|
387
|
+
parseFindingKind(finding.type) ??
|
|
388
|
+
'violation';
|
|
389
|
+
return finding.severity === 'error' && kind === 'violation';
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function resolveAdrRequirementDisplay(input) {
|
|
393
|
+
const blockedByViolations = input.enforcementAction === 'fail' &&
|
|
394
|
+
!input.adrRequirement.required &&
|
|
395
|
+
hasBlockingArchitectureViolation(input.findings);
|
|
396
|
+
if (blockedByViolations) {
|
|
397
|
+
return {
|
|
398
|
+
state: 'not-applicable',
|
|
399
|
+
requiredLabel: 'N/A',
|
|
400
|
+
satisfiedLabel: 'N/A',
|
|
401
|
+
reasons: [
|
|
402
|
+
'Blocking architecture violations must be resolved before ADR follow-up is evaluated.',
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (input.adrRequirement.required) {
|
|
407
|
+
return {
|
|
408
|
+
state: 'required',
|
|
409
|
+
requiredLabel: 'YES',
|
|
410
|
+
satisfiedLabel: input.adrRequirement.satisfied ? 'YES' : 'NO',
|
|
411
|
+
reasons: input.adrRequirement.reasons,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
state: 'not-required',
|
|
416
|
+
requiredLabel: 'NO',
|
|
417
|
+
satisfiedLabel: 'N/A',
|
|
418
|
+
reasons: input.adrRequirement.reasons,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function toFinding(result) {
|
|
422
|
+
const meta = (0, architectureValidation_1.getValidationRuleMetadataById)(result.id);
|
|
423
|
+
const classification = (0, architectureValidation_1.classifyValidationResult)(result);
|
|
424
|
+
return {
|
|
425
|
+
id: result.id,
|
|
426
|
+
title: meta.title,
|
|
427
|
+
category: meta.category,
|
|
428
|
+
severity: result.severity,
|
|
429
|
+
kind: classification.kind,
|
|
430
|
+
message: result.message,
|
|
431
|
+
...(result.status ? { status: result.status } : {}),
|
|
432
|
+
...(result.findingType ? { findingType: result.findingType } : {}),
|
|
433
|
+
...(result.module ? { module: result.module } : {}),
|
|
434
|
+
...(result.target ? { target: result.target } : {}),
|
|
435
|
+
...(result.filePath ? { filePath: result.filePath } : {}),
|
|
436
|
+
...(result.table ? { table: result.table } : {}),
|
|
437
|
+
...(result.api ? { api: result.api } : {}),
|
|
438
|
+
...(result.sourceFile ? { sourceFile: result.sourceFile } : {}),
|
|
439
|
+
...(result.sourceLine !== undefined ? { sourceLine: result.sourceLine } : {}),
|
|
440
|
+
...(result.sourceColumn !== undefined ? { sourceColumn: result.sourceColumn } : {}),
|
|
441
|
+
...(result.dependencyType ? { dependencyType: result.dependencyType } : {}),
|
|
442
|
+
...(result.sourceComponentId ? { sourceComponentId: result.sourceComponentId } : {}),
|
|
443
|
+
...(result.sourceComponentName ? { sourceComponentName: result.sourceComponentName } : {}),
|
|
444
|
+
...(result.targetComponentId !== undefined
|
|
445
|
+
? { targetComponentId: result.targetComponentId }
|
|
446
|
+
: {}),
|
|
447
|
+
...(result.targetComponentName !== undefined
|
|
448
|
+
? { targetComponentName: result.targetComponentName }
|
|
449
|
+
: {}),
|
|
450
|
+
...(result.importSpecifier ? { importSpecifier: result.importSpecifier } : {}),
|
|
451
|
+
...(result.resolvedTargetPath !== undefined
|
|
452
|
+
? { resolvedTargetPath: result.resolvedTargetPath }
|
|
453
|
+
: {}),
|
|
454
|
+
...(result.isExternal !== undefined ? { isExternal: result.isExternal } : {}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function toSuppressedFinding(result) {
|
|
458
|
+
return {
|
|
459
|
+
id: result.id,
|
|
460
|
+
title: result.title,
|
|
461
|
+
category: result.category,
|
|
462
|
+
severity: result.severity,
|
|
463
|
+
message: result.message,
|
|
464
|
+
filePath: result.filePath,
|
|
465
|
+
...(result.line !== undefined ? { line: result.line } : {}),
|
|
466
|
+
suppressionPath: result.suppressionPath,
|
|
467
|
+
...(result.suppressionReason ? { suppressionReason: result.suppressionReason } : {}),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function buildFixCommand(suggestion) {
|
|
471
|
+
return suggestion.isSafeFix
|
|
472
|
+
? `archpilot fix ${suggestion.ruleId}`
|
|
473
|
+
: `archpilot fix ${suggestion.ruleId} --force`;
|
|
474
|
+
}
|
|
475
|
+
function formatDriftViolationLine(violation) {
|
|
476
|
+
return violation.file
|
|
477
|
+
? `- ${violation.ruleId} (${violation.file})`
|
|
478
|
+
: `- ${violation.ruleId}`;
|
|
479
|
+
}
|
|
480
|
+
function getValidationResultLabel(summary) {
|
|
481
|
+
return summary.counts.errors > 0 ? 'FAIL' : 'PASS';
|
|
482
|
+
}
|
|
483
|
+
function groupBySeverity(findings) {
|
|
484
|
+
const errors = [];
|
|
485
|
+
const warnings = [];
|
|
486
|
+
const info = [];
|
|
487
|
+
for (const finding of findings) {
|
|
488
|
+
if (finding.severity === 'error') {
|
|
489
|
+
errors.push(finding);
|
|
490
|
+
}
|
|
491
|
+
else if (finding.severity === 'warning') {
|
|
492
|
+
warnings.push(finding);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
info.push(finding);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { error: errors, warning: warnings, info };
|
|
499
|
+
}
|
|
500
|
+
function emptyRuleCount(ruleId) {
|
|
501
|
+
return {
|
|
502
|
+
ruleId,
|
|
503
|
+
total: 0,
|
|
504
|
+
errors: 0,
|
|
505
|
+
warnings: 0,
|
|
506
|
+
info: 0,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function incrementSeverityCounts(counts, severity) {
|
|
510
|
+
counts.total += 1;
|
|
511
|
+
if (severity === 'error') {
|
|
512
|
+
counts.errors += 1;
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (severity === 'warning') {
|
|
516
|
+
counts.warnings += 1;
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
counts.info += 1;
|
|
520
|
+
}
|
|
521
|
+
function incrementRuleCount(rules, ruleId, severity) {
|
|
522
|
+
const count = rules.get(ruleId) ?? emptyRuleCount(ruleId);
|
|
523
|
+
incrementSeverityCounts(count, severity);
|
|
524
|
+
rules.set(ruleId, count);
|
|
525
|
+
}
|
|
526
|
+
function sortRuleCounts(rules) {
|
|
527
|
+
return [...rules].sort((left, right) => left.ruleId.localeCompare(right.ruleId));
|
|
528
|
+
}
|
|
529
|
+
function shouldIncludeComponentSummary(findings) {
|
|
530
|
+
const componentIds = new Set(findings
|
|
531
|
+
.flatMap((finding) => [finding.sourceComponentId, finding.targetComponentId ?? undefined])
|
|
532
|
+
.filter((value) => typeof value === 'string' && value.length > 0));
|
|
533
|
+
return componentIds.size > 0 && !(componentIds.size === 1 && componentIds.has('workspace'));
|
|
534
|
+
}
|
|
535
|
+
function buildArchitectureReviewComponentSummary(findings) {
|
|
536
|
+
if (!shouldIncludeComponentSummary(findings)) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
const byComponent = new Map();
|
|
540
|
+
const crossComponent = new Map();
|
|
541
|
+
for (const finding of findings) {
|
|
542
|
+
const componentRefs = [
|
|
543
|
+
finding.sourceComponentId
|
|
544
|
+
? {
|
|
545
|
+
componentId: finding.sourceComponentId,
|
|
546
|
+
componentName: finding.sourceComponentName ?? finding.sourceComponentId,
|
|
547
|
+
}
|
|
548
|
+
: undefined,
|
|
549
|
+
finding.targetComponentId
|
|
550
|
+
? {
|
|
551
|
+
componentId: finding.targetComponentId,
|
|
552
|
+
componentName: finding.targetComponentName ?? finding.targetComponentId,
|
|
553
|
+
}
|
|
554
|
+
: undefined,
|
|
555
|
+
]
|
|
556
|
+
.filter((entry) => !!entry && entry.componentId !== 'workspace')
|
|
557
|
+
.filter((entry, index, entries) => entries.findIndex((candidate) => candidate.componentId === entry.componentId) === index);
|
|
558
|
+
for (const { componentId, componentName } of componentRefs) {
|
|
559
|
+
const component = byComponent.get(componentId) ?? {
|
|
560
|
+
componentId,
|
|
561
|
+
componentName,
|
|
562
|
+
total: 0,
|
|
563
|
+
errors: 0,
|
|
564
|
+
warnings: 0,
|
|
565
|
+
info: 0,
|
|
566
|
+
rules: new Map(),
|
|
567
|
+
};
|
|
568
|
+
incrementSeverityCounts(component, finding.severity);
|
|
569
|
+
incrementRuleCount(component.rules, finding.id, finding.severity);
|
|
570
|
+
byComponent.set(componentId, component);
|
|
571
|
+
}
|
|
572
|
+
if (finding.dependencyType === 'cross-component' &&
|
|
573
|
+
finding.sourceComponentId &&
|
|
574
|
+
finding.targetComponentId &&
|
|
575
|
+
finding.sourceComponentId !== finding.targetComponentId) {
|
|
576
|
+
const key = `${finding.sourceComponentId}->${finding.targetComponentId}`;
|
|
577
|
+
const edge = crossComponent.get(key) ?? {
|
|
578
|
+
sourceComponentId: finding.sourceComponentId,
|
|
579
|
+
sourceComponentName: finding.sourceComponentName ?? finding.sourceComponentId,
|
|
580
|
+
targetComponentId: finding.targetComponentId,
|
|
581
|
+
targetComponentName: finding.targetComponentName ?? finding.targetComponentId,
|
|
582
|
+
total: 0,
|
|
583
|
+
errors: 0,
|
|
584
|
+
warnings: 0,
|
|
585
|
+
info: 0,
|
|
586
|
+
rules: new Map(),
|
|
587
|
+
};
|
|
588
|
+
incrementSeverityCounts(edge, finding.severity);
|
|
589
|
+
incrementRuleCount(edge.rules, finding.id, finding.severity);
|
|
590
|
+
crossComponent.set(key, edge);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
components: [...byComponent.values()]
|
|
595
|
+
.map((entry) => ({
|
|
596
|
+
componentId: entry.componentId,
|
|
597
|
+
componentName: entry.componentName,
|
|
598
|
+
total: entry.total,
|
|
599
|
+
errors: entry.errors,
|
|
600
|
+
warnings: entry.warnings,
|
|
601
|
+
info: entry.info,
|
|
602
|
+
rules: sortRuleCounts(entry.rules.values()),
|
|
603
|
+
}))
|
|
604
|
+
.sort((left, right) => left.componentId.localeCompare(right.componentId)),
|
|
605
|
+
crossComponentDependencies: [...crossComponent.values()]
|
|
606
|
+
.map((entry) => ({
|
|
607
|
+
sourceComponentId: entry.sourceComponentId,
|
|
608
|
+
sourceComponentName: entry.sourceComponentName,
|
|
609
|
+
targetComponentId: entry.targetComponentId,
|
|
610
|
+
targetComponentName: entry.targetComponentName,
|
|
611
|
+
total: entry.total,
|
|
612
|
+
errors: entry.errors,
|
|
613
|
+
warnings: entry.warnings,
|
|
614
|
+
info: entry.info,
|
|
615
|
+
rules: sortRuleCounts(entry.rules.values()),
|
|
616
|
+
}))
|
|
617
|
+
.sort((left, right) => left.sourceComponentId.localeCompare(right.sourceComponentId) ||
|
|
618
|
+
left.targetComponentId.localeCompare(right.targetComponentId)),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function countCrossModuleDependencyViolations(results) {
|
|
622
|
+
const crossModuleRuleIds = new Set([
|
|
623
|
+
'AP-DEP-002',
|
|
624
|
+
'AP-DEP-003',
|
|
625
|
+
'AP-DEP-004',
|
|
626
|
+
'AP-DEP-005',
|
|
627
|
+
'AP-DEP-010',
|
|
628
|
+
'AP-DEP-011',
|
|
629
|
+
]);
|
|
630
|
+
return results.filter((result) => {
|
|
631
|
+
if (result.passed || !crossModuleRuleIds.has(result.id)) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return (0, architectureValidation_1.classifyValidationResult)(result).kind === 'violation';
|
|
635
|
+
}).length;
|
|
636
|
+
}
|
|
637
|
+
function countCrossModuleContractViolations(results) {
|
|
638
|
+
return results.filter((result) => {
|
|
639
|
+
if (result.passed || (result.id !== 'AP-DEP-004' && result.id !== 'AP-DEP-005')) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
return (0, architectureValidation_1.classifyValidationResult)(result).kind === 'violation';
|
|
643
|
+
}).length;
|
|
644
|
+
}
|
|
645
|
+
function countCycleViolations(results) {
|
|
646
|
+
return results.filter((result) => {
|
|
647
|
+
if (result.passed || (result.id !== 'AP-DEP-001' && result.id !== 'AP-DEP-008')) {
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
return (0, architectureValidation_1.classifyValidationResult)(result).kind === 'violation';
|
|
651
|
+
}).length;
|
|
652
|
+
}
|
|
653
|
+
function countMultiCategoryViolations(results) {
|
|
654
|
+
const categories = new Set();
|
|
655
|
+
for (const result of results) {
|
|
656
|
+
if (result.passed) {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
categories.add((0, architectureValidation_1.getValidationRuleMetadataById)(result.id).category);
|
|
660
|
+
}
|
|
661
|
+
return categories.size;
|
|
662
|
+
}
|
|
663
|
+
function isHeuristicArchitectureSmellRuleId(ruleId) {
|
|
664
|
+
return /^AP-(?:APP|DOM|TXN)-/u.test(ruleId);
|
|
665
|
+
}
|
|
666
|
+
function buildFindingNormalization(results) {
|
|
667
|
+
const failedResults = results.filter((result) => !result.passed);
|
|
668
|
+
const normalizedKeys = new Set();
|
|
669
|
+
for (const result of failedResults) {
|
|
670
|
+
normalizedKeys.add((0, architectureValidation_1.classifyValidationResult)(result).deductionKey);
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
rawFindingCount: failedResults.length,
|
|
674
|
+
normalizedFindingCount: normalizedKeys.size,
|
|
675
|
+
duplicateReductionCount: Math.max(0, failedResults.length - normalizedKeys.size),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function buildArchitectureSmellSummary(findings) {
|
|
679
|
+
const smellFindings = findings.filter((finding) => isHeuristicArchitectureSmellRuleId(finding.id));
|
|
680
|
+
const ruleFamilies = ['AP-APP', 'AP-DOM', 'AP-TXN']
|
|
681
|
+
.map((family) => {
|
|
682
|
+
const familyFindings = smellFindings.filter((finding) => finding.id.startsWith(family));
|
|
683
|
+
const rules = [...familyFindings.reduce((acc, finding) => {
|
|
684
|
+
acc.set(finding.id, (acc.get(finding.id) ?? 0) + 1);
|
|
685
|
+
return acc;
|
|
686
|
+
}, new Map()).entries()]
|
|
687
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
688
|
+
.map(([ruleId, count]) => ({ ruleId, count }));
|
|
689
|
+
return {
|
|
690
|
+
family,
|
|
691
|
+
count: familyFindings.length,
|
|
692
|
+
rules,
|
|
693
|
+
};
|
|
694
|
+
})
|
|
695
|
+
.filter((family) => family.count > 0);
|
|
696
|
+
return {
|
|
697
|
+
count: smellFindings.length,
|
|
698
|
+
ruleFamilies,
|
|
699
|
+
explanation: 'AP-APP/AP-DOM/AP-TXN findings are heuristic architecture review signals; they are not deterministic compile, security, or database failures.',
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function buildScoreExplanation(results, healthScore) {
|
|
703
|
+
const problemSummary = (0, architectureHealthScore_1.summarizeArchitectureScoreProblems)({
|
|
704
|
+
results: results.map((result) => {
|
|
705
|
+
const classification = (0, architectureValidation_1.classifyValidationResult)(result);
|
|
706
|
+
return {
|
|
707
|
+
ruleId: result.id,
|
|
708
|
+
category: (0, architectureValidation_1.getValidationRuleMetadataById)(result.id).category,
|
|
709
|
+
severity: result.severity,
|
|
710
|
+
passed: result.passed,
|
|
711
|
+
impact: classification.scoreImpact,
|
|
712
|
+
deductionKey: classification.deductionKey,
|
|
713
|
+
};
|
|
714
|
+
}),
|
|
715
|
+
});
|
|
716
|
+
const heuristicWarnings = results.filter((result) => !result.passed &&
|
|
717
|
+
result.severity === 'warning' &&
|
|
718
|
+
isHeuristicArchitectureSmellRuleId(result.id)).length;
|
|
719
|
+
const dependencyWarnings = results.filter((result) => !result.passed && result.severity === 'warning' && result.id.startsWith('AP-DEP-')).length;
|
|
720
|
+
const databaseWarnings = results.filter((result) => !result.passed && result.severity === 'warning' && (0, architectureValidation_1.getValidationRuleMetadataById)(result.id).category === 'database').length;
|
|
721
|
+
const documentationWarnings = results.filter((result) => !result.passed && result.severity === 'warning' && (0, architectureValidation_1.getValidationRuleMetadataById)(result.id).category === 'documentation').length;
|
|
722
|
+
const blockingDependencyErrors = problemSummary.clusters.filter((cluster) => cluster.severity === 'error' &&
|
|
723
|
+
cluster.ruleIds.some((ruleId) => ruleId === 'AP-DEP-001' || ruleId === 'AP-DEP-008')).length;
|
|
724
|
+
const highestImpactCategories = Object.entries(healthScore.categoryScores)
|
|
725
|
+
.sort((left, right) => left[1] - right[1] || left[0].localeCompare(right[0]))
|
|
726
|
+
.slice(0, 3)
|
|
727
|
+
.map(([category, score]) => ({ category, score }));
|
|
728
|
+
const applicableCategoryScores = healthScore.applicableCategories
|
|
729
|
+
.map((category) => ({
|
|
730
|
+
category,
|
|
731
|
+
score: healthScore.categoryScores[category] ?? 100,
|
|
732
|
+
weight: architectureHealthScore_1.architectureHealthCategoryWeights[category] ?? 0,
|
|
733
|
+
}))
|
|
734
|
+
.sort((left, right) => left.category.localeCompare(right.category));
|
|
735
|
+
const whyScoreChanged = healthScore.errors > 0
|
|
736
|
+
? 'Error-level findings carry the highest score impact, but repeated root-cause clusters are normalized before scoring.'
|
|
737
|
+
: heuristicWarnings > 0 && dependencyWarnings > 0
|
|
738
|
+
? 'The score reflects normalized warning clusters: capped architecture-review signals plus remaining dependency governance warnings.'
|
|
739
|
+
: heuristicWarnings > 0
|
|
740
|
+
? 'The score reflects capped cumulative impact from heuristic architecture-smell warning clusters.'
|
|
741
|
+
: dependencyWarnings > 0
|
|
742
|
+
? 'The score reflects dependency governance warning clusters after repeated-surface normalization.'
|
|
743
|
+
: healthScore.warnings > 0
|
|
744
|
+
? 'The score reflects warning-level findings with volume normalization and diminishing returns for repeated problem families.'
|
|
745
|
+
: 'No active findings reduced the score.';
|
|
746
|
+
const largestScoreContributors = [];
|
|
747
|
+
if (blockingDependencyErrors > 0) {
|
|
748
|
+
largestScoreContributors.push(`Circular dependency / blocking dependency problems remain high-impact, but related findings are grouped into ${blockingDependencyErrors} blocking cluster${blockingDependencyErrors === 1 ? '' : 's'}.`);
|
|
749
|
+
}
|
|
750
|
+
if (databaseWarnings > 0) {
|
|
751
|
+
const databaseClusters = problemSummary.clusters.filter((cluster) => cluster.category === 'database').length;
|
|
752
|
+
largestScoreContributors.push(`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).`);
|
|
753
|
+
}
|
|
754
|
+
if (documentationWarnings > 0) {
|
|
755
|
+
largestScoreContributors.push(`Documentation gaps contributed ${documentationWarnings} warning${documentationWarnings === 1 ? '' : 's'} to the documentation category.`);
|
|
756
|
+
}
|
|
757
|
+
if (heuristicWarnings > 0) {
|
|
758
|
+
largestScoreContributors.push(`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'}).`);
|
|
759
|
+
}
|
|
760
|
+
if (healthScore.warnings > 1) {
|
|
761
|
+
largestScoreContributors.push(`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.`);
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
errors: healthScore.errors,
|
|
765
|
+
warnings: healthScore.warnings,
|
|
766
|
+
heuristicWarnings,
|
|
767
|
+
weightedBaseScore: healthScore.weightedBaseScore,
|
|
768
|
+
applicableCategoryScores,
|
|
769
|
+
highestImpactCategories,
|
|
770
|
+
whyScoreChanged,
|
|
771
|
+
largestScoreContributors,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function buildArchitectureReviewSummary(input) {
|
|
775
|
+
const failingFindings = input.results
|
|
776
|
+
.filter((result) => !result.passed)
|
|
777
|
+
.map((result) => toFinding(result))
|
|
778
|
+
.sort(compareReviewFinding);
|
|
779
|
+
const passedChecks = input.results
|
|
780
|
+
.filter((result) => result.passed)
|
|
781
|
+
.map((result) => toFinding(result))
|
|
782
|
+
.sort(compareReviewFinding);
|
|
783
|
+
const suppressedFindings = [...(input.suppressedResults ?? [])]
|
|
784
|
+
.map((result) => toSuppressedFinding(result))
|
|
785
|
+
.sort(compareSuppressedFinding);
|
|
786
|
+
const counts = {
|
|
787
|
+
errors: failingFindings.filter((finding) => finding.severity === 'error').length,
|
|
788
|
+
warnings: failingFindings.filter((finding) => finding.severity === 'warning').length,
|
|
789
|
+
info: failingFindings.filter((finding) => finding.severity === 'info').length,
|
|
790
|
+
passed: passedChecks.length,
|
|
791
|
+
suppressed: suppressedFindings.length,
|
|
792
|
+
exceptions: input.results.filter((result) => result.status === 'exception').length,
|
|
793
|
+
ignoredSuppressions: input.ignoredSuppressionsCount ?? 0,
|
|
794
|
+
};
|
|
795
|
+
const impactAnalysis = input.changeSignals?.impactAnalysis ??
|
|
796
|
+
buildDefaultImpactAnalysisFromSignals(input.changeSignals);
|
|
797
|
+
const crossModuleDependencyViolations = countCrossModuleDependencyViolations(input.results);
|
|
798
|
+
const crossModuleContractViolations = countCrossModuleContractViolations(input.results);
|
|
799
|
+
const cycleViolations = countCycleViolations(input.results);
|
|
800
|
+
const multiCategoryViolations = countMultiCategoryViolations(input.results);
|
|
801
|
+
const changeRisk = (0, changeRiskAssessment_1.assessChangeRisk)({
|
|
802
|
+
...(impactAnalysis.changedModules.length > 0
|
|
803
|
+
? { changedModules: impactAnalysis.changedModules }
|
|
804
|
+
: {}),
|
|
805
|
+
...(impactAnalysis.totalAffectedModules !== undefined
|
|
806
|
+
? { affectedDependentsCount: impactAnalysis.totalAffectedModules }
|
|
807
|
+
: {}),
|
|
808
|
+
...(impactAnalysis.directDependents.length > 0
|
|
809
|
+
? { directlyAffectedModules: impactAnalysis.directDependents }
|
|
810
|
+
: {}),
|
|
811
|
+
...(impactAnalysis.transitiveDependents.length > 0
|
|
812
|
+
? { transitivelyAffectedModules: impactAnalysis.transitiveDependents }
|
|
813
|
+
: {}),
|
|
814
|
+
...(input.changeSignals?.touchedContracts
|
|
815
|
+
? { touchedContracts: input.changeSignals.touchedContracts }
|
|
816
|
+
: {}),
|
|
817
|
+
...(input.changeSignals?.touchedPublicEntrypoints
|
|
818
|
+
? { touchedPublicEntrypoints: input.changeSignals.touchedPublicEntrypoints }
|
|
819
|
+
: {}),
|
|
820
|
+
...(input.changeSignals?.touchedArchitectureArtifacts
|
|
821
|
+
? { touchedArchitectureArtifacts: input.changeSignals.touchedArchitectureArtifacts }
|
|
822
|
+
: {}),
|
|
823
|
+
...(input.changeSignals?.touchedAdrs
|
|
824
|
+
? { touchedAdrs: input.changeSignals.touchedAdrs }
|
|
825
|
+
: {}),
|
|
826
|
+
...(input.changeSignals?.diffContextAvailable !== undefined
|
|
827
|
+
? { diffContextAvailable: input.changeSignals.diffContextAvailable }
|
|
828
|
+
: {}),
|
|
829
|
+
errorCount: counts.errors,
|
|
830
|
+
warningCount: counts.warnings,
|
|
831
|
+
crossModuleDependencyViolations,
|
|
832
|
+
crossModuleContractViolations,
|
|
833
|
+
cycleViolations,
|
|
834
|
+
multiCategoryViolations,
|
|
835
|
+
});
|
|
836
|
+
const risk = (0, architectureRiskAssessment_1.assessArchitectureRisk)(input.results, {
|
|
837
|
+
suppressedResults: input.suppressedResults,
|
|
838
|
+
});
|
|
839
|
+
const enforcement = input.enforcement ?? {
|
|
840
|
+
action: 'pass',
|
|
841
|
+
reasons: ['Policy enforcement not evaluated for this summary context.'],
|
|
842
|
+
};
|
|
843
|
+
const healthScore = (0, architectureHealthScore_1.calculateArchitectureHealthScore)({
|
|
844
|
+
results: input.results.map((result) => {
|
|
845
|
+
const classification = (0, architectureValidation_1.classifyValidationResult)(result);
|
|
846
|
+
return {
|
|
847
|
+
ruleId: result.id,
|
|
848
|
+
category: (0, architectureValidation_1.getValidationRuleMetadataById)(result.id).category,
|
|
849
|
+
severity: result.severity,
|
|
850
|
+
passed: result.passed,
|
|
851
|
+
impact: classification.scoreImpact,
|
|
852
|
+
deductionKey: classification.deductionKey,
|
|
853
|
+
};
|
|
854
|
+
}),
|
|
855
|
+
categories: architectureHealthScore_1.defaultArchitectureHealthCategories,
|
|
856
|
+
});
|
|
857
|
+
const adrRequirement = input.adrRequirement ?? {
|
|
858
|
+
required: false,
|
|
859
|
+
satisfied: false,
|
|
860
|
+
reasons: ['ADR requirement status was not evaluated for this summary context.'],
|
|
861
|
+
};
|
|
862
|
+
const componentSummary = buildArchitectureReviewComponentSummary(failingFindings);
|
|
863
|
+
const findingNormalization = buildFindingNormalization(input.results);
|
|
864
|
+
const architectureSmellSummary = buildArchitectureSmellSummary(failingFindings);
|
|
865
|
+
const scoreExplanation = buildScoreExplanation(input.results, healthScore);
|
|
866
|
+
return {
|
|
867
|
+
reportVersion: reviewReportVersion,
|
|
868
|
+
projectName: input.projectName,
|
|
869
|
+
generatedAtUtc: input.generatedAtUtc ?? new Date().toISOString(),
|
|
870
|
+
risk,
|
|
871
|
+
enforcement,
|
|
872
|
+
adrRequirement,
|
|
873
|
+
healthScore: healthScore.overallScore,
|
|
874
|
+
categoryScores: healthScore.categoryScores,
|
|
875
|
+
readinessScore: healthScore.readinessScore,
|
|
876
|
+
readinessCategoryScores: healthScore.readinessCategoryScores,
|
|
877
|
+
setupGaps: healthScore.setupGaps,
|
|
878
|
+
guidanceFindings: healthScore.guidanceFindings,
|
|
879
|
+
qualityFindings: healthScore.qualityFindings,
|
|
880
|
+
changeRisk,
|
|
881
|
+
impactAnalysis,
|
|
882
|
+
counts,
|
|
883
|
+
findingNormalization,
|
|
884
|
+
rawFindingCount: findingNormalization.rawFindingCount,
|
|
885
|
+
normalizedFindingCount: findingNormalization.normalizedFindingCount,
|
|
886
|
+
duplicateReductionCount: findingNormalization.duplicateReductionCount,
|
|
887
|
+
heuristicArchitectureSmellCount: architectureSmellSummary.count,
|
|
888
|
+
architectureSmellSummary,
|
|
889
|
+
scoreExplanation,
|
|
890
|
+
findings: failingFindings,
|
|
891
|
+
passedChecks,
|
|
892
|
+
suppressedFindings,
|
|
893
|
+
...(componentSummary ? { componentSummary } : {}),
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function renderArchitectureReviewConsole(summary, options) {
|
|
897
|
+
const grouped = groupBySeverity(summary.findings);
|
|
898
|
+
const adrDisplay = resolveAdrRequirementDisplay({
|
|
899
|
+
enforcementAction: summary.enforcement.action,
|
|
900
|
+
adrRequirement: summary.adrRequirement,
|
|
901
|
+
findings: summary.findings,
|
|
902
|
+
});
|
|
903
|
+
const lines = [];
|
|
904
|
+
lines.push('ArchPilot Architecture Review');
|
|
905
|
+
lines.push('');
|
|
906
|
+
lines.push(`Result: ${getValidationResultLabel(summary)}`);
|
|
907
|
+
lines.push(`Architecture Review Level: ${summary.risk.level.toUpperCase()}`);
|
|
908
|
+
lines.push(`Enforcement (Policy Outcome): ${summary.enforcement.action.toUpperCase()}`);
|
|
909
|
+
if (summary.enforcement.policyLevel) {
|
|
910
|
+
lines.push(`Policy level: ${summary.enforcement.policyLevel.toUpperCase()}`);
|
|
911
|
+
}
|
|
912
|
+
if (summary.enforcement.enforcementDecision) {
|
|
913
|
+
lines.push(`Enforcement decision: ${summary.enforcement.enforcementDecision}`);
|
|
914
|
+
}
|
|
915
|
+
if (summary.enforcement.matchedThreshold) {
|
|
916
|
+
lines.push(`Matched threshold: ${summary.enforcement.matchedThreshold.id ?? '(unknown)'} (configured=${summary.enforcement.matchedThreshold.configuredValue ?? 'n/a'}, matched=${summary.enforcement.matchedThreshold.matchedValue ?? 'n/a'}, source=${summary.enforcement.matchedThreshold.source ?? summary.enforcement.thresholdSource ?? 'n/a'})`);
|
|
917
|
+
}
|
|
918
|
+
if (summary.enforcement.baselineContribution) {
|
|
919
|
+
lines.push(`Baseline contribution: ${summary.enforcement.baselineContribution.elevated ? 'ELEVATED' : 'NOT_ELEVATED'}`);
|
|
920
|
+
lines.push(`- ${summary.enforcement.baselineContribution.reason ?? 'No baseline contribution details.'}`);
|
|
921
|
+
}
|
|
922
|
+
if (summary.enforcement.ciExitBehavior) {
|
|
923
|
+
lines.push(`CI exit behavior: exitCode=${summary.enforcement.ciExitBehavior.exitCode}, blocking=${summary.enforcement.ciExitBehavior.blocking ? 'yes' : 'no'}`);
|
|
924
|
+
lines.push(`- ${summary.enforcement.ciExitBehavior.reason}`);
|
|
925
|
+
}
|
|
926
|
+
if ((summary.enforcement.requiredActions?.length ?? 0) > 0) {
|
|
927
|
+
lines.push('Required actions:');
|
|
928
|
+
for (const action of summary.enforcement.requiredActions ?? []) {
|
|
929
|
+
lines.push(`- ${action}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
lines.push(`Errors: ${summary.counts.errors} Warnings: ${summary.counts.warnings}`);
|
|
933
|
+
lines.push(`Score: ${summary.healthScore} / 100`);
|
|
934
|
+
lines.push(`Readiness: ${summary.readinessScore} / 100`);
|
|
935
|
+
lines.push(`Change Risk (Diff-Aware PR Impact): ${summary.changeRisk.riskLevel.toUpperCase()}`);
|
|
936
|
+
lines.push(`Change Risk Summary: ${summary.changeRisk.summary}`);
|
|
937
|
+
if (summary.changeRisk.reasons.length > 0) {
|
|
938
|
+
lines.push('Reasons:');
|
|
939
|
+
for (const reason of summary.changeRisk.reasons) {
|
|
940
|
+
lines.push(`- ${reason}`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (summary.changeRisk.signals.changedModulesCount !== undefined) {
|
|
944
|
+
lines.push(`Changed modules: ${summary.changeRisk.signals.changedModulesCount}`);
|
|
945
|
+
}
|
|
946
|
+
if (summary.changeRisk.signals.affectedDependentsCount !== undefined) {
|
|
947
|
+
lines.push(`Affected dependents: ${summary.changeRisk.signals.affectedDependentsCount}`);
|
|
948
|
+
}
|
|
949
|
+
if (summary.changeRisk.factors.length > 0) {
|
|
950
|
+
lines.push('Triggered factors:');
|
|
951
|
+
for (const factor of summary.changeRisk.factors) {
|
|
952
|
+
lines.push(`- ${factor.id} ${factor.label} (${factor.evidence})`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
lines.push('Impact Analysis');
|
|
956
|
+
lines.push(`- Changed Modules: ${summary.impactAnalysis.changedModules.join(', ') || '(none)'}`);
|
|
957
|
+
lines.push(`- Direct Dependents: ${summary.impactAnalysis.directDependents.join(', ') || '(none)'}`);
|
|
958
|
+
lines.push(`- Transitive Dependents: ${summary.impactAnalysis.transitiveDependents.join(', ') || '(none)'}`);
|
|
959
|
+
lines.push(`- Total Affected Modules: ${summary.impactAnalysis.totalAffectedModules}`);
|
|
960
|
+
lines.push(`- Impact Radius Score: ${summary.impactAnalysis.impactRadiusScore}`);
|
|
961
|
+
lines.push(`- Impact Level: ${summary.impactAnalysis.impactLevel}`);
|
|
962
|
+
lines.push(`- Summary: ${summary.impactAnalysis.summary}`);
|
|
963
|
+
const baselineComparison = options?.baselineComparison ?? summary.baselineComparison;
|
|
964
|
+
if (baselineComparison) {
|
|
965
|
+
lines.push('Baseline Comparison');
|
|
966
|
+
lines.push(`- Baseline status: ${baselineComparison.status}`);
|
|
967
|
+
lines.push(`- Health score delta: ${baselineComparison.deltas.healthScoreDelta}`);
|
|
968
|
+
lines.push(`- Readiness score delta: ${baselineComparison.deltas.readinessScoreDelta}`);
|
|
969
|
+
lines.push(`- New findings: ${baselineComparison.newlyIntroducedFindings.length}`);
|
|
970
|
+
lines.push(`- Resolved findings: ${baselineComparison.resolvedFindings.length}`);
|
|
971
|
+
lines.push(`- Summary: ${baselineComparison.summary}`);
|
|
972
|
+
}
|
|
973
|
+
lines.push('ADR Requirement:');
|
|
974
|
+
lines.push(`- Required: ${adrDisplay.requiredLabel}`);
|
|
975
|
+
lines.push(`- Satisfied: ${adrDisplay.satisfiedLabel}`);
|
|
976
|
+
lines.push('- Reasons:');
|
|
977
|
+
lines.push(adrDisplay.reasons.length === 0
|
|
978
|
+
? ' - (none)'
|
|
979
|
+
: adrDisplay.reasons.map((reason) => ` - ${reason}`).join('\n'));
|
|
980
|
+
if (adrDisplay.state === 'required' && summary.adrRequirement.suggestedCommand) {
|
|
981
|
+
lines.push('- Suggested command:');
|
|
982
|
+
lines.push(` - ${summary.adrRequirement.suggestedCommand}`);
|
|
983
|
+
}
|
|
984
|
+
if (adrDisplay.state === 'required' && summary.adrRequirement.suggestedFilenameHint) {
|
|
985
|
+
lines.push('- Suggested filename hint:');
|
|
986
|
+
lines.push(` - ${summary.adrRequirement.suggestedFilenameHint}`);
|
|
987
|
+
}
|
|
988
|
+
if (summary.safetyControls) {
|
|
989
|
+
lines.push('Recommended Deployment Safety Controls:');
|
|
990
|
+
lines.push(`- Triggered: ${summary.safetyControls.triggered ? 'YES' : 'NO'}`);
|
|
991
|
+
lines.push(`- Applicable: ${summary.safetyControls.applicable ? 'YES' : 'NO'}`);
|
|
992
|
+
lines.push(`- Summary: ${summary.safetyControls.summary}`);
|
|
993
|
+
if (summary.safetyControls.triggerReasons.length > 0) {
|
|
994
|
+
lines.push('- Trigger reasons:');
|
|
995
|
+
for (const reason of summary.safetyControls.triggerReasons) {
|
|
996
|
+
lines.push(` - ${reason}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (summary.safetyControls.findings.length > 0) {
|
|
1000
|
+
lines.push('- Missing controls:');
|
|
1001
|
+
for (const finding of summary.safetyControls.findings) {
|
|
1002
|
+
lines.push(` - [${finding.ruleId}] ${finding.message}`);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
lines.push(options?.healthTrend
|
|
1007
|
+
? (0, architectureHealthHistoryReporter_1.formatArchitectureHealthTrendLine)(options.healthTrend)
|
|
1008
|
+
: 'Trend: no previous history');
|
|
1009
|
+
lines.push(validationSummaryDivider);
|
|
1010
|
+
lines.push('');
|
|
1011
|
+
lines.push(`Architecture Health Score: ${summary.healthScore} / 100`);
|
|
1012
|
+
if (options?.healthTrend) {
|
|
1013
|
+
lines.push((0, architectureHealthHistoryReporter_1.formatArchitectureHealthTrendLine)(options.healthTrend));
|
|
1014
|
+
}
|
|
1015
|
+
if (options?.driftComparison) {
|
|
1016
|
+
lines.push('');
|
|
1017
|
+
lines.push('Architecture Drift');
|
|
1018
|
+
if (!options.driftComparison.hasPreviousSnapshot) {
|
|
1019
|
+
lines.push('No previous snapshot found. Baseline snapshot created.');
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
lines.push(`New violations introduced: ${options.driftComparison.newViolations.length}`);
|
|
1023
|
+
lines.push(`Resolved violations: ${options.driftComparison.resolvedViolations.length}`);
|
|
1024
|
+
if (options.driftComparison.newViolations.length > 0) {
|
|
1025
|
+
lines.push('');
|
|
1026
|
+
lines.push('New violations:');
|
|
1027
|
+
lines.push(...options.driftComparison.newViolations.map((violation) => formatDriftViolationLine(violation)));
|
|
1028
|
+
}
|
|
1029
|
+
if (options.driftComparison.resolvedViolations.length > 0) {
|
|
1030
|
+
lines.push('');
|
|
1031
|
+
lines.push('Resolved violations:');
|
|
1032
|
+
lines.push(...options.driftComparison.resolvedViolations.map((violation) => formatDriftViolationLine(violation)));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (options?.adrSummary && options.adrSummary.total > 0) {
|
|
1037
|
+
lines.push('');
|
|
1038
|
+
lines.push('Architecture Decision Records');
|
|
1039
|
+
lines.push(`- Total ADRs: ${options.adrSummary.total}`);
|
|
1040
|
+
lines.push(`- Accepted: ${options.adrSummary.accepted}`);
|
|
1041
|
+
lines.push(`- Proposed: ${options.adrSummary.proposed}`);
|
|
1042
|
+
lines.push(`- Deprecated: ${options.adrSummary.deprecated}`);
|
|
1043
|
+
lines.push(`- Superseded: ${options.adrSummary.superseded}`);
|
|
1044
|
+
}
|
|
1045
|
+
if (options?.suppressionSummary && options.suppressionSummary.configuredSuppressions > 0) {
|
|
1046
|
+
lines.push('');
|
|
1047
|
+
lines.push('Suppression Summary');
|
|
1048
|
+
lines.push(`- Active suppressions: ${options.suppressionSummary.activeSuppressions}`);
|
|
1049
|
+
lines.push(`- Expired suppressions: ${options.suppressionSummary.expiredSuppressions}`);
|
|
1050
|
+
lines.push(`- Unused suppressions: ${options.suppressionSummary.unusedSuppressions}`);
|
|
1051
|
+
lines.push(`- Findings suppressed in this run: ${options.suppressionSummary.findingsSuppressed}`);
|
|
1052
|
+
}
|
|
1053
|
+
const categoryLines = Object.entries(summary.categoryScores)
|
|
1054
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1055
|
+
.map(([category, score]) => `- ${category}: ${score}`);
|
|
1056
|
+
if (categoryLines.length > 0) {
|
|
1057
|
+
lines.push('Category Breakdown:');
|
|
1058
|
+
lines.push(...categoryLines);
|
|
1059
|
+
}
|
|
1060
|
+
lines.push(`Counts: errors=${summary.counts.errors}, warnings=${summary.counts.warnings}, info=${summary.counts.info}, passed=${summary.counts.passed}, suppressed=${summary.counts.suppressed}, exceptions=${summary.counts.exceptions ?? 0}, ignoredSuppressions=${summary.counts.ignoredSuppressions}`);
|
|
1061
|
+
lines.push('');
|
|
1062
|
+
const renderSeveritySection = (label, items) => {
|
|
1063
|
+
lines.push(`${label}:`);
|
|
1064
|
+
if (items.length === 0) {
|
|
1065
|
+
lines.push('- (none)');
|
|
1066
|
+
lines.push('');
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
for (const findingGroup of groupReviewFindingsByRule(items)) {
|
|
1070
|
+
const item = findingGroup[0];
|
|
1071
|
+
if (!item) {
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
const explanation = (0, validationRuleExplanations_1.resolveValidationExplanation)(item.id);
|
|
1075
|
+
const relatedAdrs = (0, resolveRuleAdrs_1.resolveRuleAdrs)(item.id, options?.discoveredAdrs ?? []);
|
|
1076
|
+
lines.push(`- [${item.id}] ${item.title}${findingGroup.length > 1 ? ` (${findingGroup.length} findings)` : ''}`);
|
|
1077
|
+
lines.push(` Classification: ${isArchitectureReviewSignalRule(item.id) ? 'Architecture Review Signal' : item.kind}`);
|
|
1078
|
+
const groupedMessages = new Map();
|
|
1079
|
+
for (const entry of findingGroup) {
|
|
1080
|
+
groupedMessages.set(entry.message, (groupedMessages.get(entry.message) ?? 0) + 1);
|
|
1081
|
+
}
|
|
1082
|
+
if (groupedMessages.size === 1) {
|
|
1083
|
+
lines.push(...formatReviewMessageGroup(findingGroup));
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
lines.push(' Findings:');
|
|
1087
|
+
for (const message of [...groupedMessages.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1088
|
+
lines.push(...formatReviewMessageGroup(findingGroup.filter((entry) => entry.message === message), ' '));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
lines.push(` Why this matters: ${explanation.whyItMatters}`);
|
|
1092
|
+
lines.push(` How to fix: ${explanation.howToFix}`);
|
|
1093
|
+
lines.push(` Example: ${explanation.exampleFix}`);
|
|
1094
|
+
lines.push(` Docs: ${explanation.documentationHint}`);
|
|
1095
|
+
if (relatedAdrs.length > 0) {
|
|
1096
|
+
lines.push(' Related ADRs:');
|
|
1097
|
+
for (const adr of relatedAdrs) {
|
|
1098
|
+
lines.push(` - ${adr.id} ${adr.title}${adr.filePath ? ` (${adr.filePath})` : ''}${adr.found ? '' : ' (not found)'}`);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
lines.push('');
|
|
1103
|
+
};
|
|
1104
|
+
renderSeveritySection('Errors', grouped.error);
|
|
1105
|
+
renderSeveritySection('Warnings', grouped.warning);
|
|
1106
|
+
renderSeveritySection('Info', grouped.info);
|
|
1107
|
+
if ((options?.remediation?.fixSuggestions?.length ?? 0) > 0) {
|
|
1108
|
+
lines.push('Fix Suggestions');
|
|
1109
|
+
lines.push('---------------');
|
|
1110
|
+
for (const suggestion of options?.remediation?.fixSuggestions ?? []) {
|
|
1111
|
+
lines.push(suggestion.ruleId);
|
|
1112
|
+
lines.push(`Suggestion: ${suggestion.title}`);
|
|
1113
|
+
lines.push(`Fix command: ${buildFixCommand(suggestion)}`);
|
|
1114
|
+
lines.push('');
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (options?.remediation) {
|
|
1118
|
+
const suggestionCount = options.remediation.fixSuggestions?.length ?? 0;
|
|
1119
|
+
const appliedCount = options.remediation.appliedFixes?.length ?? 0;
|
|
1120
|
+
const remainingCount = options.remediation.remainingViolationsCount ?? summary.findings.length;
|
|
1121
|
+
lines.push('Fix Summary');
|
|
1122
|
+
lines.push(`- Fix suggestions: ${suggestionCount}`);
|
|
1123
|
+
lines.push(`- Applied fixes: ${appliedCount}`);
|
|
1124
|
+
lines.push(`- Remaining violations: ${remainingCount}`);
|
|
1125
|
+
lines.push('');
|
|
1126
|
+
}
|
|
1127
|
+
if (options?.reportPaths) {
|
|
1128
|
+
lines.push('Generated review artifacts:');
|
|
1129
|
+
lines.push(`- Markdown: ${options.reportPaths.markdownPath}`);
|
|
1130
|
+
lines.push(`- JSON: ${options.reportPaths.jsonPath}`);
|
|
1131
|
+
lines.push(`- Compact Markdown: ${options.reportPaths.compactMarkdownPath}`);
|
|
1132
|
+
lines.push('');
|
|
1133
|
+
}
|
|
1134
|
+
if (options?.reportWriteError) {
|
|
1135
|
+
lines.push(`Report generation error: ${options.reportWriteError}`);
|
|
1136
|
+
lines.push('Validation results are shown above. CI artifact generation failed.');
|
|
1137
|
+
}
|
|
1138
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
1139
|
+
}
|
|
1140
|
+
function renderArchitectureReviewMarkdown(summary, options) {
|
|
1141
|
+
const grouped = groupBySeverity(summary.findings);
|
|
1142
|
+
const architectureReviewSignals = grouped.warning.filter((finding) => isArchitectureReviewSignalRule(finding.id));
|
|
1143
|
+
const nonSignalWarnings = grouped.warning.filter((finding) => !isArchitectureReviewSignalRule(finding.id));
|
|
1144
|
+
const topIssues = [...summary.findings].slice(0, topIssuesLimit);
|
|
1145
|
+
const moduleCatalog = buildModuleCatalog(options?.dependencyGraphSummary, options?.modulePaths);
|
|
1146
|
+
const dependencyCount = options?.dependencyGraphSummary?.modules.reduce((total, moduleSummary) => total + moduleSummary.actualDependencies.length, 0) ?? 0;
|
|
1147
|
+
const recommendedActions = buildRecommendedNextActions(summary);
|
|
1148
|
+
const adrDisplay = resolveAdrRequirementDisplay({
|
|
1149
|
+
enforcementAction: summary.enforcement.action,
|
|
1150
|
+
adrRequirement: summary.adrRequirement,
|
|
1151
|
+
findings: summary.findings,
|
|
1152
|
+
});
|
|
1153
|
+
const renderFindingGroup = (findingGroup) => {
|
|
1154
|
+
const finding = findingGroup[0];
|
|
1155
|
+
if (!finding) {
|
|
1156
|
+
return [];
|
|
1157
|
+
}
|
|
1158
|
+
const explanation = (0, validationRuleExplanations_1.resolveValidationExplanation)(finding.id);
|
|
1159
|
+
const relatedAdrs = (0, resolveRuleAdrs_1.resolveRuleAdrs)(finding.id, options?.discoveredAdrs ?? []);
|
|
1160
|
+
const relatedAdrLine = relatedAdrs.length === 0
|
|
1161
|
+
? [' - Related ADRs: (none)']
|
|
1162
|
+
: [
|
|
1163
|
+
' - Related ADRs:',
|
|
1164
|
+
...relatedAdrs.map((adr) => ` - ${adr.id} ${adr.title}${adr.filePath ? ` (${adr.filePath})` : ''}${adr.found ? '' : ' (not found)'}`),
|
|
1165
|
+
];
|
|
1166
|
+
const lines = [
|
|
1167
|
+
`- [${finding.id}] ${finding.title}${findingGroup.length > 1 ? ` (${findingGroup.length} findings)` : ''}`,
|
|
1168
|
+
` - Category: ${reviewCategoryLabels[finding.category] ?? finding.category}`,
|
|
1169
|
+
` - Severity: ${finding.severity}`,
|
|
1170
|
+
` - Classification: ${isArchitectureReviewSignalRule(finding.id)
|
|
1171
|
+
? 'Architecture Review Signal'
|
|
1172
|
+
: finding.kind}`,
|
|
1173
|
+
];
|
|
1174
|
+
const groupedMessages = new Map();
|
|
1175
|
+
for (const entry of findingGroup) {
|
|
1176
|
+
groupedMessages.set(entry.message, (groupedMessages.get(entry.message) ?? 0) + 1);
|
|
1177
|
+
}
|
|
1178
|
+
if (groupedMessages.size === 1) {
|
|
1179
|
+
lines.push(...formatReviewMessageGroup(findingGroup));
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
lines.push(' - Findings:');
|
|
1183
|
+
for (const message of [...groupedMessages.keys()].sort((left, right) => left.localeCompare(right))) {
|
|
1184
|
+
lines.push(...formatReviewMessageGroup(findingGroup.filter((entry) => entry.message === message), ' '));
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
lines.push(` - Why this matters: ${explanation.whyItMatters}`);
|
|
1188
|
+
lines.push(` - How to fix: ${explanation.howToFix}`);
|
|
1189
|
+
lines.push(` - Example: ${explanation.exampleFix}`);
|
|
1190
|
+
lines.push(` - Documentation hint: ${explanation.documentationHint}`);
|
|
1191
|
+
lines.push(...relatedAdrLine);
|
|
1192
|
+
return lines;
|
|
1193
|
+
};
|
|
1194
|
+
const renderSection = (title, findings) => `## ${title}\n${findings.length === 0
|
|
1195
|
+
? '- (none)'
|
|
1196
|
+
: groupReviewFindingsByRule(findings)
|
|
1197
|
+
.map((findingGroup) => renderFindingGroup(findingGroup).join('\n'))
|
|
1198
|
+
.join('\n')}\n`;
|
|
1199
|
+
const suppressedSection = summary.suppressedFindings.length === 0
|
|
1200
|
+
? '- (none)'
|
|
1201
|
+
: summary.suppressedFindings
|
|
1202
|
+
.map((finding) => {
|
|
1203
|
+
const lineSuffix = finding.line !== undefined ? `:${finding.line}` : '';
|
|
1204
|
+
const reason = finding.suppressionReason
|
|
1205
|
+
? `\n - Reason: ${finding.suppressionReason}`
|
|
1206
|
+
: '';
|
|
1207
|
+
return `- [${finding.id}] ${finding.title}\n - Severity: ${finding.severity}\n - Category: ${finding.category}\n - Message: ${finding.message}\n - Location: ${finding.filePath}${lineSuffix}\n - Suppression Path: ${finding.suppressionPath}${reason}`;
|
|
1208
|
+
})
|
|
1209
|
+
.join('\n');
|
|
1210
|
+
const passedSummary = summary.passedChecks.length === 0
|
|
1211
|
+
? '- (none)'
|
|
1212
|
+
: groupReviewFindingsByRule(summary.passedChecks)
|
|
1213
|
+
.map((findingGroup) => {
|
|
1214
|
+
const finding = findingGroup[0];
|
|
1215
|
+
if (!finding) {
|
|
1216
|
+
return '';
|
|
1217
|
+
}
|
|
1218
|
+
return [
|
|
1219
|
+
`- [${finding.id}] ${finding.title}${findingGroup.length > 1 ? ` (${findingGroup.length} findings)` : ''}`,
|
|
1220
|
+
` - Category: ${reviewCategoryLabels[finding.category] ?? finding.category}`,
|
|
1221
|
+
' - Status: Passed',
|
|
1222
|
+
...formatReviewMessageGroup(findingGroup),
|
|
1223
|
+
].join('\n');
|
|
1224
|
+
})
|
|
1225
|
+
.filter((entry) => entry.length > 0)
|
|
1226
|
+
.join('\n');
|
|
1227
|
+
const topIssuesSection = topIssues.length === 0
|
|
1228
|
+
? '- (none)'
|
|
1229
|
+
: topIssues
|
|
1230
|
+
.map((finding) => `- [${finding.id}] ${finding.title} (${finding.severity}) | ${summarizeReviewFindingForInlineDisplay(finding)}`)
|
|
1231
|
+
.join('\n');
|
|
1232
|
+
const impactSection = options?.impactAnalysisSection
|
|
1233
|
+
? `## Impact Analysis\n${options.impactAnalysisSection}\n\n`
|
|
1234
|
+
: '';
|
|
1235
|
+
const fixSuggestionsSection = (options?.remediation?.fixSuggestions?.length ?? 0) === 0
|
|
1236
|
+
? '- (none)'
|
|
1237
|
+
: (options?.remediation?.fixSuggestions ?? [])
|
|
1238
|
+
.map((suggestion) => `- ${suggestion.ruleId} | ${suggestion.title} | command: ${buildFixCommand(suggestion)}`)
|
|
1239
|
+
.join('\n');
|
|
1240
|
+
const appliedFixesSection = (options?.remediation?.appliedFixes?.length ?? 0) === 0
|
|
1241
|
+
? '- (none)'
|
|
1242
|
+
: (options?.remediation?.appliedFixes ?? [])
|
|
1243
|
+
.map((applied) => `- ${applied.ruleId} / ${applied.fixId} | ${applied.title} | ${applied.description}`)
|
|
1244
|
+
.join('\n');
|
|
1245
|
+
const remainingViolationsLine = `- Remaining violations: ${options?.remediation?.remainingViolationsCount ?? summary.findings.length}`;
|
|
1246
|
+
const architectureSignalSummaryLines = [
|
|
1247
|
+
`- Heuristic architecture smell findings: ${summary.architectureSmellSummary.count}`,
|
|
1248
|
+
`- Explanation: ${summary.architectureSmellSummary.explanation}`,
|
|
1249
|
+
...(summary.architectureSmellSummary.ruleFamilies.length === 0
|
|
1250
|
+
? ['- Families: (none)']
|
|
1251
|
+
: summary.architectureSmellSummary.ruleFamilies.map((family) => `- ${family.family}: ${family.count} (${family.rules
|
|
1252
|
+
.map((rule) => `${rule.ruleId}=${rule.count}`)
|
|
1253
|
+
.join(', ')})`)),
|
|
1254
|
+
];
|
|
1255
|
+
const scoreExplanationLines = [
|
|
1256
|
+
`- Errors: ${summary.scoreExplanation.errors}`,
|
|
1257
|
+
`- Warnings: ${summary.scoreExplanation.warnings}`,
|
|
1258
|
+
`- Heuristic warnings: ${summary.scoreExplanation.heuristicWarnings}`,
|
|
1259
|
+
`- Weighted base health: ${summary.scoreExplanation.weightedBaseScore} / 100`,
|
|
1260
|
+
`- Applicable category weights: ${summary.scoreExplanation.applicableCategoryScores
|
|
1261
|
+
.map((entry) => `${entry.category}=${entry.score} x ${Math.round(entry.weight * 100)}%`)
|
|
1262
|
+
.join(', ') || '(none)'}`,
|
|
1263
|
+
`- Highest-impact categories: ${summary.scoreExplanation.highestImpactCategories
|
|
1264
|
+
.map((entry) => `${entry.category}=${entry.score}`)
|
|
1265
|
+
.join(', ') || '(none)'}`,
|
|
1266
|
+
`- Why score changed: ${summary.scoreExplanation.whyScoreChanged}`,
|
|
1267
|
+
];
|
|
1268
|
+
return `# ArchPilot Architecture Review Report
|
|
1269
|
+
|
|
1270
|
+
- Project: ${summary.projectName}
|
|
1271
|
+
- Timestamp (UTC): ${summary.generatedAtUtc}
|
|
1272
|
+
- Generated timestamp: ${summary.generatedAtUtc}
|
|
1273
|
+
- Commit SHA: ${options?.commitSha ?? 'Not available in this run'}
|
|
1274
|
+
- Report version: ${summary.reportVersion}
|
|
1275
|
+
- Health score: ${summary.healthScore}/100
|
|
1276
|
+
- Readiness score: ${summary.readinessScore}/100
|
|
1277
|
+
- Category scores: ${Object.entries(summary.categoryScores)
|
|
1278
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1279
|
+
.map(([category, score]) => `${category}=${score}/100`)
|
|
1280
|
+
.join(', ')}
|
|
1281
|
+
- Errors: ${summary.counts.errors}
|
|
1282
|
+
- Warnings: ${summary.counts.warnings}
|
|
1283
|
+
- Info: ${summary.counts.info}
|
|
1284
|
+
- Architecture findings: ${summary.qualityFindings}
|
|
1285
|
+
- Setup gaps: ${summary.setupGaps}
|
|
1286
|
+
- Guidance findings: ${summary.guidanceFindings}
|
|
1287
|
+
- Architecture Review Level: ${summary.risk.level.toUpperCase()}
|
|
1288
|
+
- Enforcement (policy outcome): ${summary.enforcement.action.toUpperCase()}
|
|
1289
|
+
- Change risk (diff-aware PR impact): ${summary.changeRisk.riskLevel.toUpperCase()}
|
|
1290
|
+
- Impact radius level: ${summary.impactAnalysis.impactLevel}
|
|
1291
|
+
- Baseline comparison: ${summary.baselineComparison?.status ?? 'NO_BASELINE'}
|
|
1292
|
+
- Passed: ${summary.counts.passed}
|
|
1293
|
+
- Suppressed: ${summary.counts.suppressed}
|
|
1294
|
+
- Exceptions: ${summary.counts.exceptions ?? 0}
|
|
1295
|
+
- Ignored suppressions: ${summary.counts.ignoredSuppressions}
|
|
1296
|
+
|
|
1297
|
+
## Module List and Roles
|
|
1298
|
+
${moduleCatalog.length === 0
|
|
1299
|
+
? '- Module details are not available in this run.'
|
|
1300
|
+
: moduleCatalog
|
|
1301
|
+
.map((entry) => `- ${entry.moduleName} | Role: ${entry.role} | Path: ${entry.path ?? '(not configured)'} | Contract: ${entry.hasContract ? 'yes' : 'no'} | Declared deps: ${entry.declaredDependencyCount} | Actual deps: ${entry.actualDependencyCount} | Used by: ${entry.inboundDependencyCount}`)
|
|
1302
|
+
.join('\n')}
|
|
1303
|
+
|
|
1304
|
+
## Dependency Graph Summary
|
|
1305
|
+
- Modules: ${moduleCatalog.length}
|
|
1306
|
+
- Dependencies: ${dependencyCount}
|
|
1307
|
+
- Modules with contracts: ${moduleCatalog.filter((entry) => entry.hasContract).length}
|
|
1308
|
+
- Orphan / isolated detection: See validation findings and architecture map outputs when generated.
|
|
1309
|
+
|
|
1310
|
+
## Violations Summary
|
|
1311
|
+
- Total active findings: ${summary.findings.length}
|
|
1312
|
+
- Raw findings: ${summary.rawFindingCount}
|
|
1313
|
+
- Normalized findings: ${summary.normalizedFindingCount}
|
|
1314
|
+
- Duplicate reduction: ${summary.duplicateReductionCount}
|
|
1315
|
+
- Blocking errors: ${summary.counts.errors}
|
|
1316
|
+
- Warnings: ${summary.counts.warnings}
|
|
1317
|
+
- Info: ${summary.counts.info}
|
|
1318
|
+
- Setup gaps: ${summary.setupGaps}
|
|
1319
|
+
- Guidance findings: ${summary.guidanceFindings}
|
|
1320
|
+
|
|
1321
|
+
## Score Explanation
|
|
1322
|
+
${scoreExplanationLines.join('\n')}
|
|
1323
|
+
|
|
1324
|
+
## Largest Score Contributors
|
|
1325
|
+
${summary.scoreExplanation.largestScoreContributors.length === 0
|
|
1326
|
+
? '- (none)'
|
|
1327
|
+
: summary.scoreExplanation.largestScoreContributors.map((entry) => `- ${entry}`).join('\n')}
|
|
1328
|
+
|
|
1329
|
+
## Architecture Drift
|
|
1330
|
+
${options?.driftReport
|
|
1331
|
+
? `- Drift detected: ${options.driftReport.summaryCounts.totalChanges > 0 ? 'YES' : 'NO'}
|
|
1332
|
+
- Baseline snapshot: ${options.driftReport.baselineCreatedAtUtc}
|
|
1333
|
+
- Current snapshot: ${options.driftReport.generatedAtUtc}
|
|
1334
|
+
- Total changes: ${options.driftReport.summaryCounts.totalChanges}
|
|
1335
|
+
- Modules added: ${options.driftReport.summaryCounts.modulesAdded}
|
|
1336
|
+
- Modules removed: ${options.driftReport.summaryCounts.modulesRemoved}
|
|
1337
|
+
- Dependencies added: ${options.driftReport.summaryCounts.dependenciesAdded}
|
|
1338
|
+
- Dependencies removed: ${options.driftReport.summaryCounts.dependenciesRemoved}`
|
|
1339
|
+
: '- No baseline drift report is available. Run `archpilot baseline check` to generate drift-report.md details.'}
|
|
1340
|
+
|
|
1341
|
+
## Category Breakdown
|
|
1342
|
+
- Architecture health:
|
|
1343
|
+
${Object.entries(summary.categoryScores)
|
|
1344
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1345
|
+
.map(([category, score]) => ` - ${reviewCategoryLabels[category] ?? category}: ${score} / 100`)
|
|
1346
|
+
.join('\n')}
|
|
1347
|
+
- Setup readiness:
|
|
1348
|
+
${Object.entries(summary.readinessCategoryScores)
|
|
1349
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1350
|
+
.map(([category, score]) => ` - ${reviewCategoryLabels[category] ?? category}: ${score} / 100`)
|
|
1351
|
+
.join('\n')}
|
|
1352
|
+
|
|
1353
|
+
## Architecture Review Signals
|
|
1354
|
+
${architectureSignalSummaryLines.join('\n')}
|
|
1355
|
+
${renderSection('Architecture Review Signals', architectureReviewSignals).replace(/^## Architecture Review Signals\n/u, '')}
|
|
1356
|
+
|
|
1357
|
+
## Governance Summary
|
|
1358
|
+
- Architecture Review Level: ${summary.risk.level.toUpperCase()}
|
|
1359
|
+
- Change Risk: ${summary.changeRisk.riskLevel.toUpperCase()}
|
|
1360
|
+
- Enforcement Outcome: ${summary.enforcement.enforcementDecision ?? summary.enforcement.action.toUpperCase()}
|
|
1361
|
+
- Policy Level: ${summary.enforcement.policyLevel?.toUpperCase() ?? 'N/A'}
|
|
1362
|
+
- Approved exceptions: ${summary.counts.exceptions ?? 0}
|
|
1363
|
+
- Suppressed findings: ${summary.counts.suppressed}
|
|
1364
|
+
|
|
1365
|
+
## Architecture Review Level
|
|
1366
|
+
- Level: ${summary.risk.level.toUpperCase()}
|
|
1367
|
+
- Reasons:
|
|
1368
|
+
${summary.risk.reasons.map((reason) => ` - ${reason}`).join('\n')}
|
|
1369
|
+
|
|
1370
|
+
## Enforcement Decision (Policy Outcome)
|
|
1371
|
+
- Action: ${summary.enforcement.action.toUpperCase()}
|
|
1372
|
+
- Policy level: ${summary.enforcement.policyLevel?.toUpperCase() ?? 'N/A'}
|
|
1373
|
+
- Enforcement decision: ${summary.enforcement.enforcementDecision ?? 'N/A'}
|
|
1374
|
+
- Matched threshold: ${summary.enforcement.matchedThreshold
|
|
1375
|
+
? `${summary.enforcement.matchedThreshold.id ?? '(unknown)'} (configured=${summary.enforcement.matchedThreshold.configuredValue ?? 'n/a'}, matched=${summary.enforcement.matchedThreshold.matchedValue ?? 'n/a'}, source=${summary.enforcement.matchedThreshold.source ?? summary.enforcement.thresholdSource ?? 'n/a'})`
|
|
1376
|
+
: 'N/A'}
|
|
1377
|
+
- Baseline contribution: ${summary.enforcement.baselineContribution
|
|
1378
|
+
? summary.enforcement.baselineContribution.elevated
|
|
1379
|
+
? 'ELEVATED'
|
|
1380
|
+
: 'NOT_ELEVATED'
|
|
1381
|
+
: 'N/A'}
|
|
1382
|
+
- Baseline reason: ${summary.enforcement.baselineContribution?.reason ?? 'N/A'}
|
|
1383
|
+
- CI exit behavior: ${summary.enforcement.ciExitBehavior
|
|
1384
|
+
? `exitCode=${summary.enforcement.ciExitBehavior.exitCode}, blocking=${summary.enforcement.ciExitBehavior.blocking ? 'yes' : 'no'}`
|
|
1385
|
+
: 'N/A'}
|
|
1386
|
+
- CI exit reason: ${summary.enforcement.ciExitBehavior?.reason ?? 'N/A'}
|
|
1387
|
+
- Reasons:
|
|
1388
|
+
${summary.enforcement.reasons.map((reason) => ` - ${reason}`).join('\n')}
|
|
1389
|
+
- Required Actions:
|
|
1390
|
+
${(summary.enforcement.requiredActions ?? []).length > 0
|
|
1391
|
+
? summary.enforcement.requiredActions?.map((action) => ` - ${action}`).join('\n')
|
|
1392
|
+
: ' - (none)'}
|
|
1393
|
+
|
|
1394
|
+
## ADR Requirement
|
|
1395
|
+
- Required: ${adrDisplay.requiredLabel}
|
|
1396
|
+
- Satisfied: ${adrDisplay.satisfiedLabel}
|
|
1397
|
+
- Reasons:
|
|
1398
|
+
${adrDisplay.reasons.length === 0
|
|
1399
|
+
? ' - (none)'
|
|
1400
|
+
: adrDisplay.reasons.map((reason) => ` - ${reason}`).join('\n')}
|
|
1401
|
+
- Suggested command:
|
|
1402
|
+
${adrDisplay.state === 'required' && summary.adrRequirement.suggestedCommand
|
|
1403
|
+
? ` - ${summary.adrRequirement.suggestedCommand}`
|
|
1404
|
+
: ' - (none)'}
|
|
1405
|
+
- Suggested filename hint:
|
|
1406
|
+
${adrDisplay.state === 'required' && summary.adrRequirement.suggestedFilenameHint
|
|
1407
|
+
? ` - ${summary.adrRequirement.suggestedFilenameHint}`
|
|
1408
|
+
: ' - (none)'}
|
|
1409
|
+
|
|
1410
|
+
## Recommended Deployment Safety Controls
|
|
1411
|
+
${summary.safetyControls
|
|
1412
|
+
? `- Triggered: ${summary.safetyControls.triggered ? 'YES' : 'NO'}
|
|
1413
|
+
- Applicable: ${summary.safetyControls.applicable ? 'YES' : 'NO'}
|
|
1414
|
+
- Summary: ${summary.safetyControls.summary}
|
|
1415
|
+
- Trigger reasons:
|
|
1416
|
+
${summary.safetyControls.triggerReasons.length === 0
|
|
1417
|
+
? ' - (none)'
|
|
1418
|
+
: summary.safetyControls.triggerReasons.map((reason) => ` - ${reason}`).join('\n')}
|
|
1419
|
+
- Missing controls:
|
|
1420
|
+
${summary.safetyControls.findings.length === 0
|
|
1421
|
+
? ' - (none)'
|
|
1422
|
+
: summary.safetyControls.findings
|
|
1423
|
+
.map((finding) => ` - [${finding.ruleId}] ${finding.message}`)
|
|
1424
|
+
.join('\n')}`
|
|
1425
|
+
: '- Safety controls analysis not available in this run.'}
|
|
1426
|
+
|
|
1427
|
+
## Change Risk (Diff-Aware PR Impact)
|
|
1428
|
+
- Risk level: ${summary.changeRisk.riskLevel.toUpperCase()}
|
|
1429
|
+
- Summary: ${summary.changeRisk.summary}
|
|
1430
|
+
- Score: ${summary.changeRisk.score}
|
|
1431
|
+
- Score impact: ${summary.changeRisk.scoreImpact}
|
|
1432
|
+
- Signals: ${Object.entries(summary.changeRisk.signals)
|
|
1433
|
+
.map(([key, value]) => `${key}=${String(value)}`)
|
|
1434
|
+
.join(', ')}
|
|
1435
|
+
- Reasons:
|
|
1436
|
+
${summary.changeRisk.reasons.map((reason) => ` - ${reason}`).join('\n')}
|
|
1437
|
+
- Triggered factors:
|
|
1438
|
+
${summary.changeRisk.factors.length === 0
|
|
1439
|
+
? ' - (none)'
|
|
1440
|
+
: summary.changeRisk.factors
|
|
1441
|
+
.map((factor) => ` - ${factor.id} | ${factor.label} | ${factor.evidence}`)
|
|
1442
|
+
.join('\n')}
|
|
1443
|
+
- Changed modules:
|
|
1444
|
+
${summary.changeRisk.changedModules.length === 0
|
|
1445
|
+
? ' - (none)'
|
|
1446
|
+
: summary.changeRisk.changedModules.map((moduleName) => ` - ${moduleName}`).join('\n')}
|
|
1447
|
+
- Directly affected modules:
|
|
1448
|
+
${summary.changeRisk.directlyAffectedModules.length === 0
|
|
1449
|
+
? ' - (none)'
|
|
1450
|
+
: summary.changeRisk.directlyAffectedModules
|
|
1451
|
+
.map((moduleName) => ` - ${moduleName}`)
|
|
1452
|
+
.join('\n')}
|
|
1453
|
+
- Transitively affected modules:
|
|
1454
|
+
${summary.changeRisk.transitivelyAffectedModules.length === 0
|
|
1455
|
+
? ' - (none)'
|
|
1456
|
+
: summary.changeRisk.transitivelyAffectedModules
|
|
1457
|
+
.map((moduleName) => ` - ${moduleName}`)
|
|
1458
|
+
.join('\n')}
|
|
1459
|
+
|
|
1460
|
+
## Impact Analysis
|
|
1461
|
+
- Changed Modules: ${summary.impactAnalysis.changedModules.join(', ') || '(none)'}
|
|
1462
|
+
- Direct Dependents: ${summary.impactAnalysis.directDependents.join(', ') || '(none)'}
|
|
1463
|
+
- Transitive Dependents: ${summary.impactAnalysis.transitiveDependents.join(', ') || '(none)'}
|
|
1464
|
+
- Total Affected Modules: ${summary.impactAnalysis.totalAffectedModules}
|
|
1465
|
+
- Impact Radius Score: ${summary.impactAnalysis.impactRadiusScore}
|
|
1466
|
+
- Impact Level: ${summary.impactAnalysis.impactLevel}
|
|
1467
|
+
|
|
1468
|
+
## Baseline Comparison
|
|
1469
|
+
${(summary.baselineComparison ?? options?.baselineComparison)
|
|
1470
|
+
? `- Baseline status: ${(summary.baselineComparison ?? options?.baselineComparison)?.status}
|
|
1471
|
+
- Health score delta: ${(summary.baselineComparison ?? options?.baselineComparison)?.deltas.healthScoreDelta}
|
|
1472
|
+
- Readiness score delta: ${(summary.baselineComparison ?? options?.baselineComparison)?.deltas.readinessScoreDelta}
|
|
1473
|
+
- New findings: ${(summary.baselineComparison ?? options?.baselineComparison)?.newlyIntroducedFindings.length}
|
|
1474
|
+
- Resolved findings: ${(summary.baselineComparison ?? options?.baselineComparison)?.resolvedFindings.length}
|
|
1475
|
+
- Summary: ${(summary.baselineComparison ?? options?.baselineComparison)?.summary}`
|
|
1476
|
+
: '- Baseline status: NO_BASELINE'}
|
|
1477
|
+
|
|
1478
|
+
${renderSection('Errors', grouped.error)}
|
|
1479
|
+
${renderSection('Warnings', nonSignalWarnings)}
|
|
1480
|
+
${renderSection('Info', grouped.info)}
|
|
1481
|
+
## Top Issues To Fix First
|
|
1482
|
+
${topIssuesSection}
|
|
1483
|
+
|
|
1484
|
+
## Suppressed Findings
|
|
1485
|
+
${suppressedSection}
|
|
1486
|
+
|
|
1487
|
+
## Suppression Summary
|
|
1488
|
+
${options?.suppressionSummary
|
|
1489
|
+
? `- Active suppressions: ${options.suppressionSummary.activeSuppressions}
|
|
1490
|
+
- Expired suppressions: ${options.suppressionSummary.expiredSuppressions}
|
|
1491
|
+
- Unused suppressions: ${options.suppressionSummary.unusedSuppressions}
|
|
1492
|
+
- Findings suppressed in this run: ${options.suppressionSummary.findingsSuppressed}`
|
|
1493
|
+
: '- (none)'}
|
|
1494
|
+
|
|
1495
|
+
## Passed Checks
|
|
1496
|
+
${passedSummary}
|
|
1497
|
+
|
|
1498
|
+
## Fix Suggestions
|
|
1499
|
+
${fixSuggestionsSection}
|
|
1500
|
+
|
|
1501
|
+
## Applied Fixes
|
|
1502
|
+
${appliedFixesSection}
|
|
1503
|
+
|
|
1504
|
+
## Remaining Violations
|
|
1505
|
+
${remainingViolationsLine}
|
|
1506
|
+
|
|
1507
|
+
## Module Dependency Summary
|
|
1508
|
+
${moduleCatalog.length === 0
|
|
1509
|
+
? '- Module details are not available in this run.'
|
|
1510
|
+
: moduleCatalog
|
|
1511
|
+
.map((entry) => `- ${entry.moduleName} | Role: ${entry.role} | Path: ${entry.path ?? '(not configured)'} | Contract: ${entry.hasContract ? 'yes' : 'no'} | Declared deps: ${entry.declaredDependencyCount} | Actual deps: ${entry.actualDependencyCount} | Used by: ${entry.inboundDependencyCount}`)
|
|
1512
|
+
.join('\n')}
|
|
1513
|
+
|
|
1514
|
+
## Suggested Next Actions
|
|
1515
|
+
${recommendedActions.length === 0
|
|
1516
|
+
? '- (none)'
|
|
1517
|
+
: recommendedActions.map((action) => `- ${action}`).join('\n')}
|
|
1518
|
+
|
|
1519
|
+
${impactSection}`.trimEnd() + '\n';
|
|
1520
|
+
}
|
|
1521
|
+
function renderArchitectureReviewJson(summary, options) {
|
|
1522
|
+
const moduleCount = options?.dependencyGraphSummary?.modules.length;
|
|
1523
|
+
const dependencyCount = options?.dependencyGraphSummary?.modules.reduce((total, moduleSummary) => total + moduleSummary.actualDependencies.length, 0);
|
|
1524
|
+
return `${JSON.stringify({
|
|
1525
|
+
...(options?.metadata ? { xArchPilot: options.metadata } : {}),
|
|
1526
|
+
reportVersion: summary.reportVersion,
|
|
1527
|
+
projectName: summary.projectName,
|
|
1528
|
+
generatedAtUtc: summary.generatedAtUtc,
|
|
1529
|
+
timestamp: summary.generatedAtUtc,
|
|
1530
|
+
healthScore: summary.healthScore,
|
|
1531
|
+
architectureScore: summary.healthScore,
|
|
1532
|
+
errorCount: summary.counts.errors,
|
|
1533
|
+
warningCount: summary.counts.warnings,
|
|
1534
|
+
infoCount: summary.counts.info,
|
|
1535
|
+
categoryScores: summary.categoryScores,
|
|
1536
|
+
readinessScore: summary.readinessScore,
|
|
1537
|
+
readinessCategoryScores: summary.readinessCategoryScores,
|
|
1538
|
+
setupGaps: summary.setupGaps,
|
|
1539
|
+
guidanceFindings: summary.guidanceFindings,
|
|
1540
|
+
qualityFindings: summary.qualityFindings,
|
|
1541
|
+
risk: summary.risk,
|
|
1542
|
+
governanceRisk: summary.risk.level.toUpperCase(),
|
|
1543
|
+
enforcement: summary.enforcement,
|
|
1544
|
+
enforcementOutcome: summary.enforcement.enforcementDecision ?? summary.enforcement.action.toUpperCase(),
|
|
1545
|
+
adrRequirement: summary.adrRequirement,
|
|
1546
|
+
changeRisk: summary.changeRisk,
|
|
1547
|
+
changeRiskLevel: summary.changeRisk.riskLevel.toUpperCase(),
|
|
1548
|
+
impactAnalysis: summary.impactAnalysis,
|
|
1549
|
+
...(moduleCount !== undefined ? { moduleCount } : {}),
|
|
1550
|
+
...(dependencyCount !== undefined ? { dependencyCount } : {}),
|
|
1551
|
+
...(options?.driftReport
|
|
1552
|
+
? { driftDetected: options.driftReport.summaryCounts.totalChanges > 0 }
|
|
1553
|
+
: options?.driftDetected !== undefined
|
|
1554
|
+
? { driftDetected: options.driftDetected }
|
|
1555
|
+
: {}),
|
|
1556
|
+
...(options?.commitSha ? { commitSha: options.commitSha } : {}),
|
|
1557
|
+
...(summary.safetyControls ? { safetyControls: summary.safetyControls } : {}),
|
|
1558
|
+
...(summary.baselineComparison
|
|
1559
|
+
? { baselineComparison: summary.baselineComparison }
|
|
1560
|
+
: {}),
|
|
1561
|
+
counts: summary.counts,
|
|
1562
|
+
findingCount: summary.findings.length,
|
|
1563
|
+
findings: summary.findings,
|
|
1564
|
+
...(summary.componentSummary ? { componentSummary: summary.componentSummary } : {}),
|
|
1565
|
+
passedCount: summary.passedChecks.length,
|
|
1566
|
+
suppressedCount: summary.counts.suppressed,
|
|
1567
|
+
ignoredSuppressionsCount: summary.counts.ignoredSuppressions,
|
|
1568
|
+
suppressionSummary: options?.suppressionSummary,
|
|
1569
|
+
fixSuggestions: options?.remediation?.fixSuggestions ?? [],
|
|
1570
|
+
appliedFixes: options?.remediation?.appliedFixes ?? [],
|
|
1571
|
+
remainingViolationsCount: options?.remediation?.remainingViolationsCount ?? summary.findings.length,
|
|
1572
|
+
findingNormalization: summary.findingNormalization,
|
|
1573
|
+
rawFindingCount: summary.rawFindingCount,
|
|
1574
|
+
normalizedFindingCount: summary.normalizedFindingCount,
|
|
1575
|
+
duplicateReductionCount: summary.duplicateReductionCount,
|
|
1576
|
+
heuristicArchitectureSmellCount: summary.heuristicArchitectureSmellCount,
|
|
1577
|
+
architectureSmellSummary: summary.architectureSmellSummary,
|
|
1578
|
+
scoreExplanation: summary.scoreExplanation,
|
|
1579
|
+
}, null, 2)}\n`;
|
|
1580
|
+
}
|
|
1581
|
+
function renderArchitectureReviewCompactMarkdown(summary, options) {
|
|
1582
|
+
const topFindings = [...summary.findings].slice(0, compactTopFindingsLimit);
|
|
1583
|
+
const adrDisplay = resolveAdrRequirementDisplay({
|
|
1584
|
+
enforcementAction: summary.enforcement.action,
|
|
1585
|
+
adrRequirement: summary.adrRequirement,
|
|
1586
|
+
findings: summary.findings,
|
|
1587
|
+
});
|
|
1588
|
+
const topFindingsLines = topFindings.length === 0
|
|
1589
|
+
? '- (none)'
|
|
1590
|
+
: topFindings
|
|
1591
|
+
.map((finding) => `- [${finding.id}] ${finding.title} (${finding.severity}) | ${finding.message}`)
|
|
1592
|
+
.join('\n');
|
|
1593
|
+
return `## ArchPilot Architecture Review
|
|
1594
|
+
|
|
1595
|
+
- Health score: ${summary.healthScore}/100
|
|
1596
|
+
- Readiness score: ${summary.readinessScore}/100
|
|
1597
|
+
- Category scores: ${Object.entries(summary.categoryScores)
|
|
1598
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1599
|
+
.map(([category, score]) => `${category}=${score}`)
|
|
1600
|
+
.join(', ')}
|
|
1601
|
+
- Errors: ${summary.counts.errors}
|
|
1602
|
+
- Warnings: ${summary.counts.warnings}
|
|
1603
|
+
- Info: ${summary.counts.info}
|
|
1604
|
+
- Passed: ${summary.counts.passed}
|
|
1605
|
+
- Suppressed: ${summary.counts.suppressed}
|
|
1606
|
+
- Exceptions: ${summary.counts.exceptions ?? 0}
|
|
1607
|
+
- Architecture Review Level: ${summary.risk.level.toUpperCase()}
|
|
1608
|
+
- Enforcement (Policy Outcome): ${summary.enforcement.action.toUpperCase()}
|
|
1609
|
+
- Policy Level: ${summary.enforcement.policyLevel?.toUpperCase() ?? 'N/A'}
|
|
1610
|
+
- ADR Required: ${adrDisplay.requiredLabel}
|
|
1611
|
+
- ADR Satisfied: ${adrDisplay.satisfiedLabel}
|
|
1612
|
+
- ADR Suggested command: ${adrDisplay.state === 'required'
|
|
1613
|
+
? summary.adrRequirement.suggestedCommand ?? '(none)'
|
|
1614
|
+
: '(none)'}
|
|
1615
|
+
- Recommended Deployment Safety Controls: ${(summary.safetyControls?.findings.length ?? 0) > 0
|
|
1616
|
+
? `${summary.safetyControls?.findings.length} finding(s)`
|
|
1617
|
+
: summary.safetyControls?.triggered
|
|
1618
|
+
? 'none'
|
|
1619
|
+
: 'not-applicable'}
|
|
1620
|
+
- Change Risk (Diff-Aware PR Impact): ${summary.changeRisk.riskLevel.toUpperCase()}
|
|
1621
|
+
- Change Risk Summary: ${summary.changeRisk.summary}
|
|
1622
|
+
- Impact Analysis: ${summary.impactAnalysis.impactLevel} (radius ${summary.impactAnalysis.impactRadiusScore})
|
|
1623
|
+
- Baseline Comparison: ${summary.baselineComparison?.status ?? 'NO_BASELINE'}
|
|
1624
|
+
- Remaining violations: ${options?.remediation?.remainingViolationsCount ?? summary.findings.length}
|
|
1625
|
+
|
|
1626
|
+
### Top Failing Findings
|
|
1627
|
+
${topFindingsLines}
|
|
1628
|
+
|
|
1629
|
+
### Passed Summary
|
|
1630
|
+
- ${summary.counts.passed} checks passed.
|
|
1631
|
+
`.trimEnd() + '\n';
|
|
1632
|
+
}
|
|
1633
|
+
//# sourceMappingURL=architectureReviewReporter.js.map
|