@mergesignal/shared 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist/actionsStepSummary.d.ts +1 -22
  2. package/dist/actionsStepSummary.d.ts.map +1 -1
  3. package/dist/actionsStepSummary.js +19 -716
  4. package/dist/actionsStepSummary.test.js +34 -329
  5. package/dist/assessmentLabels.d.ts +6 -0
  6. package/dist/assessmentLabels.d.ts.map +1 -0
  7. package/dist/assessmentLabels.js +33 -0
  8. package/dist/assessmentProjection.d.ts +12 -0
  9. package/dist/assessmentProjection.d.ts.map +1 -0
  10. package/dist/assessmentProjection.js +100 -0
  11. package/dist/assessmentSchema.d.ts +155 -0
  12. package/dist/assessmentSchema.d.ts.map +1 -0
  13. package/dist/assessmentSchema.js +110 -0
  14. package/dist/cardObservationCatalog.d.ts +2 -0
  15. package/dist/cardObservationCatalog.d.ts.map +1 -1
  16. package/dist/cardObservationCatalog.js +47 -1
  17. package/dist/engineWireRegression.test.js +24 -4
  18. package/dist/fixtures/assessmentFixtures.d.ts +13 -0
  19. package/dist/fixtures/assessmentFixtures.d.ts.map +1 -0
  20. package/dist/fixtures/assessmentFixtures.js +164 -0
  21. package/dist/formatCardExposureDisplay.d.ts +0 -4
  22. package/dist/formatCardExposureDisplay.d.ts.map +1 -1
  23. package/dist/formatCardExposureDisplay.js +0 -15
  24. package/dist/formatInsight.d.ts +3 -2
  25. package/dist/formatInsight.d.ts.map +1 -1
  26. package/dist/formatInsight.js +26 -1
  27. package/dist/index.d.ts +7 -11
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +7 -11
  30. package/dist/narrativeParity.test.js +6 -37
  31. package/dist/presentGitHubPrComment.d.ts +1 -3
  32. package/dist/presentGitHubPrComment.d.ts.map +1 -1
  33. package/dist/presentGitHubPrComment.js +0 -17
  34. package/dist/presentation/compose/narrativeChannels.d.ts +17 -0
  35. package/dist/presentation/compose/narrativeChannels.d.ts.map +1 -0
  36. package/dist/presentation/compose/narrativeChannels.js +45 -0
  37. package/dist/presentation/compose/narrativeChannels.test.d.ts +2 -0
  38. package/dist/presentation/compose/narrativeChannels.test.d.ts.map +1 -0
  39. package/dist/presentation/compose/narrativeChannels.test.js +42 -0
  40. package/dist/presentation/compose/narrativeCompose.d.ts +1 -0
  41. package/dist/presentation/compose/narrativeCompose.d.ts.map +1 -1
  42. package/dist/presentation/compose/narrativeCompose.js +5 -7
  43. package/dist/presentation/dto/assessmentPresentationFields.d.ts +13 -0
  44. package/dist/presentation/dto/assessmentPresentationFields.d.ts.map +1 -0
  45. package/dist/presentation/dto/dashboardCardPresentation.d.ts +30 -0
  46. package/dist/presentation/dto/dashboardCardPresentation.d.ts.map +1 -0
  47. package/dist/presentation/dto/dashboardCardPresentation.js +1 -0
  48. package/dist/presentation/dto/githubAndCliPresentation.d.ts +4 -3
  49. package/dist/presentation/dto/githubAndCliPresentation.d.ts.map +1 -1
  50. package/dist/presentation/dto/scanDetailsPresentation.d.ts +2 -1
  51. package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -1
  52. package/dist/presentation/dto/types.d.ts +0 -9
  53. package/dist/presentation/dto/types.d.ts.map +1 -1
  54. package/dist/presentation/dto/types.js +1 -22
  55. package/dist/presentation/fixtures/dashboardCardGolden.test.d.ts +2 -0
  56. package/dist/presentation/fixtures/dashboardCardGolden.test.d.ts.map +1 -0
  57. package/dist/presentation/fixtures/dashboardCardGolden.test.js +38 -0
  58. package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts.map +1 -1
  59. package/dist/presentation/fixtures/presentationPersonaFixtures.js +31 -24
  60. package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -1
  61. package/dist/presentation/fixtures/scanResultFixtures.js +14 -3
  62. package/dist/presentation/index.d.ts +6 -6
  63. package/dist/presentation/index.d.ts.map +1 -1
  64. package/dist/presentation/index.js +2 -4
  65. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts +2 -2
  66. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -1
  67. package/dist/presentation/orchestration/buildScanCardPresentation.js +13 -9
  68. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts +1 -1
  69. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -1
  70. package/dist/presentation/orchestration/buildScanPresentationBundle.js +12 -10
  71. package/dist/presentation/orchestration/scanPresentationBundle.d.ts +8 -3
  72. package/dist/presentation/orchestration/scanPresentationBundle.d.ts.map +1 -1
  73. package/dist/presentation/presentation.test.js +16 -18
  74. package/dist/presentation/presentationPersonas.test.js +20 -19
  75. package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -1
  76. package/dist/presentation/presenters/presentCliScanSummary.js +4 -2
  77. package/dist/presentation/presenters/presentDashboardCard.d.ts +5 -0
  78. package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -0
  79. package/dist/presentation/presenters/presentDashboardCard.js +119 -0
  80. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -1
  81. package/dist/presentation/presenters/presentGitHubCheckRun.js +4 -2
  82. package/dist/presentation/presenters/presentGitHubPrComment.d.ts.map +1 -1
  83. package/dist/presentation/presenters/presentGitHubPrComment.js +15 -21
  84. package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -1
  85. package/dist/presentation/presenters/presentScanDetails.js +53 -127
  86. package/dist/presentation/profile/buildProfileFromAssessment.d.ts +5 -0
  87. package/dist/presentation/profile/buildProfileFromAssessment.d.ts.map +1 -0
  88. package/dist/presentation/profile/buildProfileFromAssessment.js +58 -0
  89. package/dist/presentation/projectAssessmentFields.d.ts +4 -0
  90. package/dist/presentation/projectAssessmentFields.d.ts.map +1 -0
  91. package/dist/presentation/projectAssessmentFields.js +13 -0
  92. package/dist/presentation/surfaceParity.test.d.ts +2 -0
  93. package/dist/presentation/surfaceParity.test.d.ts.map +1 -0
  94. package/dist/presentation/surfaceParity.test.js +74 -0
  95. package/dist/productMessaging.d.ts +4 -4
  96. package/dist/productMessaging.d.ts.map +1 -1
  97. package/dist/productMessaging.js +4 -4
  98. package/dist/productMessaging.test.js +2 -2
  99. package/dist/repoIntelligenceSchema.d.ts +2 -2
  100. package/dist/resolvePipelineStatus.d.ts +15 -0
  101. package/dist/resolvePipelineStatus.d.ts.map +1 -0
  102. package/dist/resolvePipelineStatus.js +37 -0
  103. package/dist/scanCardPresentationState.d.ts +4 -0
  104. package/dist/scanCardPresentationState.d.ts.map +1 -0
  105. package/dist/scanCardPresentationState.js +4 -0
  106. package/dist/{scanDetailViewModel.d.ts → scanDetailViewModelTypes.d.ts} +60 -32
  107. package/dist/scanDetailViewModelTypes.d.ts.map +1 -0
  108. package/dist/scanDetailViewModelTypes.js +4 -0
  109. package/dist/scanResultSchema.d.ts +75 -2
  110. package/dist/scanResultSchema.d.ts.map +1 -1
  111. package/dist/scanResultSchema.js +4 -2
  112. package/dist/scanResultSchema.test.js +16 -0
  113. package/dist/trustedScanGuards.test.js +7 -0
  114. package/dist/types.d.ts +3 -0
  115. package/dist/types.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/dist/collectNarrativeWhyBullets.d.ts +0 -12
  118. package/dist/collectNarrativeWhyBullets.d.ts.map +0 -1
  119. package/dist/collectNarrativeWhyBullets.js +0 -104
  120. package/dist/deriveCardDisplaySummary.d.ts +0 -16
  121. package/dist/deriveCardDisplaySummary.d.ts.map +0 -1
  122. package/dist/deriveCardDisplaySummary.js +0 -56
  123. package/dist/deriveCardDisplaySummary.test.d.ts +0 -2
  124. package/dist/deriveCardDisplaySummary.test.d.ts.map +0 -1
  125. package/dist/deriveCardDisplaySummary.test.js +0 -89
  126. package/dist/deriveCardOperationalObservations.d.ts +0 -19
  127. package/dist/deriveCardOperationalObservations.d.ts.map +0 -1
  128. package/dist/deriveCardOperationalObservations.js +0 -180
  129. package/dist/deriveCardOperationalObservations.test.d.ts +0 -2
  130. package/dist/deriveCardOperationalObservations.test.d.ts.map +0 -1
  131. package/dist/deriveCardOperationalObservations.test.js +0 -268
  132. package/dist/deriveRiskScoreSummary.d.ts +0 -33
  133. package/dist/deriveRiskScoreSummary.d.ts.map +0 -1
  134. package/dist/deriveRiskScoreSummary.js +0 -90
  135. package/dist/deriveRiskScoreSummary.test.d.ts +0 -2
  136. package/dist/deriveRiskScoreSummary.test.d.ts.map +0 -1
  137. package/dist/deriveRiskScoreSummary.test.js +0 -47
  138. package/dist/deriveScanDetailRecommendations.d.ts +0 -63
  139. package/dist/deriveScanDetailRecommendations.d.ts.map +0 -1
  140. package/dist/deriveScanDetailRecommendations.js +0 -725
  141. package/dist/deriveScanDetailRecommendations.test.d.ts +0 -2
  142. package/dist/deriveScanDetailRecommendations.test.d.ts.map +0 -1
  143. package/dist/deriveScanDetailRecommendations.test.js +0 -181
  144. package/dist/deriveScanSummaryText.d.ts +0 -7
  145. package/dist/deriveScanSummaryText.d.ts.map +0 -1
  146. package/dist/deriveScanSummaryText.js +0 -24
  147. package/dist/deriveScanSummaryText.test.d.ts +0 -2
  148. package/dist/deriveScanSummaryText.test.d.ts.map +0 -1
  149. package/dist/deriveScanSummaryText.test.js +0 -77
  150. package/dist/formatCardEvidenceCounts.d.ts +0 -7
  151. package/dist/formatCardEvidenceCounts.d.ts.map +0 -1
  152. package/dist/formatCardEvidenceCounts.js +0 -19
  153. package/dist/prCheckRunPresentation.d.ts +0 -68
  154. package/dist/prCheckRunPresentation.d.ts.map +0 -1
  155. package/dist/prCheckRunPresentation.js +0 -319
  156. package/dist/prCheckRunPresentation.test.d.ts +0 -2
  157. package/dist/prCheckRunPresentation.test.d.ts.map +0 -1
  158. package/dist/prCheckRunPresentation.test.js +0 -278
  159. package/dist/presentation/dto/scanCardPresentation.d.ts +0 -27
  160. package/dist/presentation/dto/scanCardPresentation.d.ts.map +0 -1
  161. package/dist/presentation/intent/derivePresentationInterpretation.d.ts +0 -4
  162. package/dist/presentation/intent/derivePresentationInterpretation.d.ts.map +0 -1
  163. package/dist/presentation/intent/derivePresentationInterpretation.js +0 -202
  164. package/dist/presentation/intent/formatPresentationCopy.d.ts +0 -7
  165. package/dist/presentation/intent/formatPresentationCopy.d.ts.map +0 -1
  166. package/dist/presentation/intent/formatPresentationCopy.js +0 -153
  167. package/dist/presentation/intent/presentationIntent.test.d.ts +0 -2
  168. package/dist/presentation/intent/presentationIntent.test.d.ts.map +0 -1
  169. package/dist/presentation/intent/presentationIntent.test.js +0 -29
  170. package/dist/presentation/intent/verificationFocusLabels.d.ts +0 -2
  171. package/dist/presentation/intent/verificationFocusLabels.d.ts.map +0 -1
  172. package/dist/presentation/intent/verificationFocusLabels.js +0 -45
  173. package/dist/presentation/presenters/presentPipelineScanCard.d.ts +0 -3
  174. package/dist/presentation/presenters/presentPipelineScanCard.d.ts.map +0 -1
  175. package/dist/presentation/presenters/presentPipelineScanCard.js +0 -23
  176. package/dist/presentation/presenters/presentScanCard.d.ts +0 -7
  177. package/dist/presentation/presenters/presentScanCard.d.ts.map +0 -1
  178. package/dist/presentation/presenters/presentScanCard.js +0 -44
  179. package/dist/presentation/profile/derivePresentationProfile.d.ts +0 -12
  180. package/dist/presentation/profile/derivePresentationProfile.d.ts.map +0 -1
  181. package/dist/presentation/profile/derivePresentationProfile.js +0 -85
  182. package/dist/scanCardSummary.d.ts +0 -59
  183. package/dist/scanCardSummary.d.ts.map +0 -1
  184. package/dist/scanCardSummary.js +0 -146
  185. package/dist/scanCardSummary.test.d.ts +0 -2
  186. package/dist/scanCardSummary.test.d.ts.map +0 -1
  187. package/dist/scanCardSummary.test.js +0 -234
  188. package/dist/scanDetailViewModel.d.ts.map +0 -1
  189. package/dist/scanDetailViewModel.js +0 -636
  190. package/dist/scanDetailViewModel.test.d.ts +0 -2
  191. package/dist/scanDetailViewModel.test.d.ts.map +0 -1
  192. package/dist/scanDetailViewModel.test.js +0 -530
  193. /package/dist/presentation/dto/{scanCardPresentation.js → assessmentPresentationFields.js} +0 -0
@@ -1,319 +0,0 @@
1
- /**
2
- * GitHub Check Run markdown — policy → sections → dumb renderer.
3
- * Thin projection of ScanResult for PR decision support (not a presentation framework).
4
- */
5
- import { collectWhyBullets } from "./collectNarrativeWhyBullets.js";
6
- import { deriveScanNarrative } from "./deriveScanNarrative.js";
7
- import { formatInsight } from "./formatInsight.js";
8
- import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
9
- import { selectReviewerGuidance } from "./narrativePresentation.js";
10
- import { layerDriverSummary, sortPRInsightsForDisplay, sortRecommendationsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
11
- import { mergePostureLabel } from "./riskVocabulary.js";
12
- import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
13
- // --- Caps (change only with tests) ---
14
- export const CHECK_RUN_MAX_WHY_BULLETS = 3;
15
- export const CHECK_RUN_MAX_ACTION_BULLETS = 3;
16
- export const CHECK_RUN_MAX_REPO_DRIVER_PHRASES = 2;
17
- export const CHECK_RUN_ACTION_BULLET_MAX_CHARS = 220;
18
- export const CHECK_RUN_SOFT_MAX_CHARS = 3800;
19
- const LAYER_LABELS = [
20
- ["security", "Security"],
21
- ["maintainability", "Maintainability"],
22
- ["ecosystem", "Ecosystem"],
23
- ["upgradeImpact", "Upgrade Impact"],
24
- ];
25
- const DEFAULT_SECTION_ORDER = [
26
- "lead",
27
- "why",
28
- "actions",
29
- "baselineOutcome",
30
- "repoContext",
31
- "layerScores",
32
- "footer",
33
- ];
34
- function roundScore(n) {
35
- return Math.round(n);
36
- }
37
- function normalizeOrigin(origin) {
38
- return origin.replace(/\/+$/, "");
39
- }
40
- export function formatScanDashboardUrl(webAppOrigin, scanId) {
41
- return `${normalizeOrigin(webAppOrigin)}/scan/${scanId}`;
42
- }
43
- export function buildPrCheckRunTitle(opts = {}) {
44
- const base = scanSurfaceCopy.checkRun.titleBase;
45
- if (opts.baselineOnly) {
46
- return `${base} - ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
47
- }
48
- return base;
49
- }
50
- /** Strong drivers for optional repo-context line (full mode only). */
51
- export function hasStrongRepoGraphDrivers(result, facts) {
52
- if (facts?.availability.mode === "pr_intelligence" ||
53
- facts?.availability.tiersPresent.tier1) {
54
- return false;
55
- }
56
- const phrases = collectWhyBullets(facts ?? deriveScanNarrative(result), result, CHECK_RUN_MAX_REPO_DRIVER_PHRASES);
57
- return phrases.some((p) => p.trim().length >= 12);
58
- }
59
- function hasActionableBullets(result) {
60
- const insights = Array.isArray(result.insights) ? result.insights : [];
61
- const recs = Array.isArray(result.recommendations)
62
- ? result.recommendations
63
- : [];
64
- const findings = Array.isArray(result.findings) ? result.findings : [];
65
- return insights.length > 0 || recs.length > 0 || findings.length > 0;
66
- }
67
- function insightToBullet(insight) {
68
- const f = formatInsight(insight);
69
- const msg = truncateWithEllipsis(f.message, CHECK_RUN_ACTION_BULLET_MAX_CHARS);
70
- const action = f.action.trim();
71
- if (action && action.length < 120) {
72
- return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
73
- }
74
- return msg;
75
- }
76
- function recommendationToBullet(rec) {
77
- const title = String(rec.title ?? "").trim();
78
- const rat = String(rec.rationale ?? "").trim();
79
- const pkgs = Array.isArray(rec.packages) && rec.packages.length > 0
80
- ? ` (${rec.packages.slice(0, 3).join(", ")}${rec.packages.length > 3 ? ", ..." : ""})`
81
- : "";
82
- if (rat && (!title || rat.length > title.length)) {
83
- return truncateWithEllipsis(rat, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkgs;
84
- }
85
- const line = title || "Review dependency guidance";
86
- return truncateWithEllipsis(line, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkgs;
87
- }
88
- function findingToBullet(f) {
89
- const title = String(f.title ?? "").trim();
90
- const pkg = f.packageName ? ` (\`${f.packageName}\`)` : "";
91
- return (truncateWithEllipsis(title || f.description, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkg);
92
- }
93
- function guidanceToActionBullet(g) {
94
- const msg = truncateWithEllipsis(g.message.trim(), CHECK_RUN_ACTION_BULLET_MAX_CHARS);
95
- const action = g.remediation?.trim();
96
- if (action && action.length < 120) {
97
- return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
98
- }
99
- return normalizeGeneratedText(msg);
100
- }
101
- function buildActionBulletsFromFacts(facts, result, max) {
102
- const prIntel = facts.availability.mode === "pr_intelligence" ||
103
- facts.availability.tiersPresent.tier1;
104
- if (!prIntel) {
105
- return buildActionBullets(result, max);
106
- }
107
- const out = [];
108
- for (const g of selectReviewerGuidance(facts, { scope: "changed", max })) {
109
- out.push(guidanceToActionBullet(g));
110
- if (out.length >= max)
111
- return out.slice(0, max);
112
- }
113
- if (out.length >= max)
114
- return out.slice(0, max);
115
- const legacy = buildActionBullets(result, max - out.length);
116
- return [...out, ...legacy].slice(0, max);
117
- }
118
- function buildActionBullets(result, max) {
119
- const out = [];
120
- const sortedI = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
121
- const sortedR = sortRecommendationsForDisplay(Array.isArray(result.recommendations) ? result.recommendations : []);
122
- const findings = Array.isArray(result.findings) ? result.findings : [];
123
- for (const ins of sortedI) {
124
- if (out.length >= max)
125
- break;
126
- out.push(insightToBullet(ins));
127
- }
128
- for (const rec of sortedR) {
129
- if (out.length >= max)
130
- break;
131
- out.push(recommendationToBullet(rec));
132
- }
133
- for (const f of findings) {
134
- if (out.length >= max)
135
- break;
136
- if (f.severity === "high" || f.severity === "critical") {
137
- out.push(findingToBullet(f));
138
- }
139
- }
140
- return out.slice(0, max);
141
- }
142
- function layerScoreRows(result) {
143
- const layers = result.layerScores ?? {};
144
- const rows = [];
145
- for (const [key, label] of LAYER_LABELS) {
146
- const v = layers[key];
147
- const n = typeof v === "number" && Number.isFinite(v) ? v : null;
148
- if (n === null)
149
- continue;
150
- const drivers = layerDriverSummary(result, key, 120);
151
- if (!drivers)
152
- continue;
153
- rows.push({
154
- label,
155
- score: String(roundScore(n)),
156
- driver: drivers,
157
- });
158
- }
159
- return rows;
160
- }
161
- export function deriveCheckRunPolicy(result, ctx, facts) {
162
- const resolvedFacts = facts ?? deriveScanNarrative(result);
163
- const baseline = ctx.baseline;
164
- const actionBullets = buildActionBulletsFromFacts(resolvedFacts, result, CHECK_RUN_MAX_ACTION_BULLETS);
165
- const layerRows = layerScoreRows(result);
166
- const showRepoGraphContext = !baseline &&
167
- hasStrongRepoGraphDrivers(result, resolvedFacts) &&
168
- typeof result.totalScore === "number" &&
169
- Number.isFinite(result.totalScore);
170
- const showLayerScores = !baseline && layerRows.length > 0;
171
- return {
172
- baseline,
173
- showBaselineOutcome: baseline && actionBullets.length === 0,
174
- showRepoGraphContext,
175
- showLayerScores,
176
- useDetailsForLayers: showLayerScores && layerRows.length >= 2,
177
- maxWhyBullets: CHECK_RUN_MAX_WHY_BULLETS,
178
- maxActionBullets: CHECK_RUN_MAX_ACTION_BULLETS,
179
- maxRepoDriverPhrases: CHECK_RUN_MAX_REPO_DRIVER_PHRASES,
180
- };
181
- }
182
- function buildLeadSection(result, policy) {
183
- const posture = result.decision?.recommendation
184
- ? mergePostureLabel(result.decision.recommendation)
185
- : scanSurfaceCopy.checkRun.mergePostureUnavailable;
186
- const total = typeof result.totalScore === "number" && Number.isFinite(result.totalScore)
187
- ? result.totalScore
188
- : null;
189
- const showRiskIndexInLead = !policy.baseline && !policy.showRepoGraphContext && total !== null;
190
- const riskIndexLine = showRiskIndexInLead
191
- ? `${scanSurfaceCopy.checkRun.repoContextLabel} ${roundScore(total)}/100 (${scanSurfaceCopy.product.riskIndexDirectionShort})`
192
- : null;
193
- return { kind: "lead", posture, riskIndexLine };
194
- }
195
- export function buildPrCheckRunSections(policy, result, ctx, facts) {
196
- const resolvedFacts = facts ?? deriveScanNarrative(result);
197
- const sections = {};
198
- sections.lead = buildLeadSection(result, policy);
199
- const why = collectWhyBullets(resolvedFacts, result, policy.maxWhyBullets);
200
- if (why.length > 0) {
201
- sections.why = { kind: "why", bullets: why };
202
- }
203
- const actions = buildActionBulletsFromFacts(resolvedFacts, result, policy.maxActionBullets);
204
- if (actions.length > 0) {
205
- sections.actions = { kind: "actions", bullets: actions };
206
- }
207
- if (policy.showBaselineOutcome) {
208
- sections.baselineOutcome = {
209
- kind: "baselineOutcome",
210
- primary: scanSurfaceCopy.checkRun.baselineOutcomePrimary,
211
- scope: scanSurfaceCopy.checkRun.baselineOutcomeScope,
212
- };
213
- }
214
- if (policy.showRepoGraphContext) {
215
- const score = roundScore(result.totalScore);
216
- sections.repoContext = {
217
- kind: "repoContext",
218
- score,
219
- driverPhrases: collectWhyBullets(resolvedFacts, result, policy.maxRepoDriverPhrases),
220
- };
221
- }
222
- if (policy.showLayerScores) {
223
- const rows = layerScoreRows(result);
224
- if (rows.length > 0) {
225
- sections.layerScores = { kind: "layerScores", rows };
226
- }
227
- }
228
- sections.footer = {
229
- kind: "footer",
230
- url: formatScanDashboardUrl(ctx.webAppOrigin, ctx.scanId),
231
- label: scanSurfaceCopy.checkRun.footerLinkLabel,
232
- };
233
- const out = [];
234
- for (const key of DEFAULT_SECTION_ORDER) {
235
- const s = sections[key];
236
- if (s)
237
- out.push(s);
238
- }
239
- return out;
240
- }
241
- export function renderCheckRunMarkdown(sections) {
242
- const parts = [];
243
- for (const section of sections) {
244
- switch (section.kind) {
245
- case "lead": {
246
- parts.push(`**${section.posture}**`);
247
- if (section.riskIndexLine) {
248
- parts.push(section.riskIndexLine);
249
- }
250
- parts.push("");
251
- break;
252
- }
253
- case "why": {
254
- for (const b of section.bullets) {
255
- parts.push(`- ${b}`);
256
- }
257
- parts.push("");
258
- break;
259
- }
260
- case "actions": {
261
- for (const b of section.bullets) {
262
- parts.push(`- ${b}`);
263
- }
264
- parts.push("");
265
- break;
266
- }
267
- case "baselineOutcome": {
268
- parts.push(section.primary);
269
- if (section.scope) {
270
- parts.push("");
271
- parts.push(section.scope);
272
- }
273
- parts.push("");
274
- break;
275
- }
276
- case "repoContext": {
277
- const drivers = section.driverPhrases.join(" | ");
278
- parts.push(normalizeGeneratedText(`${scanSurfaceCopy.checkRun.repoContextLabel} ${section.score}/100 - ${drivers}`));
279
- parts.push("");
280
- break;
281
- }
282
- case "layerScores": {
283
- parts.push("<details>");
284
- parts.push(`<summary>${scanSurfaceCopy.checkRun.layerScoresDetailsSummary}</summary>`);
285
- parts.push("");
286
- for (const row of section.rows) {
287
- parts.push(normalizeGeneratedText(`- **${row.label}** ${row.score}/100 - ${row.driver}`));
288
- }
289
- parts.push("");
290
- parts.push("</details>");
291
- parts.push("");
292
- break;
293
- }
294
- case "footer": {
295
- parts.push(`<a href="${section.url}" target="_blank" rel="noopener noreferrer">${section.label}</a>`);
296
- break;
297
- }
298
- default: {
299
- const _exhaustive = section;
300
- void _exhaustive;
301
- }
302
- }
303
- }
304
- let md = normalizeGeneratedText(parts.join("\n").trimEnd());
305
- if (md.length > CHECK_RUN_SOFT_MAX_CHARS) {
306
- md = md.slice(0, CHECK_RUN_SOFT_MAX_CHARS - 1) + "...";
307
- }
308
- return md;
309
- }
310
- export function buildPrCheckRunSummaryMarkdown(ctx) {
311
- const facts = deriveScanNarrative(ctx.result);
312
- const policy = deriveCheckRunPolicy(ctx.result, { baseline: ctx.baseline }, facts);
313
- const sections = buildPrCheckRunSections(policy, ctx.result, ctx, facts);
314
- return renderCheckRunMarkdown(sections);
315
- }
316
- /** Whether baseline scan has no actionable PR bullets (for tests / callers). */
317
- export function checkRunHasActionableContent(result) {
318
- return hasActionableBullets(result);
319
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=prCheckRunPresentation.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prCheckRunPresentation.test.d.ts","sourceRoot":"","sources":["../src/prCheckRunPresentation.test.ts"],"names":[],"mappings":""}
@@ -1,278 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { CHECK_RUN_MAX_ACTION_BULLETS, CHECK_RUN_SOFT_MAX_CHARS, buildPrCheckRunSummaryMarkdown, buildPrCheckRunTitle, deriveCheckRunPolicy, formatScanDashboardUrl, hasStrongRepoGraphDrivers, } from "./prCheckRunPresentation.js";
3
- const ORIGIN = "https://mergesignal-web.fly.dev";
4
- const SCAN_ID = "11111111-1111-4111-8111-111111111111";
5
- function minimalResult(overrides = {}) {
6
- return {
7
- totalScore: 42,
8
- layerScores: {
9
- security: 10,
10
- maintainability: 20,
11
- ecosystem: 30,
12
- upgradeImpact: 15,
13
- },
14
- findings: [],
15
- generatedAt: new Date().toISOString(),
16
- decision: {
17
- recommendation: "needs_review",
18
- confidence: "medium",
19
- reasoning: ["Lockfile adds transitive depth on shared utilities."],
20
- },
21
- explain: {
22
- reasons: [
23
- {
24
- id: "graph.depth",
25
- layer: "ecosystem",
26
- title: "graph.depth",
27
- scoreImpact: 12,
28
- },
29
- ],
30
- },
31
- ...overrides,
32
- };
33
- }
34
- describe("buildPrCheckRunTitle", () => {
35
- it("uses short product title without app prefix", () => {
36
- expect(buildPrCheckRunTitle()).toBe("PR dependency change");
37
- expect(buildPrCheckRunTitle()).not.toContain("MergeSignal scan");
38
- expect(buildPrCheckRunTitle()).not.toContain("—");
39
- });
40
- it("appends baseline suffix when requested", () => {
41
- expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change - baseline scan only");
42
- });
43
- });
44
- describe("formatScanDashboardUrl", () => {
45
- it("strips trailing slash from origin", () => {
46
- expect(formatScanDashboardUrl(`${ORIGIN}/`, SCAN_ID)).toBe(`${ORIGIN}/scan/${SCAN_ID}`);
47
- });
48
- });
49
- describe("deriveCheckRunPolicy", () => {
50
- it("never shows repo graph context in baseline", () => {
51
- const rich = minimalResult({
52
- totalScore: 68,
53
- explain: {
54
- reasons: [
55
- {
56
- id: "graph.transitive",
57
- layer: "ecosystem",
58
- title: "Large transitive dependency volume",
59
- scoreImpact: 20,
60
- },
61
- ],
62
- },
63
- });
64
- const policy = deriveCheckRunPolicy(rich, { baseline: true });
65
- expect(policy.showRepoGraphContext).toBe(false);
66
- expect(policy.baseline).toBe(true);
67
- });
68
- it("shows repo graph context in full only with strong drivers", () => {
69
- const weak = minimalResult({
70
- explain: { reasons: [] },
71
- decision: {
72
- recommendation: "safe",
73
- confidence: "high",
74
- reasoning: [],
75
- },
76
- });
77
- expect(hasStrongRepoGraphDrivers(weak)).toBe(false);
78
- expect(deriveCheckRunPolicy(weak, { baseline: false }).showRepoGraphContext).toBe(false);
79
- const strong = minimalResult({
80
- explain: {
81
- reasons: [
82
- {
83
- id: "graph.depth",
84
- layer: "ecosystem",
85
- title: "Indirect dependency depth exceeds typical baseline",
86
- scoreImpact: 15,
87
- },
88
- ],
89
- },
90
- });
91
- expect(hasStrongRepoGraphDrivers(strong)).toBe(true);
92
- expect(deriveCheckRunPolicy(strong, { baseline: false }).showRepoGraphContext).toBe(true);
93
- });
94
- });
95
- describe("buildPrCheckRunSummaryMarkdown", () => {
96
- it("always includes dashboard footer link", () => {
97
- const md = buildPrCheckRunSummaryMarkdown({
98
- result: minimalResult(),
99
- scanId: SCAN_ID,
100
- webAppOrigin: ORIGIN,
101
- baseline: false,
102
- });
103
- expect(md).toContain(`<a href="${ORIGIN}/scan/${SCAN_ID}" target="_blank" rel="noopener noreferrer">View full scan</a>`);
104
- });
105
- it("baseline body omits repo graph score lines", () => {
106
- const md = buildPrCheckRunSummaryMarkdown({
107
- result: minimalResult({ totalScore: 68 }),
108
- scanId: SCAN_ID,
109
- webAppOrigin: ORIGIN,
110
- baseline: true,
111
- });
112
- expect(md).not.toMatch(/68\/100/);
113
- expect(md).not.toMatch(/moderate risk/i);
114
- expect(md).not.toMatch(/Repository graph/i);
115
- });
116
- it("full mode omits unexplained score when drivers are weak", () => {
117
- const md = buildPrCheckRunSummaryMarkdown({
118
- result: minimalResult({
119
- totalScore: 68,
120
- explain: { reasons: [] },
121
- decision: { recommendation: "safe", confidence: "high", reasoning: [] },
122
- }),
123
- scanId: SCAN_ID,
124
- webAppOrigin: ORIGIN,
125
- baseline: false,
126
- });
127
- expect(md).not.toMatch(/68\/100 —/);
128
- });
129
- it("shows baseline outcome when baseline and no actionable bullets", () => {
130
- const md = buildPrCheckRunSummaryMarkdown({
131
- result: minimalResult({
132
- insights: [],
133
- recommendations: [],
134
- findings: [],
135
- decision: {
136
- recommendation: "safe",
137
- confidence: "high",
138
- reasoning: [],
139
- },
140
- explain: { reasons: [] },
141
- }),
142
- scanId: SCAN_ID,
143
- webAppOrigin: ORIGIN,
144
- baseline: true,
145
- });
146
- expect(md).toContain("No actionable dependency concerns showed up for this PR");
147
- expect(md).not.toMatch(/high-confidence/i);
148
- expect(md).not.toMatch(/did not emit/i);
149
- });
150
- it("does not include standalone changed packages list", () => {
151
- const md = buildPrCheckRunSummaryMarkdown({
152
- result: minimalResult({
153
- insights: [
154
- {
155
- type: "usage_risk",
156
- priority: "high",
157
- confidence: "likely",
158
- scope: "changed",
159
- message: "Runtime path may load updated semver helpers.",
160
- context: "src/version.ts",
161
- remediation: "Run integration tests for version parsing.",
162
- },
163
- ],
164
- }),
165
- scanId: SCAN_ID,
166
- webAppOrigin: ORIGIN,
167
- baseline: false,
168
- });
169
- expect(md).not.toMatch(/^Changed packages:/m);
170
- expect(md).not.toMatch(/^@types\/semver/m);
171
- });
172
- it("caps action bullets and stays under soft char limit for fat ScanResult", () => {
173
- const fat = {
174
- ...minimalResult(),
175
- totalScore: 72,
176
- graphInsights: {
177
- maxDepth: 14,
178
- nodes: 2400,
179
- edges: 9000,
180
- deepest: Array.from({ length: 20 }, (_, i) => ({
181
- kind: "deep",
182
- packageName: `pkg-${i}`,
183
- depth: 10 + i,
184
- direct: false,
185
- via: ["a", "b", "c"],
186
- })),
187
- hotspots: Array.from({ length: 12 }, (_, i) => ({
188
- kind: "hotspot",
189
- packageName: `hot-${i}`,
190
- depth: 3,
191
- direct: false,
192
- })),
193
- vulnerable: Array.from({ length: 8 }, (_, i) => ({
194
- kind: "vulnerable",
195
- packageName: `vuln-${i}`,
196
- depth: 2,
197
- direct: false,
198
- })),
199
- },
200
- insights: Array.from({ length: 30 }, (_, i) => ({
201
- type: "behavioral_change",
202
- priority: "high",
203
- confidence: "confirmed",
204
- scope: "changed",
205
- message: `Insight message ${i} with extra detail about runtime behavior and dependency coupling.`,
206
- context: `src/module-${i}/index.ts`,
207
- remediation: `Remediation step ${i} including verify, test, and document.`,
208
- })),
209
- recommendations: Array.from({ length: 25 }, (_, i) => ({
210
- id: `rec-${i}`,
211
- title: `Recommendation title ${i}`,
212
- rationale: `Long rationale ${i} explaining ecosystem overlap and transitive churn in multiple sentences.`,
213
- impact: "high",
214
- packages: [`pkg-a-${i}`, `pkg-b-${i}`],
215
- })),
216
- explain: {
217
- reasons: Array.from({ length: 40 }, (_, i) => ({
218
- id: `graph.metric.${i}`,
219
- layer: (["security", "maintainability", "ecosystem", "upgradeImpact"][i % 4] ?? "ecosystem"),
220
- title: `graph.transitive volume cluster ${i}`,
221
- scoreImpact: 5 + i,
222
- })),
223
- },
224
- };
225
- const md = buildPrCheckRunSummaryMarkdown({
226
- result: fat,
227
- scanId: SCAN_ID,
228
- webAppOrigin: ORIGIN,
229
- baseline: false,
230
- });
231
- expect(md).toContain("Insight message 0");
232
- expect(md).not.toContain("Insight message 3");
233
- const insightBullets = md
234
- .split("\n")
235
- .filter((l) => l.includes("Insight message"));
236
- expect(insightBullets.length).toBeLessThanOrEqual(CHECK_RUN_MAX_ACTION_BULLETS);
237
- expect(md.length).toBeLessThanOrEqual(CHECK_RUN_SOFT_MAX_CHARS);
238
- expect(md).not.toContain("pkg-0 is reached transitively");
239
- expect(md).not.toContain("topology");
240
- expect(md).not.toMatch(/moderate risk/i);
241
- });
242
- it("pairs layer scores with drivers in details when shown", () => {
243
- const md = buildPrCheckRunSummaryMarkdown({
244
- result: minimalResult({
245
- layerScores: {
246
- security: 0,
247
- maintainability: 45,
248
- ecosystem: 50,
249
- upgradeImpact: 10,
250
- },
251
- explain: {
252
- reasons: [
253
- {
254
- id: "maint.band",
255
- layer: "maintainability",
256
- title: "Stale releases on multiple direct dependencies",
257
- scoreImpact: 18,
258
- },
259
- ],
260
- },
261
- contributions: [
262
- {
263
- id: "maint-stale",
264
- layer: "maintainability",
265
- scoreImpact: 10,
266
- },
267
- ],
268
- }),
269
- scanId: SCAN_ID,
270
- webAppOrigin: ORIGIN,
271
- baseline: false,
272
- });
273
- if (md.includes("Layer scores")) {
274
- expect(md).toMatch(/Maintainability.*\/100 -/);
275
- expect(md).not.toMatch(/\| Security \| 0 \|/);
276
- }
277
- });
278
- });
@@ -1,27 +0,0 @@
1
- import type { FindingCountSummary, PresentationConfidence, PresentationDensity, PresentationEvidenceContext, PresentationEvidenceRow, PresentationStatus } from "./types.js";
2
- import type { PresentationIntent } from "../intent/presentationIntent.js";
3
- export type ScanCardPresentation = {
4
- pipeline?: {
5
- status: "queued" | "running" | "failed";
6
- headline: string;
7
- subheadline?: string;
8
- };
9
- status?: PresentationStatus;
10
- density?: PresentationDensity;
11
- confidence?: PresentationConfidence;
12
- evidenceContext?: PresentationEvidenceContext;
13
- headline: string;
14
- subheadline?: string;
15
- changedPackages: string[];
16
- primaryPackage?: string;
17
- keyPoints: string[];
18
- affectedAreas: string[];
19
- verificationActions: string[];
20
- evidence: PresentationEvidenceRow[];
21
- supportingContext?: string[];
22
- riskIndex?: number | null;
23
- findingCounts?: FindingCountSummary | null;
24
- actionLabel?: string;
25
- presentationIntent?: PresentationIntent;
26
- };
27
- //# sourceMappingURL=scanCardPresentation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanCardPresentation.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/scanCardPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE;QACT,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;QACxC,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,uBAAuB,EAAE,CAAC;IAEpC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC,CAAC"}
@@ -1,4 +0,0 @@
1
- import type { ScanNarrativeFacts } from "../../scanNarrativeFacts.js";
2
- import type { PresentationInterpretation } from "./presentationIntent.js";
3
- export declare function derivePresentationInterpretation(facts: ScanNarrativeFacts): PresentationInterpretation;
4
- //# sourceMappingURL=derivePresentationInterpretation.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"derivePresentationInterpretation.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/derivePresentationInterpretation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAMV,kBAAkB,EACnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,yBAAyB,CAAC;AAsJjC,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,kBAAkB,GACxB,0BAA0B,CAkG5B"}