@mergesignal/shared 0.4.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actionsStepSummary.d.ts +1 -22
- package/dist/actionsStepSummary.d.ts.map +1 -1
- package/dist/actionsStepSummary.js +19 -716
- package/dist/actionsStepSummary.test.js +34 -329
- package/dist/assessmentLabels.d.ts +6 -0
- package/dist/assessmentLabels.d.ts.map +1 -0
- package/dist/assessmentLabels.js +33 -0
- package/dist/assessmentProjection.d.ts +12 -0
- package/dist/assessmentProjection.d.ts.map +1 -0
- package/dist/assessmentProjection.js +100 -0
- package/dist/assessmentSchema.d.ts +155 -0
- package/dist/assessmentSchema.d.ts.map +1 -0
- package/dist/assessmentSchema.js +110 -0
- package/dist/cardObservationCatalog.d.ts +2 -0
- package/dist/cardObservationCatalog.d.ts.map +1 -1
- package/dist/cardObservationCatalog.js +47 -1
- package/dist/engineWireRegression.test.js +22 -2
- package/dist/fixtures/assessmentFixtures.d.ts +13 -0
- package/dist/fixtures/assessmentFixtures.d.ts.map +1 -0
- package/dist/fixtures/assessmentFixtures.js +164 -0
- package/dist/formatCardExposureDisplay.d.ts +0 -4
- package/dist/formatCardExposureDisplay.d.ts.map +1 -1
- package/dist/formatCardExposureDisplay.js +0 -15
- package/dist/formatInsight.d.ts +3 -2
- package/dist/formatInsight.d.ts.map +1 -1
- package/dist/formatInsight.js +26 -1
- package/dist/index.d.ts +7 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -11
- package/dist/narrativeParity.test.js +4 -34
- package/dist/presentGitHubPrComment.d.ts +1 -3
- package/dist/presentGitHubPrComment.d.ts.map +1 -1
- package/dist/presentGitHubPrComment.js +0 -17
- package/dist/presentation/buildGitHubCheckRunOutput.d.ts +20 -0
- package/dist/presentation/buildGitHubCheckRunOutput.d.ts.map +1 -0
- package/dist/presentation/buildGitHubCheckRunOutput.js +42 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts +2 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts.map +1 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.js +188 -0
- package/dist/presentation/compose/narrativeChannels.d.ts.map +1 -1
- package/dist/presentation/compose/narrativeChannels.js +13 -43
- package/dist/presentation/compose/narrativeCompose.d.ts.map +1 -1
- package/dist/presentation/compose/narrativeCompose.js +2 -5
- package/dist/presentation/dto/assessmentPresentationFields.d.ts +13 -0
- package/dist/presentation/dto/assessmentPresentationFields.d.ts.map +1 -0
- package/dist/presentation/dto/assessmentPresentationFields.js +1 -0
- package/dist/presentation/dto/dashboardCardPresentation.d.ts +2 -1
- package/dist/presentation/dto/dashboardCardPresentation.d.ts.map +1 -1
- package/dist/presentation/dto/githubAndCliPresentation.d.ts +4 -3
- package/dist/presentation/dto/githubAndCliPresentation.d.ts.map +1 -1
- package/dist/presentation/dto/scanDetailsPresentation.d.ts +2 -1
- package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -1
- package/dist/presentation/fixtures/dashboardCardGolden.test.js +21 -14
- package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts.map +1 -1
- package/dist/presentation/fixtures/presentationPersonaFixtures.js +31 -24
- package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -1
- package/dist/presentation/fixtures/scanResultFixtures.js +14 -3
- package/dist/presentation/index.d.ts +2 -2
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +2 -2
- package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -1
- package/dist/presentation/orchestration/buildScanCardPresentation.js +8 -3
- package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts +1 -1
- package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -1
- package/dist/presentation/orchestration/buildScanPresentationBundle.js +12 -10
- package/dist/presentation/orchestration/scanPresentationBundle.d.ts +8 -3
- package/dist/presentation/orchestration/scanPresentationBundle.d.ts.map +1 -1
- package/dist/presentation/presentationPersonas.test.js +6 -6
- package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -1
- package/dist/presentation/presenters/presentCliScanSummary.js +2 -0
- package/dist/presentation/presenters/presentDashboardCard.d.ts +1 -0
- package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -1
- package/dist/presentation/presenters/presentDashboardCard.js +18 -0
- package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -1
- package/dist/presentation/presenters/presentGitHubCheckRun.js +2 -0
- package/dist/presentation/presenters/presentGitHubPrComment.d.ts.map +1 -1
- package/dist/presentation/presenters/presentGitHubPrComment.js +15 -21
- package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -1
- package/dist/presentation/presenters/presentScanDetails.js +52 -129
- package/dist/presentation/profile/buildProfileFromAssessment.d.ts +5 -0
- package/dist/presentation/profile/buildProfileFromAssessment.d.ts.map +1 -0
- package/dist/presentation/profile/buildProfileFromAssessment.js +58 -0
- package/dist/presentation/projectAssessmentFields.d.ts +4 -0
- package/dist/presentation/projectAssessmentFields.d.ts.map +1 -0
- package/dist/presentation/projectAssessmentFields.js +13 -0
- package/dist/presentation/surfaceParity.test.d.ts +2 -0
- package/dist/presentation/surfaceParity.test.d.ts.map +1 -0
- package/dist/presentation/surfaceParity.test.js +74 -0
- package/dist/productMessaging.d.ts +4 -4
- package/dist/productMessaging.d.ts.map +1 -1
- package/dist/productMessaging.js +4 -4
- package/dist/productMessaging.test.js +2 -2
- package/dist/repoIntelligenceSchema.d.ts +2 -2
- package/dist/resolvePipelineStatus.d.ts +15 -0
- package/dist/resolvePipelineStatus.d.ts.map +1 -0
- package/dist/resolvePipelineStatus.js +37 -0
- package/dist/scanCardPresentationState.d.ts +10 -0
- package/dist/scanCardPresentationState.d.ts.map +1 -0
- package/dist/scanCardPresentationState.js +4 -0
- package/dist/{scanDetailViewModel.d.ts → scanDetailViewModelTypes.d.ts} +60 -32
- package/dist/scanDetailViewModelTypes.d.ts.map +1 -0
- package/dist/scanDetailViewModelTypes.js +4 -0
- package/dist/scanResultSchema.d.ts +75 -2
- package/dist/scanResultSchema.d.ts.map +1 -1
- package/dist/scanResultSchema.js +4 -2
- package/dist/scanResultSchema.test.js +16 -0
- package/dist/scanSurfaceCopy.d.ts +2 -0
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +2 -0
- package/dist/trustedScanGuards.test.js +7 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/collectNarrativeWhyBullets.d.ts +0 -12
- package/dist/collectNarrativeWhyBullets.d.ts.map +0 -1
- package/dist/collectNarrativeWhyBullets.js +0 -104
- package/dist/deriveCardDisplaySummary.d.ts +0 -16
- package/dist/deriveCardDisplaySummary.d.ts.map +0 -1
- package/dist/deriveCardDisplaySummary.js +0 -56
- package/dist/deriveCardDisplaySummary.test.d.ts +0 -2
- package/dist/deriveCardDisplaySummary.test.d.ts.map +0 -1
- package/dist/deriveCardDisplaySummary.test.js +0 -89
- package/dist/deriveCardOperationalObservations.d.ts +0 -19
- package/dist/deriveCardOperationalObservations.d.ts.map +0 -1
- package/dist/deriveCardOperationalObservations.js +0 -180
- package/dist/deriveCardOperationalObservations.test.d.ts +0 -2
- package/dist/deriveCardOperationalObservations.test.d.ts.map +0 -1
- package/dist/deriveCardOperationalObservations.test.js +0 -268
- package/dist/deriveRiskScoreSummary.d.ts +0 -33
- package/dist/deriveRiskScoreSummary.d.ts.map +0 -1
- package/dist/deriveRiskScoreSummary.js +0 -90
- package/dist/deriveRiskScoreSummary.test.d.ts +0 -2
- package/dist/deriveRiskScoreSummary.test.d.ts.map +0 -1
- package/dist/deriveRiskScoreSummary.test.js +0 -47
- package/dist/deriveScanDetailRecommendations.d.ts +0 -63
- package/dist/deriveScanDetailRecommendations.d.ts.map +0 -1
- package/dist/deriveScanDetailRecommendations.js +0 -725
- package/dist/deriveScanDetailRecommendations.test.d.ts +0 -2
- package/dist/deriveScanDetailRecommendations.test.d.ts.map +0 -1
- package/dist/deriveScanDetailRecommendations.test.js +0 -181
- package/dist/deriveScanSummaryText.d.ts +0 -7
- package/dist/deriveScanSummaryText.d.ts.map +0 -1
- package/dist/deriveScanSummaryText.js +0 -24
- package/dist/deriveScanSummaryText.test.d.ts +0 -2
- package/dist/deriveScanSummaryText.test.d.ts.map +0 -1
- package/dist/deriveScanSummaryText.test.js +0 -77
- package/dist/formatCardEvidenceCounts.d.ts +0 -7
- package/dist/formatCardEvidenceCounts.d.ts.map +0 -1
- package/dist/formatCardEvidenceCounts.js +0 -19
- package/dist/prCheckRunPresentation.d.ts +0 -68
- package/dist/prCheckRunPresentation.d.ts.map +0 -1
- package/dist/prCheckRunPresentation.js +0 -319
- package/dist/prCheckRunPresentation.test.d.ts +0 -2
- package/dist/prCheckRunPresentation.test.d.ts.map +0 -1
- package/dist/prCheckRunPresentation.test.js +0 -278
- package/dist/presentation/intent/derivePresentationInterpretation.d.ts +0 -4
- package/dist/presentation/intent/derivePresentationInterpretation.d.ts.map +0 -1
- package/dist/presentation/intent/derivePresentationInterpretation.js +0 -202
- package/dist/presentation/intent/formatPresentationCopy.d.ts +0 -8
- package/dist/presentation/intent/formatPresentationCopy.d.ts.map +0 -1
- package/dist/presentation/intent/formatPresentationCopy.js +0 -140
- package/dist/presentation/intent/presentationIntent.test.d.ts +0 -2
- package/dist/presentation/intent/presentationIntent.test.d.ts.map +0 -1
- package/dist/presentation/intent/presentationIntent.test.js +0 -29
- package/dist/presentation/intent/verificationFocusLabels.d.ts +0 -2
- package/dist/presentation/intent/verificationFocusLabels.d.ts.map +0 -1
- package/dist/presentation/intent/verificationFocusLabels.js +0 -45
- package/dist/presentation/profile/derivePresentationProfile.d.ts +0 -12
- package/dist/presentation/profile/derivePresentationProfile.d.ts.map +0 -1
- package/dist/presentation/profile/derivePresentationProfile.js +0 -85
- package/dist/scanCardSummary.d.ts +0 -58
- package/dist/scanCardSummary.d.ts.map +0 -1
- package/dist/scanCardSummary.js +0 -146
- package/dist/scanCardSummary.test.d.ts +0 -2
- package/dist/scanCardSummary.test.d.ts.map +0 -1
- package/dist/scanCardSummary.test.js +0 -234
- package/dist/scanDetailViewModel.d.ts.map +0 -1
- package/dist/scanDetailViewModel.js +0 -636
- package/dist/scanDetailViewModel.test.d.ts +0 -2
- package/dist/scanDetailViewModel.test.d.ts.map +0 -1
- package/dist/scanDetailViewModel.test.js +0 -530
|
@@ -1,636 +0,0 @@
|
|
|
1
|
-
import { humanizeEngineSurfaceText, layerRiskBandLabel, sortPRInsightsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
|
|
2
|
-
import { mapFindingToCatalogPhrase, mapGraphInsightsToCatalogPhrases, phraseForFamily, } from "./cardObservationCatalog.js";
|
|
3
|
-
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
4
|
-
import { formatCardAreaLabels } from "./formatCardAreaLabels.js";
|
|
5
|
-
import { composeContextLineFromFacts, formatChangedPackagesShort, formatUsageSummaryLine, labelBlastRadiusLevel, labelReachabilityKind, labelRuntimeSurface, summarizeBlastRadius, } from "./narrativePresentation.js";
|
|
6
|
-
import { normalizeGeneratedText, normalizeGeneratedTextNullable, } from "./normalizeGeneratedText.js";
|
|
7
|
-
import { deriveCardOperationalObservations } from "./deriveCardOperationalObservations.js";
|
|
8
|
-
import { deriveScanDetailRecommendations, recommendationTitlesForDedupe, } from "./deriveScanDetailRecommendations.js";
|
|
9
|
-
import { deriveSignalSummary, } from "./deriveRiskScoreSummary.js";
|
|
10
|
-
import { deriveCardExposureDisplay, deriveDetailReachChip, } from "./formatCardExposureDisplay.js";
|
|
11
|
-
import { formatInsight } from "./formatInsight.js";
|
|
12
|
-
import { mergePostureFromDecision, } from "./riskVocabulary.js";
|
|
13
|
-
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
14
|
-
export const ACT2_MAX_THEMES = 4;
|
|
15
|
-
export const ACT2_MAX_THEME_CHARS = 80;
|
|
16
|
-
export const VERDICT_LINE_MAX_CHARS = 120;
|
|
17
|
-
export const TIER1_MAX_VISIBLE_IMPACTS = 3;
|
|
18
|
-
export const ACT3_EVIDENCE_COLLAPSE_THRESHOLD = 10;
|
|
19
|
-
const ACT2_FORBIDDEN = /CVE-\d|@\d|\d+\.\d+\.\d+|[a-z0-9@./+-]+\/[a-z0-9@./+-]+/i;
|
|
20
|
-
const SEVERITY_ORDER = {
|
|
21
|
-
critical: 4,
|
|
22
|
-
high: 3,
|
|
23
|
-
medium: 2,
|
|
24
|
-
low: 1,
|
|
25
|
-
};
|
|
26
|
-
const LAYER_ORDER = [
|
|
27
|
-
"security",
|
|
28
|
-
"maintainability",
|
|
29
|
-
"ecosystem",
|
|
30
|
-
"upgradeImpact",
|
|
31
|
-
];
|
|
32
|
-
/** Bridge copy from Signal summary to Recommended actions. */
|
|
33
|
-
export function deriveFollowUpBridgeNote(actionCount) {
|
|
34
|
-
if (actionCount <= 0)
|
|
35
|
-
return null;
|
|
36
|
-
const copy = scanSurfaceCopy.scanDetail.signalSummary;
|
|
37
|
-
if (actionCount === 1)
|
|
38
|
-
return copy.followUpImprovementIdentified;
|
|
39
|
-
return copy.followUpImprovementsIdentified.replace("{count}", String(actionCount));
|
|
40
|
-
}
|
|
41
|
-
/** Act 2 theme sanitizer — strips package/CVE/version content. */
|
|
42
|
-
export function sanitizeAct2Theme(raw) {
|
|
43
|
-
const trimmed = raw.trim();
|
|
44
|
-
if (!trimmed || ACT2_FORBIDDEN.test(trimmed))
|
|
45
|
-
return null;
|
|
46
|
-
return truncateWithEllipsis(trimmed, ACT2_MAX_THEME_CHARS);
|
|
47
|
-
}
|
|
48
|
-
function reachBand(category) {
|
|
49
|
-
if (!category || category === "minimal" || category === "limited")
|
|
50
|
-
return "narrow";
|
|
51
|
-
if (category === "moderate")
|
|
52
|
-
return "moderate";
|
|
53
|
-
return "wide";
|
|
54
|
-
}
|
|
55
|
-
/** Verdict line from posture + reach band (scanSurfaceCopy.scanDetail.verdictLine). */
|
|
56
|
-
export function deriveVerdictLine(posture, totalScore) {
|
|
57
|
-
const copy = scanSurfaceCopy.scanDetail.verdictLine;
|
|
58
|
-
const category = deriveCardExposureDisplay(totalScore)?.category;
|
|
59
|
-
const band = reachBand(category);
|
|
60
|
-
if (posture === "safe") {
|
|
61
|
-
if (band === "wide")
|
|
62
|
-
return copy.safeNoBlockersWide;
|
|
63
|
-
if (band === "moderate")
|
|
64
|
-
return copy.safeNoBlockersModerate;
|
|
65
|
-
return copy.safeNoBlockersNarrow;
|
|
66
|
-
}
|
|
67
|
-
if (posture === "risky") {
|
|
68
|
-
if (band === "wide")
|
|
69
|
-
return copy.riskyResolveWide;
|
|
70
|
-
if (band === "moderate")
|
|
71
|
-
return copy.riskyResolveModerate;
|
|
72
|
-
return copy.riskyResolveNarrow;
|
|
73
|
-
}
|
|
74
|
-
if (posture === "needs_review") {
|
|
75
|
-
if (band === "wide")
|
|
76
|
-
return copy.reviewBeforeMergeWide;
|
|
77
|
-
if (band === "moderate")
|
|
78
|
-
return copy.reviewBeforeMergeModerate;
|
|
79
|
-
return copy.reviewBeforeMergeNarrow;
|
|
80
|
-
}
|
|
81
|
-
return scanSurfaceCopy.pipeline.scanIncomplete;
|
|
82
|
-
}
|
|
83
|
-
function themeFromCatalogPhrase(phrase) {
|
|
84
|
-
const map = {
|
|
85
|
-
"Vulnerable transitive packages detected": "Known vulnerabilities in the transitive tree",
|
|
86
|
-
"Transitive dependency hotspots detected": "Shared packages concentrate dependency risk",
|
|
87
|
-
"Duplicate dependency versions detected": "Overlapping dependency paths detected",
|
|
88
|
-
"Large upgrade blast radius": "Large upgrade footprint across the graph",
|
|
89
|
-
"High transitive dependency volume": "High transitive dependency volume in the graph",
|
|
90
|
-
"Deep indirect dependency chains": "Deep indirect dependency chains",
|
|
91
|
-
"Broad package surface area": "Broad declared package footprint",
|
|
92
|
-
"Stale ecosystem dependencies detected": "Stale ecosystem dependencies detected",
|
|
93
|
-
"Overlapping dependency paths detected": "Overlapping dependency paths detected",
|
|
94
|
-
};
|
|
95
|
-
return map[phrase] ?? phrase;
|
|
96
|
-
}
|
|
97
|
-
function collectAct2ThemeCandidates(result) {
|
|
98
|
-
const candidates = [];
|
|
99
|
-
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
100
|
-
const observations = deriveCardOperationalObservations(result, {
|
|
101
|
-
mergePosture: posture,
|
|
102
|
-
hasFullResult: true,
|
|
103
|
-
max: ACT2_MAX_THEMES + 2,
|
|
104
|
-
});
|
|
105
|
-
for (const phrase of observations.operationalObservations) {
|
|
106
|
-
candidates.push(themeFromCatalogPhrase(phrase));
|
|
107
|
-
}
|
|
108
|
-
for (const phrase of mapGraphInsightsToCatalogPhrases(result.graphInsights)) {
|
|
109
|
-
candidates.push(themeFromCatalogPhrase(phrase.phrase));
|
|
110
|
-
}
|
|
111
|
-
if (Array.isArray(result.decision?.reasoning)) {
|
|
112
|
-
for (const reason of result.decision.reasoning) {
|
|
113
|
-
const humanized = humanizeEngineSurfaceText(reason);
|
|
114
|
-
if (humanized)
|
|
115
|
-
candidates.push(humanized);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
for (const layer of LAYER_ORDER) {
|
|
119
|
-
const layerScore = result.layerScores?.[layer];
|
|
120
|
-
const band = layerRiskBandLabel(layerScore);
|
|
121
|
-
if (band !== "Moderate" && band !== "High")
|
|
122
|
-
continue;
|
|
123
|
-
const drivers = humanizeEngineSurfaceText(`${layer} ${band.toLowerCase()} contribution`);
|
|
124
|
-
if (drivers)
|
|
125
|
-
candidates.push(`${layer} dimension elevated`);
|
|
126
|
-
}
|
|
127
|
-
return candidates;
|
|
128
|
-
}
|
|
129
|
-
export function deriveAct2Themes(result) {
|
|
130
|
-
const seen = new Set();
|
|
131
|
-
const themes = [];
|
|
132
|
-
for (const candidate of collectAct2ThemeCandidates(result)) {
|
|
133
|
-
const sanitized = sanitizeAct2Theme(candidate);
|
|
134
|
-
if (!sanitized)
|
|
135
|
-
continue;
|
|
136
|
-
const key = sanitized.toLowerCase();
|
|
137
|
-
if (seen.has(key))
|
|
138
|
-
continue;
|
|
139
|
-
seen.add(key);
|
|
140
|
-
themes.push(sanitized);
|
|
141
|
-
if (themes.length >= ACT2_MAX_THEMES)
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
return themes;
|
|
145
|
-
}
|
|
146
|
-
function insightToImpactItem(insight, verifyLabels) {
|
|
147
|
-
const formatted = formatInsight(insight);
|
|
148
|
-
const verify = formatted.action &&
|
|
149
|
-
!verifyLabels.has(formatted.action.toLowerCase().replace(/\s+/g, " ").trim())
|
|
150
|
-
? formatted.action
|
|
151
|
-
: undefined;
|
|
152
|
-
return {
|
|
153
|
-
message: formatted.message,
|
|
154
|
-
where: formatted.where ?? undefined,
|
|
155
|
-
verify,
|
|
156
|
-
affectedFiles: insight.affectedFiles?.length
|
|
157
|
-
? insight.affectedFiles
|
|
158
|
-
: undefined,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
export function deriveOperationalImpact(result, verifyLabels = new Set()) {
|
|
162
|
-
const insights = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
|
|
163
|
-
const copy = scanSurfaceCopy.scanDetail.tier1Fallback;
|
|
164
|
-
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
165
|
-
const reachCategory = deriveCardExposureDisplay(result.totalScore)?.category;
|
|
166
|
-
if (insights.length > 0) {
|
|
167
|
-
return {
|
|
168
|
-
status: "rich",
|
|
169
|
-
items: insights.map((insight) => insightToImpactItem(insight, verifyLabels)),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const isQuietSafe = posture === "safe" &&
|
|
173
|
-
(reachCategory === "minimal" || reachCategory === "limited");
|
|
174
|
-
if (result.reportPresentation?.mode === "lightweight_pr_graph_baseline") {
|
|
175
|
-
return {
|
|
176
|
-
status: "fallback",
|
|
177
|
-
items: [],
|
|
178
|
-
fallbackMessage: copy.lightweightPr,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const hasGraphProof = (result.graphInsights?.vulnerable?.length ?? 0) > 0 ||
|
|
182
|
-
(result.graphInsights?.hotspots?.length ?? 0) > 0 ||
|
|
183
|
-
(result.findings?.length ?? 0) > 0;
|
|
184
|
-
if (hasGraphProof && posture !== "safe") {
|
|
185
|
-
return {
|
|
186
|
-
status: "fallback",
|
|
187
|
-
items: [],
|
|
188
|
-
fallbackMessage: copy.structuralProofBelow,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
if (isQuietSafe) {
|
|
192
|
-
return {
|
|
193
|
-
status: "fallback",
|
|
194
|
-
items: [],
|
|
195
|
-
fallbackMessage: copy.quietSafe,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
status: "fallback",
|
|
200
|
-
items: [],
|
|
201
|
-
fallbackMessage: copy.structuralComplete,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
function graphInsightToPackage(item, severity, evidence) {
|
|
205
|
-
return {
|
|
206
|
-
name: item.packageName,
|
|
207
|
-
version: item.version,
|
|
208
|
-
direct: item.direct,
|
|
209
|
-
severity,
|
|
210
|
-
evidence,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
function usageWhereFromFacts(facts) {
|
|
214
|
-
const primary = facts.changedPackages.primary;
|
|
215
|
-
const row = facts.packageUsage.find((u) => u.packageName === primary) ??
|
|
216
|
-
facts.packageUsage[0];
|
|
217
|
-
if (!row)
|
|
218
|
-
return undefined;
|
|
219
|
-
const paths = [...row.paths, ...row.criticalPaths, ...row.files].filter(Boolean);
|
|
220
|
-
if (paths.length === 0 && row.areas.length > 0) {
|
|
221
|
-
return row.areas.slice(0, 3).join(", ");
|
|
222
|
-
}
|
|
223
|
-
if (paths.length === 0)
|
|
224
|
-
return undefined;
|
|
225
|
-
const sample = paths.slice(0, 3).join(", ");
|
|
226
|
-
if (paths.length > 3) {
|
|
227
|
-
return `${sample} (+${paths.length - 3} more)`;
|
|
228
|
-
}
|
|
229
|
-
return sample;
|
|
230
|
-
}
|
|
231
|
-
function buildAttentionAreasFromFacts(facts) {
|
|
232
|
-
const areas = [];
|
|
233
|
-
if (facts.hotspots.length > 0) {
|
|
234
|
-
const packages = facts.hotspots.map((h) => ({
|
|
235
|
-
name: h.packageName,
|
|
236
|
-
direct: true,
|
|
237
|
-
evidence: h.paths[0],
|
|
238
|
-
}));
|
|
239
|
-
areas.push({
|
|
240
|
-
problemLabel: "Dependency hotspots",
|
|
241
|
-
problemDescription: "Packages where paths converge or corpus analysis flagged critical usage.",
|
|
242
|
-
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
243
|
-
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
const blast = facts.blastRadius;
|
|
247
|
-
if (blast &&
|
|
248
|
-
(blast.level === "wide" || (blast.changedPackageCount ?? 0) >= 8)) {
|
|
249
|
-
const names = facts.changedPackages.all;
|
|
250
|
-
if (names.length > 0) {
|
|
251
|
-
const packages = names
|
|
252
|
-
.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD)
|
|
253
|
-
.map((name) => ({ name, direct: true }));
|
|
254
|
-
areas.push({
|
|
255
|
-
problemLabel: phraseForFamily("blast_radius"),
|
|
256
|
-
problemDescription: blast.factors.length > 0
|
|
257
|
-
? `Wide blast radius: ${blast.factors.slice(0, 3).join(", ").replace(/_/g, " ")}`
|
|
258
|
-
: "Many packages changed in this upgrade.",
|
|
259
|
-
packages,
|
|
260
|
-
overflowCount: Math.max(0, names.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return areas;
|
|
265
|
-
}
|
|
266
|
-
function mergeAttentionAreas(fromFacts, fromGraph) {
|
|
267
|
-
if (fromFacts.length === 0)
|
|
268
|
-
return fromGraph;
|
|
269
|
-
const factPackageNames = new Set(fromFacts.flatMap((a) => a.packages.map((p) => p.name)));
|
|
270
|
-
const filteredGraph = fromGraph.filter((area) => {
|
|
271
|
-
if (area.problemLabel !== phraseForFamily("transitive_hotspots")) {
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
const onlyDupes = area.packages.every((p) => factPackageNames.has(p.name));
|
|
275
|
-
return !onlyDupes;
|
|
276
|
-
});
|
|
277
|
-
return [...fromFacts, ...filteredGraph];
|
|
278
|
-
}
|
|
279
|
-
function buildAttentionAreas(result) {
|
|
280
|
-
const areas = [];
|
|
281
|
-
const gi = result.graphInsights;
|
|
282
|
-
const vulnerable = Array.isArray(gi?.vulnerable) ? gi.vulnerable : [];
|
|
283
|
-
if (vulnerable.length > 0) {
|
|
284
|
-
const packages = vulnerable.map((v) => graphInsightToPackage(v));
|
|
285
|
-
areas.push({
|
|
286
|
-
problemLabel: phraseForFamily("vulnerable_transitive"),
|
|
287
|
-
problemDescription: "Packages with known security advisories in the dependency tree.",
|
|
288
|
-
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
289
|
-
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
const hotspots = Array.isArray(gi?.hotspots) ? gi.hotspots : [];
|
|
293
|
-
if (hotspots.length > 0) {
|
|
294
|
-
const packages = hotspots.map((h) => graphInsightToPackage(h));
|
|
295
|
-
areas.push({
|
|
296
|
-
problemLabel: phraseForFamily("transitive_hotspots"),
|
|
297
|
-
problemDescription: "Packages many dependency paths converge on.",
|
|
298
|
-
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
299
|
-
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
const findings = Array.isArray(result.findings) ? result.findings : [];
|
|
303
|
-
const duplicateFindings = findings.filter((f) => {
|
|
304
|
-
const mapped = mapFindingToCatalogPhrase(f);
|
|
305
|
-
return mapped?.family === "duplicate_versions";
|
|
306
|
-
});
|
|
307
|
-
if (duplicateFindings.length > 0) {
|
|
308
|
-
const packages = duplicateFindings.map((f) => ({
|
|
309
|
-
name: f.packageName,
|
|
310
|
-
direct: true,
|
|
311
|
-
severity: f.severity,
|
|
312
|
-
}));
|
|
313
|
-
areas.push({
|
|
314
|
-
problemLabel: phraseForFamily("duplicate_versions"),
|
|
315
|
-
problemDescription: "Multiple resolved versions of the same dependency.",
|
|
316
|
-
packages: packages.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
317
|
-
overflowCount: Math.max(0, packages.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
const changed = Array.isArray(result.changedPackages)
|
|
321
|
-
? result.changedPackages
|
|
322
|
-
: [];
|
|
323
|
-
if (changed.length >= 8) {
|
|
324
|
-
const packages = changed
|
|
325
|
-
.slice(0, ACT3_EVIDENCE_COLLAPSE_THRESHOLD)
|
|
326
|
-
.map((name) => ({
|
|
327
|
-
name,
|
|
328
|
-
direct: true,
|
|
329
|
-
}));
|
|
330
|
-
areas.push({
|
|
331
|
-
problemLabel: phraseForFamily("blast_radius"),
|
|
332
|
-
problemDescription: "Many packages changed in this upgrade.",
|
|
333
|
-
packages,
|
|
334
|
-
overflowCount: Math.max(0, changed.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
return areas;
|
|
338
|
-
}
|
|
339
|
-
function findingToRow(f, source) {
|
|
340
|
-
return {
|
|
341
|
-
id: f.id,
|
|
342
|
-
severity: f.severity,
|
|
343
|
-
title: f.title,
|
|
344
|
-
description: f.description,
|
|
345
|
-
packageName: f.packageName,
|
|
346
|
-
recommendation: f.recommendation,
|
|
347
|
-
source,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function sortFindings(findings) {
|
|
351
|
-
return [...findings].sort((a, b) => {
|
|
352
|
-
const sd = SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
|
|
353
|
-
if (sd !== 0)
|
|
354
|
-
return sd;
|
|
355
|
-
return a.packageName.localeCompare(b.packageName);
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
function buildFindingRows(result) {
|
|
359
|
-
const rows = [];
|
|
360
|
-
for (const f of Array.isArray(result.findings) ? result.findings : []) {
|
|
361
|
-
rows.push(findingToRow(f, "dependency"));
|
|
362
|
-
}
|
|
363
|
-
return sortFindings(rows);
|
|
364
|
-
}
|
|
365
|
-
function buildTopology(result) {
|
|
366
|
-
const gi = result.graphInsights;
|
|
367
|
-
if (!gi)
|
|
368
|
-
return null;
|
|
369
|
-
const nodes = typeof gi.nodes === "number" ? gi.nodes : 0;
|
|
370
|
-
const edges = typeof gi.edges === "number" ? gi.edges : 0;
|
|
371
|
-
const maxDepth = typeof gi.maxDepth === "number" ? gi.maxDepth : 0;
|
|
372
|
-
if (nodes === 0 && edges === 0 && maxDepth === 0)
|
|
373
|
-
return null;
|
|
374
|
-
const deepest = Array.isArray(gi.deepest)
|
|
375
|
-
? gi.deepest.slice(0, 5).map((d) => ({
|
|
376
|
-
packageName: d.packageName,
|
|
377
|
-
depth: d.depth,
|
|
378
|
-
direct: d.direct,
|
|
379
|
-
via: Array.isArray(d.via) ? d.via : [],
|
|
380
|
-
}))
|
|
381
|
-
: [];
|
|
382
|
-
return {
|
|
383
|
-
summaryLine: normalizeGeneratedText(`${nodes} packages | ${edges} edges | max depth ${maxDepth}`),
|
|
384
|
-
deepest,
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
function annotateFindingsWithCoverage(rows, center) {
|
|
388
|
-
const rankByFindingId = new Map();
|
|
389
|
-
for (const item of center.items) {
|
|
390
|
-
for (const findingId of item.proofRefs?.findingIds ?? []) {
|
|
391
|
-
if (!rankByFindingId.has(findingId)) {
|
|
392
|
-
rankByFindingId.set(findingId, item.rank);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
return rows.map((row) => {
|
|
397
|
-
const rank = rankByFindingId.get(row.id);
|
|
398
|
-
if (!rank)
|
|
399
|
-
return row;
|
|
400
|
-
return {
|
|
401
|
-
...row,
|
|
402
|
-
recommendation: undefined,
|
|
403
|
-
coveredByRecommendationRank: rank,
|
|
404
|
-
};
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
function deriveConfidenceCaveat(result) {
|
|
408
|
-
const confidence = result.decision?.confidence;
|
|
409
|
-
if (confidence === "medium") {
|
|
410
|
-
return scanSurfaceCopy.scanDetail.confidenceCaveat.medium;
|
|
411
|
-
}
|
|
412
|
-
if (confidence === "low") {
|
|
413
|
-
return scanSurfaceCopy.scanDetail.confidenceCaveat.low;
|
|
414
|
-
}
|
|
415
|
-
return undefined;
|
|
416
|
-
}
|
|
417
|
-
export function presentScanDetailNarrativeContext(facts) {
|
|
418
|
-
const affectedAreas = [];
|
|
419
|
-
for (const area of facts.affectedAreas) {
|
|
420
|
-
const formatted = formatCardAreaLabels([area.label], 1);
|
|
421
|
-
if (formatted[0])
|
|
422
|
-
affectedAreas.push(formatted[0]);
|
|
423
|
-
if (affectedAreas.length >= 4)
|
|
424
|
-
break;
|
|
425
|
-
}
|
|
426
|
-
const usageHighlights = [];
|
|
427
|
-
for (const row of facts.packageUsage.slice(0, 4)) {
|
|
428
|
-
const paths = [...row.paths, ...row.criticalPaths, ...row.files];
|
|
429
|
-
if (paths[0])
|
|
430
|
-
usageHighlights.push(paths[0]);
|
|
431
|
-
}
|
|
432
|
-
const blast = summarizeBlastRadius(facts, 5);
|
|
433
|
-
return {
|
|
434
|
-
mode: facts.availability.mode,
|
|
435
|
-
codeIntelligenceAvailable: facts.availability.codeIntelligenceAvailable,
|
|
436
|
-
changedPackagesDisplay: formatChangedPackagesShort(facts, 4),
|
|
437
|
-
runtimeSurfaceLabel: labelRuntimeSurface(facts),
|
|
438
|
-
reachabilityLabel: labelReachabilityKind(facts),
|
|
439
|
-
blastRadiusLabel: labelBlastRadiusLevel(facts),
|
|
440
|
-
affectedAreas,
|
|
441
|
-
structuralOnlyDisclaimer: facts.availability.mode === "graph_fallback" &&
|
|
442
|
-
!facts.availability.tiersPresent.tier1
|
|
443
|
-
? scanSurfaceCopy.narrativeCard.structuralOnlyDisclaimer
|
|
444
|
-
: null,
|
|
445
|
-
usageHighlights: usageHighlights.slice(0, 5),
|
|
446
|
-
frameworks: facts.frameworks.slice(0, 5),
|
|
447
|
-
blastRadiusFactors: blast.factors.map((f) => f.replace(/_/g, " ")),
|
|
448
|
-
hotspotNames: facts.hotspots.map((h) => h.packageName).slice(0, 8),
|
|
449
|
-
usageContextLine: normalizeGeneratedTextNullable(formatUsageSummaryLine(facts, 2)),
|
|
450
|
-
upgradeContextLine: normalizeGeneratedTextNullable(composeContextLineFromFacts(facts, {
|
|
451
|
-
includePathSample: true,
|
|
452
|
-
maxAreas: 4,
|
|
453
|
-
})),
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
function narrativeContextFromFacts(facts) {
|
|
457
|
-
return presentScanDetailNarrativeContext(facts);
|
|
458
|
-
}
|
|
459
|
-
function guidanceToImpactItem(g, verifyLabels, facts) {
|
|
460
|
-
const verify = g.remediation &&
|
|
461
|
-
!verifyLabels.has(g.remediation.toLowerCase().replace(/\s+/g, " ").trim())
|
|
462
|
-
? g.remediation
|
|
463
|
-
: undefined;
|
|
464
|
-
let where = g.context?.trim() || undefined;
|
|
465
|
-
if (!where &&
|
|
466
|
-
facts &&
|
|
467
|
-
(facts.availability.mode === "pr_intelligence" ||
|
|
468
|
-
facts.availability.tiersPresent.tier1)) {
|
|
469
|
-
where = usageWhereFromFacts(facts);
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
message: normalizeGeneratedText(g.message),
|
|
473
|
-
where: where ? normalizeGeneratedText(where) : undefined,
|
|
474
|
-
verify: verify ? normalizeGeneratedText(verify) : undefined,
|
|
475
|
-
affectedFiles: g.affectedFiles,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function deriveOperationalImpactFromFacts(facts, result, verifyLabels) {
|
|
479
|
-
const changedGuidance = facts.reviewerGuidance.filter((g) => g.scope === "changed" || g.kind === "insight");
|
|
480
|
-
if (facts.availability.mode === "pr_intelligence" ||
|
|
481
|
-
(facts.availability.tiersPresent.tier1 && changedGuidance.length > 0)) {
|
|
482
|
-
const items = changedGuidance
|
|
483
|
-
.slice(0, 6)
|
|
484
|
-
.map((g) => guidanceToImpactItem(g, verifyLabels, facts));
|
|
485
|
-
if (items.length > 0) {
|
|
486
|
-
return { status: "rich", items };
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
if (changedGuidance.length > 0) {
|
|
490
|
-
const insightCount = Array.isArray(result.insights)
|
|
491
|
-
? result.insights.length
|
|
492
|
-
: 0;
|
|
493
|
-
if (insightCount > TIER1_MAX_VISIBLE_IMPACTS) {
|
|
494
|
-
return deriveOperationalImpact(result, verifyLabels);
|
|
495
|
-
}
|
|
496
|
-
return {
|
|
497
|
-
status: "rich",
|
|
498
|
-
items: changedGuidance
|
|
499
|
-
.slice(0, 6)
|
|
500
|
-
.map((g) => guidanceToImpactItem(g, verifyLabels, facts)),
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
return deriveOperationalImpact(result, verifyLabels);
|
|
504
|
-
}
|
|
505
|
-
function themeCandidateFromLabel(label) {
|
|
506
|
-
const trimmed = label.trim();
|
|
507
|
-
if (!trimmed)
|
|
508
|
-
return null;
|
|
509
|
-
return sanitizeAct2Theme(trimmed) ?? trimmed.slice(0, ACT2_MAX_THEME_CHARS);
|
|
510
|
-
}
|
|
511
|
-
function deriveBecauseThemesFromFacts(facts) {
|
|
512
|
-
const themes = [];
|
|
513
|
-
const seen = new Set();
|
|
514
|
-
const prIntelligence = facts.availability.mode === "pr_intelligence" ||
|
|
515
|
-
facts.availability.tiersPresent.tier1;
|
|
516
|
-
const pushTheme = (raw) => {
|
|
517
|
-
if (!raw)
|
|
518
|
-
return;
|
|
519
|
-
const normalized = normalizeGeneratedText(raw);
|
|
520
|
-
const key = normalized.toLowerCase();
|
|
521
|
-
if (seen.has(key))
|
|
522
|
-
return;
|
|
523
|
-
seen.add(key);
|
|
524
|
-
themes.push(normalized);
|
|
525
|
-
};
|
|
526
|
-
if (prIntelligence) {
|
|
527
|
-
for (const area of facts.affectedAreas) {
|
|
528
|
-
pushTheme(themeCandidateFromLabel(area.label));
|
|
529
|
-
if (themes.length >= ACT2_MAX_THEMES)
|
|
530
|
-
return themes;
|
|
531
|
-
}
|
|
532
|
-
for (const factor of facts.blastRadius?.factors ?? []) {
|
|
533
|
-
pushTheme(themeCandidateFromLabel(factor.replace(/_/g, " ")));
|
|
534
|
-
if (themes.length >= ACT2_MAX_THEMES)
|
|
535
|
-
return themes;
|
|
536
|
-
}
|
|
537
|
-
const reachLabel = labelReachabilityKind(facts);
|
|
538
|
-
if (reachLabel)
|
|
539
|
-
pushTheme(reachLabel);
|
|
540
|
-
}
|
|
541
|
-
for (const g of facts.reviewerGuidance.slice(0, ACT2_MAX_THEMES + 2)) {
|
|
542
|
-
pushTheme(sanitizeAct2Theme(g.message));
|
|
543
|
-
if (themes.length >= ACT2_MAX_THEMES)
|
|
544
|
-
return themes;
|
|
545
|
-
}
|
|
546
|
-
if (themes.length < ACT2_MAX_THEMES && !prIntelligence) {
|
|
547
|
-
for (const ctx of facts.repositoryContext) {
|
|
548
|
-
pushTheme(sanitizeAct2Theme(phraseForFamily(ctx.family)));
|
|
549
|
-
if (themes.length >= ACT2_MAX_THEMES)
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return themes.slice(0, ACT2_MAX_THEMES);
|
|
554
|
-
}
|
|
555
|
-
export function deriveVerdictLineFromFacts(facts, result) {
|
|
556
|
-
const topGuidance = facts.reviewerGuidance.find((g) => (g.scope === "changed" || g.kind === "insight") && g.message.trim());
|
|
557
|
-
if (topGuidance) {
|
|
558
|
-
return truncateWithEllipsis(topGuidance.message.trim(), VERDICT_LINE_MAX_CHARS);
|
|
559
|
-
}
|
|
560
|
-
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
561
|
-
return truncateWithEllipsis(deriveVerdictLine(posture, result.totalScore), VERDICT_LINE_MAX_CHARS);
|
|
562
|
-
}
|
|
563
|
-
/** Scan detail presentation from shared narrative facts. */
|
|
564
|
-
export function presentScanDetailViewModel(facts, result, options) {
|
|
565
|
-
const posture = mergePostureFromDecision(result.decision?.recommendation);
|
|
566
|
-
const scopeChip = deriveDetailReachChip(result.totalScore);
|
|
567
|
-
const verdictLine = deriveVerdictLineFromFacts(facts, result);
|
|
568
|
-
const narrativeContext = narrativeContextFromFacts(facts);
|
|
569
|
-
const recommendedActions = deriveScanDetailRecommendations(result);
|
|
570
|
-
const signalSummary = deriveSignalSummary(result);
|
|
571
|
-
const verifyLabels = recommendationTitlesForDedupe(recommendedActions);
|
|
572
|
-
const operationalImpact = deriveOperationalImpactFromFacts(facts, result, verifyLabels);
|
|
573
|
-
const themes = deriveBecauseThemesFromFacts(facts);
|
|
574
|
-
const confidenceCaveat = deriveConfidenceCaveat(result);
|
|
575
|
-
const because = themes.length > 0 || confidenceCaveat ? { themes, confidenceCaveat } : null;
|
|
576
|
-
const factsAttention = facts.availability.mode === "pr_intelligence" ||
|
|
577
|
-
facts.availability.tiersPresent.tier1
|
|
578
|
-
? buildAttentionAreasFromFacts(facts)
|
|
579
|
-
: [];
|
|
580
|
-
const attentionAreas = mergeAttentionAreas(factsAttention, buildAttentionAreas(result));
|
|
581
|
-
const allFindings = annotateFindingsWithCoverage(buildFindingRows(result), recommendedActions);
|
|
582
|
-
const topology = buildTopology(result);
|
|
583
|
-
const hasEvidence = attentionAreas.length > 0 || allFindings.length > 0 || topology != null;
|
|
584
|
-
return {
|
|
585
|
-
verdict: {
|
|
586
|
-
posture,
|
|
587
|
-
scopeChip,
|
|
588
|
-
verdictLine,
|
|
589
|
-
prLabel: options.prNumber != null ? `#${options.prNumber}` : undefined,
|
|
590
|
-
},
|
|
591
|
-
signalSummary,
|
|
592
|
-
followUpBridgeNote: deriveFollowUpBridgeNote(recommendedActions.items.length),
|
|
593
|
-
recommendedActions,
|
|
594
|
-
operationalImpact,
|
|
595
|
-
because,
|
|
596
|
-
evidence: hasEvidence
|
|
597
|
-
? {
|
|
598
|
-
attentionAreas,
|
|
599
|
-
findings: allFindings,
|
|
600
|
-
findingsOverflowCount: Math.max(0, allFindings.length - ACT3_EVIDENCE_COLLAPSE_THRESHOLD),
|
|
601
|
-
topology,
|
|
602
|
-
}
|
|
603
|
-
: null,
|
|
604
|
-
repoContext: options.repoContext ?? { status: "hidden" },
|
|
605
|
-
narrativeContext,
|
|
606
|
-
metadata: {
|
|
607
|
-
scanId: options.scanId,
|
|
608
|
-
generatedAt: result.generatedAt,
|
|
609
|
-
methodologyVersion: options.methodologyVersion,
|
|
610
|
-
changedPackagesSummary: narrativeContext.changedPackagesDisplay ??
|
|
611
|
-
facts.changedPackages.primary ??
|
|
612
|
-
undefined,
|
|
613
|
-
codeAnalysisTimedOut: result.codeAnalysisMetrics?.timedOut === true,
|
|
614
|
-
codeIntelligenceAvailable: facts.availability.codeIntelligenceAvailable,
|
|
615
|
-
},
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
export function deriveScanDetailViewModel(result, options) {
|
|
619
|
-
if (!result || options.status !== "done")
|
|
620
|
-
return null;
|
|
621
|
-
const facts = deriveScanNarrative(result);
|
|
622
|
-
return presentScanDetailViewModel(facts, result, options);
|
|
623
|
-
}
|
|
624
|
-
/** Tier 1 recall test helper — true when message describes application-level impact. */
|
|
625
|
-
export function tier1PassesRecallTest(operationalImpact) {
|
|
626
|
-
if (operationalImpact.status === "rich") {
|
|
627
|
-
return operationalImpact.items.some((item) => item.message.trim().length > 10);
|
|
628
|
-
}
|
|
629
|
-
if (operationalImpact.status === "fallback" &&
|
|
630
|
-
operationalImpact.fallbackMessage) {
|
|
631
|
-
return (operationalImpact.fallbackMessage.includes("Dependency") ||
|
|
632
|
-
operationalImpact.fallbackMessage.includes("runtime") ||
|
|
633
|
-
operationalImpact.fallbackMessage.includes("application"));
|
|
634
|
-
}
|
|
635
|
-
return false;
|
|
636
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scanDetailViewModel.test.d.ts","sourceRoot":"","sources":["../src/scanDetailViewModel.test.ts"],"names":[],"mappings":""}
|