@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.
Files changed (181) 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 +22 -2
  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 +4 -34
  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/buildGitHubCheckRunOutput.d.ts +20 -0
  35. package/dist/presentation/buildGitHubCheckRunOutput.d.ts.map +1 -0
  36. package/dist/presentation/buildGitHubCheckRunOutput.js +42 -0
  37. package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts +2 -0
  38. package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts.map +1 -0
  39. package/dist/presentation/buildGitHubCheckRunOutput.test.js +188 -0
  40. package/dist/presentation/compose/narrativeChannels.d.ts.map +1 -1
  41. package/dist/presentation/compose/narrativeChannels.js +13 -43
  42. package/dist/presentation/compose/narrativeCompose.d.ts.map +1 -1
  43. package/dist/presentation/compose/narrativeCompose.js +2 -5
  44. package/dist/presentation/dto/assessmentPresentationFields.d.ts +13 -0
  45. package/dist/presentation/dto/assessmentPresentationFields.d.ts.map +1 -0
  46. package/dist/presentation/dto/assessmentPresentationFields.js +1 -0
  47. package/dist/presentation/dto/dashboardCardPresentation.d.ts +2 -1
  48. package/dist/presentation/dto/dashboardCardPresentation.d.ts.map +1 -1
  49. package/dist/presentation/dto/githubAndCliPresentation.d.ts +4 -3
  50. package/dist/presentation/dto/githubAndCliPresentation.d.ts.map +1 -1
  51. package/dist/presentation/dto/scanDetailsPresentation.d.ts +2 -1
  52. package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -1
  53. package/dist/presentation/fixtures/dashboardCardGolden.test.js +21 -14
  54. package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts.map +1 -1
  55. package/dist/presentation/fixtures/presentationPersonaFixtures.js +31 -24
  56. package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -1
  57. package/dist/presentation/fixtures/scanResultFixtures.js +14 -3
  58. package/dist/presentation/index.d.ts +2 -2
  59. package/dist/presentation/index.d.ts.map +1 -1
  60. package/dist/presentation/index.js +2 -2
  61. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -1
  62. package/dist/presentation/orchestration/buildScanCardPresentation.js +8 -3
  63. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts +1 -1
  64. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -1
  65. package/dist/presentation/orchestration/buildScanPresentationBundle.js +12 -10
  66. package/dist/presentation/orchestration/scanPresentationBundle.d.ts +8 -3
  67. package/dist/presentation/orchestration/scanPresentationBundle.d.ts.map +1 -1
  68. package/dist/presentation/presentationPersonas.test.js +6 -6
  69. package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -1
  70. package/dist/presentation/presenters/presentCliScanSummary.js +2 -0
  71. package/dist/presentation/presenters/presentDashboardCard.d.ts +1 -0
  72. package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -1
  73. package/dist/presentation/presenters/presentDashboardCard.js +18 -0
  74. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -1
  75. package/dist/presentation/presenters/presentGitHubCheckRun.js +2 -0
  76. package/dist/presentation/presenters/presentGitHubPrComment.d.ts.map +1 -1
  77. package/dist/presentation/presenters/presentGitHubPrComment.js +15 -21
  78. package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -1
  79. package/dist/presentation/presenters/presentScanDetails.js +52 -129
  80. package/dist/presentation/profile/buildProfileFromAssessment.d.ts +5 -0
  81. package/dist/presentation/profile/buildProfileFromAssessment.d.ts.map +1 -0
  82. package/dist/presentation/profile/buildProfileFromAssessment.js +58 -0
  83. package/dist/presentation/projectAssessmentFields.d.ts +4 -0
  84. package/dist/presentation/projectAssessmentFields.d.ts.map +1 -0
  85. package/dist/presentation/projectAssessmentFields.js +13 -0
  86. package/dist/presentation/surfaceParity.test.d.ts +2 -0
  87. package/dist/presentation/surfaceParity.test.d.ts.map +1 -0
  88. package/dist/presentation/surfaceParity.test.js +74 -0
  89. package/dist/productMessaging.d.ts +4 -4
  90. package/dist/productMessaging.d.ts.map +1 -1
  91. package/dist/productMessaging.js +4 -4
  92. package/dist/productMessaging.test.js +2 -2
  93. package/dist/repoIntelligenceSchema.d.ts +2 -2
  94. package/dist/resolvePipelineStatus.d.ts +15 -0
  95. package/dist/resolvePipelineStatus.d.ts.map +1 -0
  96. package/dist/resolvePipelineStatus.js +37 -0
  97. package/dist/scanCardPresentationState.d.ts +10 -0
  98. package/dist/scanCardPresentationState.d.ts.map +1 -0
  99. package/dist/scanCardPresentationState.js +4 -0
  100. package/dist/{scanDetailViewModel.d.ts → scanDetailViewModelTypes.d.ts} +60 -32
  101. package/dist/scanDetailViewModelTypes.d.ts.map +1 -0
  102. package/dist/scanDetailViewModelTypes.js +4 -0
  103. package/dist/scanResultSchema.d.ts +75 -2
  104. package/dist/scanResultSchema.d.ts.map +1 -1
  105. package/dist/scanResultSchema.js +4 -2
  106. package/dist/scanResultSchema.test.js +16 -0
  107. package/dist/scanSurfaceCopy.d.ts +2 -0
  108. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  109. package/dist/scanSurfaceCopy.js +2 -0
  110. package/dist/trustedScanGuards.test.js +7 -0
  111. package/dist/types.d.ts +3 -0
  112. package/dist/types.d.ts.map +1 -1
  113. package/package.json +1 -1
  114. package/dist/collectNarrativeWhyBullets.d.ts +0 -12
  115. package/dist/collectNarrativeWhyBullets.d.ts.map +0 -1
  116. package/dist/collectNarrativeWhyBullets.js +0 -104
  117. package/dist/deriveCardDisplaySummary.d.ts +0 -16
  118. package/dist/deriveCardDisplaySummary.d.ts.map +0 -1
  119. package/dist/deriveCardDisplaySummary.js +0 -56
  120. package/dist/deriveCardDisplaySummary.test.d.ts +0 -2
  121. package/dist/deriveCardDisplaySummary.test.d.ts.map +0 -1
  122. package/dist/deriveCardDisplaySummary.test.js +0 -89
  123. package/dist/deriveCardOperationalObservations.d.ts +0 -19
  124. package/dist/deriveCardOperationalObservations.d.ts.map +0 -1
  125. package/dist/deriveCardOperationalObservations.js +0 -180
  126. package/dist/deriveCardOperationalObservations.test.d.ts +0 -2
  127. package/dist/deriveCardOperationalObservations.test.d.ts.map +0 -1
  128. package/dist/deriveCardOperationalObservations.test.js +0 -268
  129. package/dist/deriveRiskScoreSummary.d.ts +0 -33
  130. package/dist/deriveRiskScoreSummary.d.ts.map +0 -1
  131. package/dist/deriveRiskScoreSummary.js +0 -90
  132. package/dist/deriveRiskScoreSummary.test.d.ts +0 -2
  133. package/dist/deriveRiskScoreSummary.test.d.ts.map +0 -1
  134. package/dist/deriveRiskScoreSummary.test.js +0 -47
  135. package/dist/deriveScanDetailRecommendations.d.ts +0 -63
  136. package/dist/deriveScanDetailRecommendations.d.ts.map +0 -1
  137. package/dist/deriveScanDetailRecommendations.js +0 -725
  138. package/dist/deriveScanDetailRecommendations.test.d.ts +0 -2
  139. package/dist/deriveScanDetailRecommendations.test.d.ts.map +0 -1
  140. package/dist/deriveScanDetailRecommendations.test.js +0 -181
  141. package/dist/deriveScanSummaryText.d.ts +0 -7
  142. package/dist/deriveScanSummaryText.d.ts.map +0 -1
  143. package/dist/deriveScanSummaryText.js +0 -24
  144. package/dist/deriveScanSummaryText.test.d.ts +0 -2
  145. package/dist/deriveScanSummaryText.test.d.ts.map +0 -1
  146. package/dist/deriveScanSummaryText.test.js +0 -77
  147. package/dist/formatCardEvidenceCounts.d.ts +0 -7
  148. package/dist/formatCardEvidenceCounts.d.ts.map +0 -1
  149. package/dist/formatCardEvidenceCounts.js +0 -19
  150. package/dist/prCheckRunPresentation.d.ts +0 -68
  151. package/dist/prCheckRunPresentation.d.ts.map +0 -1
  152. package/dist/prCheckRunPresentation.js +0 -319
  153. package/dist/prCheckRunPresentation.test.d.ts +0 -2
  154. package/dist/prCheckRunPresentation.test.d.ts.map +0 -1
  155. package/dist/prCheckRunPresentation.test.js +0 -278
  156. package/dist/presentation/intent/derivePresentationInterpretation.d.ts +0 -4
  157. package/dist/presentation/intent/derivePresentationInterpretation.d.ts.map +0 -1
  158. package/dist/presentation/intent/derivePresentationInterpretation.js +0 -202
  159. package/dist/presentation/intent/formatPresentationCopy.d.ts +0 -8
  160. package/dist/presentation/intent/formatPresentationCopy.d.ts.map +0 -1
  161. package/dist/presentation/intent/formatPresentationCopy.js +0 -140
  162. package/dist/presentation/intent/presentationIntent.test.d.ts +0 -2
  163. package/dist/presentation/intent/presentationIntent.test.d.ts.map +0 -1
  164. package/dist/presentation/intent/presentationIntent.test.js +0 -29
  165. package/dist/presentation/intent/verificationFocusLabels.d.ts +0 -2
  166. package/dist/presentation/intent/verificationFocusLabels.d.ts.map +0 -1
  167. package/dist/presentation/intent/verificationFocusLabels.js +0 -45
  168. package/dist/presentation/profile/derivePresentationProfile.d.ts +0 -12
  169. package/dist/presentation/profile/derivePresentationProfile.d.ts.map +0 -1
  170. package/dist/presentation/profile/derivePresentationProfile.js +0 -85
  171. package/dist/scanCardSummary.d.ts +0 -58
  172. package/dist/scanCardSummary.d.ts.map +0 -1
  173. package/dist/scanCardSummary.js +0 -146
  174. package/dist/scanCardSummary.test.d.ts +0 -2
  175. package/dist/scanCardSummary.test.d.ts.map +0 -1
  176. package/dist/scanCardSummary.test.js +0 -234
  177. package/dist/scanDetailViewModel.d.ts.map +0 -1
  178. package/dist/scanDetailViewModel.js +0 -636
  179. package/dist/scanDetailViewModel.test.d.ts +0 -2
  180. package/dist/scanDetailViewModel.test.d.ts.map +0 -1
  181. package/dist/scanDetailViewModel.test.js +0 -530
@@ -1,89 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { deriveCardDisplaySummary, isGenericCardSummary, } from "./deriveCardDisplaySummary.js";
3
- import { truncateCardSummary } from "./truncateCardSummary.js";
4
- import { formatCardAreaLabels, joinCardAreaLabels, } from "./formatCardAreaLabels.js";
5
- import { formatCardEvidenceCounts } from "./formatCardEvidenceCounts.js";
6
- describe("isGenericCardSummary", () => {
7
- it("flags platform boilerplate", () => {
8
- expect(isGenericCardSummary("Potential runtime impact detected")).toBe(true);
9
- expect(isGenericCardSummary("No high-confidence merge risk from dependency change")).toBe(true);
10
- expect(isGenericCardSummary("No high-confidence merge risks from this PR dependency change")).toBe(true);
11
- });
12
- it("allows concrete operational copy", () => {
13
- expect(isGenericCardSummary("This upgrade changes websocket reconnect timing in the API layer")).toBe(false);
14
- });
15
- });
16
- describe("truncateCardSummary", () => {
17
- it("truncates at sentence boundary when possible", () => {
18
- const text = "First sentence is clear. Second sentence adds more detail that should be cut.";
19
- const out = truncateCardSummary(text, 50);
20
- expect(out).toBe("First sentence is clear.");
21
- });
22
- it("uses ellipsis at word boundary", () => {
23
- const text = "This upgrade changes retry behavior in payment polling handlers";
24
- const out = truncateCardSummary(text, 40);
25
- expect(out.endsWith("…")).toBe(true);
26
- expect(out.length).toBeLessThanOrEqual(43);
27
- });
28
- });
29
- describe("formatCardAreaLabels", () => {
30
- it("drops paths and generic taxonomy", () => {
31
- expect(formatCardAreaLabels([
32
- "packages/web/src/auth",
33
- "Auth flows",
34
- "Session refresh flow",
35
- ])).toEqual(["Session refresh flow"]);
36
- });
37
- it("prefers longer specific labels", () => {
38
- const out = formatCardAreaLabels(["API routes", "Payment retry handling"]);
39
- expect(out[0]).toBe("Payment retry handling");
40
- });
41
- });
42
- describe("deriveCardDisplaySummary", () => {
43
- const emptyCounts = { critical: 0, high: 0, medium: 0, low: 0 };
44
- it("returns null for quiet safe cards", () => {
45
- expect(deriveCardDisplaySummary({
46
- mergePosture: "safe",
47
- rawSummary: "No merge blockers detected",
48
- findingCounts: emptyCounts,
49
- topAffectedAreas: [],
50
- })).toBeNull();
51
- });
52
- it("surfaces affected areas on quiet safe cards when specific", () => {
53
- expect(deriveCardDisplaySummary({
54
- mergePosture: "safe",
55
- rawSummary: "No high-confidence merge risks",
56
- findingCounts: emptyCounts,
57
- topAffectedAreas: ["Session refresh flow"],
58
- })).toBe("Session refresh flow");
59
- });
60
- it("uses specific reasoning for risky", () => {
61
- expect(deriveCardDisplaySummary({
62
- mergePosture: "risky",
63
- rawSummary: "Auth boundary change in session middleware",
64
- findingCounts: { ...emptyCounts, high: 1 },
65
- topAffectedAreas: [],
66
- })).toBe("Auth boundary change in session middleware");
67
- });
68
- it("falls back to formatted areas when reasoning is generic", () => {
69
- expect(deriveCardDisplaySummary({
70
- mergePosture: "needs_review",
71
- rawSummary: "Potential runtime impact detected",
72
- findingCounts: emptyCounts,
73
- topAffectedAreas: ["Payment retry handling", "Auth flows"],
74
- })).toBe("Payment retry handling");
75
- });
76
- });
77
- describe("formatCardEvidenceCounts", () => {
78
- it("uses soft phrasing for risky critical", () => {
79
- expect(formatCardEvidenceCounts({ critical: 2, high: 0, medium: 0, low: 0 }, "risky")).toBe("Critical findings present");
80
- });
81
- it("returns null for safe", () => {
82
- expect(formatCardEvidenceCounts({ critical: 1, high: 1, medium: 0, low: 0 }, "safe")).toBeNull();
83
- });
84
- });
85
- describe("joinCardAreaLabels", () => {
86
- it("joins with middle dot", () => {
87
- expect(joinCardAreaLabels(["Session refresh flow", "Checkout handlers"])).toBe("Session refresh flow · Checkout handlers");
88
- });
89
- });
@@ -1,19 +0,0 @@
1
- import { type CatalogPhrase } from "./cardObservationCatalog.js";
2
- import type { MergePosture } from "./riskVocabulary.js";
3
- import type { ScanResult } from "./types.js";
4
- export type CardOperationalObservations = {
5
- operationalObservations: CatalogPhrase[];
6
- supportingLine: string | null;
7
- };
8
- export type DeriveCardOperationalObservationsOptions = {
9
- mergePosture: MergePosture | null;
10
- /** Primary path: full ScanResult JSON present. Never emit posture fallbacks. */
11
- hasFullResult?: boolean;
12
- max?: number;
13
- };
14
- /**
15
- * Derive 0–3 catalog-mapped operational observations for PR dashboard cards.
16
- * Primary path: silence when no mappable signals (G2).
17
- */
18
- export declare function deriveCardOperationalObservations(result: ScanResult | null | undefined, options: DeriveCardOperationalObservationsOptions): CardOperationalObservations;
19
- //# sourceMappingURL=deriveCardOperationalObservations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deriveCardOperationalObservations.d.ts","sourceRoot":"","sources":["../src/deriveCardOperationalObservations.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EASnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,2BAA2B,GAAG;IACxC,uBAAuB,EAAE,aAAa,EAAE,CAAC;IACzC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,wCAAwC,GAAG;IACrD,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAiNF;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACrC,OAAO,EAAE,wCAAwC,GAChD,2BAA2B,CA4B7B"}
@@ -1,180 +0,0 @@
1
- import { sortRecommendationsForDisplay } from "./actionsStepSummary.js";
2
- import { mapContributionToCatalogPhrase, mapExplainReasonToCatalogPhrase, mapFindingToCatalogPhrase, mapGraphInsightsToCatalogPhrases, mapRecommendationToCatalogPhrase, mapTextToCatalogPhrase, } from "./cardObservationCatalog.js";
3
- import { formatCardAreaLabels } from "./formatCardAreaLabels.js";
4
- import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
5
- const SOURCE_WEIGHT = {
6
- recommendation: 100,
7
- explain: 80,
8
- finding: 60,
9
- graph: 50,
10
- contribution: 45,
11
- summary: 20,
12
- area: 15,
13
- };
14
- let rankCounter = 0;
15
- function nextRank() {
16
- rankCounter += 1;
17
- return rankCounter;
18
- }
19
- function addCandidate(bucket, candidate) {
20
- bucket.push({ ...candidate, rank: nextRank() });
21
- }
22
- function collectCandidates(result) {
23
- const candidates = [];
24
- rankCounter = 0;
25
- const recs = sortRecommendationsForDisplay(Array.isArray(result.recommendations) ? result.recommendations : []);
26
- for (const rec of recs) {
27
- const mapped = mapRecommendationToCatalogPhrase(rec);
28
- if (mapped) {
29
- addCandidate(candidates, {
30
- ...mapped,
31
- weight: SOURCE_WEIGHT.recommendation,
32
- });
33
- }
34
- }
35
- if (Array.isArray(result.explain?.reasons)) {
36
- const sorted = [...result.explain.reasons]
37
- .filter((r) => r.title)
38
- .sort((a, b) => Math.abs(b.scoreImpact ?? 0) - Math.abs(a.scoreImpact ?? 0));
39
- for (const reason of sorted) {
40
- const mapped = mapExplainReasonToCatalogPhrase(reason);
41
- if (mapped) {
42
- addCandidate(candidates, {
43
- ...mapped,
44
- weight: SOURCE_WEIGHT.explain,
45
- });
46
- }
47
- }
48
- }
49
- if (Array.isArray(result.findings)) {
50
- const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
51
- const sortedFindings = [...result.findings].sort((a, b) => (severityOrder[b.severity] ?? 0) - (severityOrder[a.severity] ?? 0));
52
- for (const finding of sortedFindings) {
53
- const mapped = mapFindingToCatalogPhrase(finding);
54
- if (mapped) {
55
- addCandidate(candidates, {
56
- ...mapped,
57
- weight: SOURCE_WEIGHT.finding,
58
- });
59
- }
60
- }
61
- }
62
- for (const mapped of mapGraphInsightsToCatalogPhrases(result.graphInsights)) {
63
- addCandidate(candidates, {
64
- ...mapped,
65
- weight: SOURCE_WEIGHT.graph,
66
- });
67
- }
68
- if (Array.isArray(result.contributions)) {
69
- for (const c of result.contributions) {
70
- const mapped = mapContributionToCatalogPhrase(c.id);
71
- if (mapped) {
72
- addCandidate(candidates, {
73
- ...mapped,
74
- weight: SOURCE_WEIGHT.contribution,
75
- });
76
- }
77
- }
78
- }
79
- const reasoning = result.decision?.reasoning;
80
- if (Array.isArray(reasoning)) {
81
- for (const line of reasoning) {
82
- const mapped = mapTextToCatalogPhrase(line);
83
- if (mapped) {
84
- addCandidate(candidates, {
85
- ...mapped,
86
- weight: SOURCE_WEIGHT.summary,
87
- });
88
- }
89
- }
90
- }
91
- if (Array.isArray(result.insights)) {
92
- for (const ins of result.insights) {
93
- const mapped = mapTextToCatalogPhrase(ins.message);
94
- if (mapped) {
95
- addCandidate(candidates, {
96
- ...mapped,
97
- weight: SOURCE_WEIGHT.summary,
98
- });
99
- }
100
- }
101
- }
102
- return candidates;
103
- }
104
- function selectObservations(candidates, max) {
105
- const byFamily = new Map();
106
- for (const c of candidates) {
107
- const existing = byFamily.get(c.family);
108
- if (!existing || c.weight > existing.weight) {
109
- byFamily.set(c.family, c);
110
- }
111
- else if (c.weight === existing.weight && c.rank < existing.rank) {
112
- byFamily.set(c.family, c);
113
- }
114
- }
115
- const distinct = [...byFamily.values()].sort((a, b) => {
116
- if (b.weight !== a.weight)
117
- return b.weight - a.weight;
118
- return a.rank - b.rank;
119
- });
120
- if (distinct.length === 0)
121
- return [];
122
- const topWeight = distinct[0].weight;
123
- const strongFamilies = distinct.filter((c) => c.weight >= topWeight - 15 || c.weight >= SOURCE_WEIGHT.finding);
124
- let take = distinct;
125
- if (strongFamilies.length <= 2 && distinct.length > 2) {
126
- take = distinct.slice(0, 2);
127
- }
128
- else if (distinct.length > max) {
129
- take = distinct.slice(0, max);
130
- }
131
- return take.map((c) => c.phrase);
132
- }
133
- function deriveSupportingLine(result, observations, candidates) {
134
- if (observations.length !== 1)
135
- return null;
136
- const usedPhrase = observations[0];
137
- const usedFamily = candidates.find((c) => c.phrase === usedPhrase)?.family;
138
- const secondCatalog = candidates.find((c) => c.phrase !== usedPhrase && c.family !== usedFamily);
139
- if (secondCatalog)
140
- return secondCatalog.phrase;
141
- const areas = formatCardAreaLabels(selectTopAffectedAreas(result, { max: 2 }));
142
- const area = areas[0];
143
- if (area && area.length <= 48 && !/\d/.test(area)) {
144
- return area;
145
- }
146
- return null;
147
- }
148
- function denormalizedFallback(mergePosture) {
149
- if (mergePosture === "safe") {
150
- return { operationalObservations: [], supportingLine: null };
151
- }
152
- if (mergePosture === "needs_review") {
153
- return { operationalObservations: [], supportingLine: null };
154
- }
155
- return { operationalObservations: [], supportingLine: null };
156
- }
157
- /**
158
- * Derive 0–3 catalog-mapped operational observations for PR dashboard cards.
159
- * Primary path: silence when no mappable signals (G2).
160
- */
161
- export function deriveCardOperationalObservations(result, options) {
162
- const max = options.max ?? 3;
163
- const hasFullResult = options.hasFullResult !== false;
164
- if (!result) {
165
- if (hasFullResult) {
166
- return { operationalObservations: [], supportingLine: null };
167
- }
168
- return denormalizedFallback(options.mergePosture);
169
- }
170
- const candidates = collectCandidates(result);
171
- const operationalObservations = selectObservations(candidates, max);
172
- if (operationalObservations.length === 0) {
173
- if (hasFullResult) {
174
- return { operationalObservations: [], supportingLine: null };
175
- }
176
- return denormalizedFallback(options.mergePosture);
177
- }
178
- const supportingLine = deriveSupportingLine(result, operationalObservations, candidates);
179
- return { operationalObservations, supportingLine };
180
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=deriveCardOperationalObservations.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deriveCardOperationalObservations.test.d.ts","sourceRoot":"","sources":["../src/deriveCardOperationalObservations.test.ts"],"names":[],"mappings":""}
@@ -1,268 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CARD_OBSERVATION_CATALOG, containsImperativeOrActionLanguage, containsTelemetryOrDigits, isCatalogPhrase, isGenericObservation, mapRecommendationToCatalogPhrase, mapTextToCatalogPhrase, validateCatalogIntegrity, } from "./cardObservationCatalog.js";
3
- import { deriveCardOperationalObservations } from "./deriveCardOperationalObservations.js";
4
- const baseResult = {
5
- totalScore: 10,
6
- layerScores: {
7
- security: 1,
8
- maintainability: 2,
9
- ecosystem: 3,
10
- upgradeImpact: 4,
11
- },
12
- findings: [],
13
- generatedAt: "2026-01-01T00:00:00.000Z",
14
- };
15
- describe("cardObservationCatalog", () => {
16
- it("validates catalog integrity", () => {
17
- expect(() => validateCatalogIntegrity()).not.toThrow();
18
- });
19
- it("keeps all catalog phrases within length and guardrails", () => {
20
- for (const phrase of CARD_OBSERVATION_CATALOG) {
21
- expect(isCatalogPhrase(phrase)).toBe(true);
22
- expect(containsTelemetryOrDigits(phrase)).toBe(false);
23
- expect(containsImperativeOrActionLanguage(phrase)).toBe(false);
24
- expect(isGenericObservation(phrase)).toBe(false);
25
- expect(phrase.length).toBeLessThanOrEqual(48);
26
- }
27
- });
28
- it("maps recommendation imperatives to detection phrases", () => {
29
- const mapped = mapRecommendationToCatalogPhrase({
30
- id: "rec-1",
31
- title: "Reduce duplicate dependency versions",
32
- rationale: "Overlapping semver ranges on runtime boundary",
33
- impact: "high",
34
- });
35
- expect(mapped?.phrase).toBe("Duplicate dependency versions detected");
36
- });
37
- it("maps graph.duplicates tokens to duplicate versions phrase", () => {
38
- const mapped = mapTextToCatalogPhrase("graph.duplicates on runtime boundary");
39
- expect(mapped?.phrase).toBe("Duplicate dependency versions detected");
40
- });
41
- it("rejects generic narration", () => {
42
- expect(isGenericObservation("No high-confidence merge risks")).toBe(true);
43
- expect(isGenericObservation("dependency concerns detected")).toBe(true);
44
- });
45
- });
46
- describe("deriveCardOperationalObservations", () => {
47
- it("returns silence on primary path when no mappable signals", () => {
48
- const result = deriveCardOperationalObservations({
49
- ...baseResult,
50
- decision: {
51
- recommendation: "safe",
52
- confidence: "high",
53
- reasoning: [
54
- "No high-confidence merge risks from this PR dependency change",
55
- ],
56
- },
57
- }, { mergePosture: "safe", hasFullResult: true });
58
- expect(result.operationalObservations).toEqual([]);
59
- expect(result.supportingLine).toBeNull();
60
- });
61
- it("surfaces catalog phrase from explain.reasons instead of generic reasoning", () => {
62
- const result = deriveCardOperationalObservations({
63
- ...baseResult,
64
- totalScore: 55,
65
- decision: {
66
- recommendation: "needs_review",
67
- confidence: "medium",
68
- reasoning: ["Potential runtime impact detected"],
69
- },
70
- explain: {
71
- reasons: [
72
- {
73
- id: "graph.transitive.1",
74
- layer: "ecosystem",
75
- title: "graph.transitive volume cluster",
76
- scoreImpact: 18,
77
- },
78
- ],
79
- },
80
- }, { mergePosture: "needs_review", hasFullResult: true });
81
- expect(result.operationalObservations).toContain("High transitive dependency volume");
82
- expect(result.operationalObservations.some((o) => /merge risk|runtime impact/i.test(o))).toBe(false);
83
- });
84
- it("maps duplicate signal from recommendation without leaking imperative title", () => {
85
- const result = deriveCardOperationalObservations({
86
- ...baseResult,
87
- recommendations: [
88
- {
89
- id: "rec-dup",
90
- title: "Reduce duplicate dependency versions",
91
- rationale: "Multiple semver majors on shared packages",
92
- impact: "high",
93
- priorityScore: 90,
94
- },
95
- ],
96
- }, { mergePosture: "needs_review", hasFullResult: true });
97
- expect(result.operationalObservations).toEqual([
98
- "Duplicate dependency versions detected",
99
- ]);
100
- expect(result.operationalObservations.some((o) => /^reduce/i.test(o))).toBe(false);
101
- });
102
- it("caps at three distinct catalog phrases on fat scans", () => {
103
- const fat = {
104
- ...baseResult,
105
- totalScore: 72,
106
- recommendations: [
107
- {
108
- id: "r1",
109
- title: "Reduce duplicate dependency versions",
110
- rationale: "semver overlap",
111
- impact: "high",
112
- priorityScore: 95,
113
- },
114
- {
115
- id: "r2",
116
- title: "Review transitive dependency surface",
117
- rationale: "graph.transitive volume",
118
- impact: "high",
119
- priorityScore: 90,
120
- },
121
- ],
122
- explain: {
123
- reasons: [
124
- {
125
- id: "g1",
126
- layer: "security",
127
- title: "graph.vulnerable packages",
128
- scoreImpact: 20,
129
- },
130
- {
131
- id: "g2",
132
- layer: "maintainability",
133
- title: "Stale releases on multiple direct dependencies",
134
- scoreImpact: 15,
135
- },
136
- ],
137
- },
138
- graphInsights: {
139
- maxDepth: 9,
140
- nodes: 1500,
141
- edges: 4000,
142
- vulnerable: [
143
- {
144
- kind: "vulnerable",
145
- packageName: "lodash",
146
- direct: false,
147
- depth: 3,
148
- },
149
- ],
150
- hotspots: Array.from({ length: 5 }, (_, i) => ({
151
- kind: "hotspot",
152
- packageName: `pkg-${i}`,
153
- direct: true,
154
- depth: 1,
155
- })),
156
- },
157
- };
158
- const result = deriveCardOperationalObservations(fat, {
159
- mergePosture: "risky",
160
- hasFullResult: true,
161
- max: 3,
162
- });
163
- expect(result.operationalObservations.length).toBeLessThanOrEqual(3);
164
- for (const phrase of result.operationalObservations) {
165
- expect(isCatalogPhrase(phrase)).toBe(true);
166
- expect(containsTelemetryOrDigits(phrase)).toBe(false);
167
- expect(containsImperativeOrActionLanguage(phrase)).toBe(false);
168
- }
169
- expect(result.supportingLine).toBeNull();
170
- });
171
- it("prefers one to two observations when one family dominates", () => {
172
- const result = deriveCardOperationalObservations({
173
- ...baseResult,
174
- explain: {
175
- reasons: [
176
- {
177
- id: "t1",
178
- layer: "ecosystem",
179
- title: "graph.transitive volume cluster 1",
180
- scoreImpact: 30,
181
- },
182
- {
183
- id: "t2",
184
- layer: "ecosystem",
185
- title: "graph.transitive volume cluster 2",
186
- scoreImpact: 25,
187
- },
188
- {
189
- id: "t3",
190
- layer: "ecosystem",
191
- title: "graph.transitive volume cluster 3",
192
- scoreImpact: 20,
193
- },
194
- ],
195
- },
196
- }, { mergePosture: "needs_review", hasFullResult: true });
197
- expect(result.operationalObservations).toEqual([
198
- "High transitive dependency volume",
199
- ]);
200
- });
201
- it("omits supporting line when three observations are shown", () => {
202
- const result = deriveCardOperationalObservations({
203
- ...baseResult,
204
- recommendations: [
205
- {
206
- id: "r1",
207
- title: "Reduce duplicate dependency versions",
208
- rationale: "dup",
209
- impact: "high",
210
- priorityScore: 95,
211
- },
212
- ],
213
- explain: {
214
- reasons: [
215
- {
216
- id: "e1",
217
- layer: "security",
218
- title: "graph.vulnerable",
219
- scoreImpact: 20,
220
- },
221
- {
222
- id: "e2",
223
- layer: "upgradeImpact",
224
- title: "Large upgrade blast radius",
225
- scoreImpact: 18,
226
- },
227
- ],
228
- },
229
- }, { mergePosture: "risky", hasFullResult: true });
230
- if (result.operationalObservations.length === 3) {
231
- expect(result.supportingLine).toBeNull();
232
- }
233
- });
234
- it("allows supporting line only with exactly one observation", () => {
235
- const result = deriveCardOperationalObservations({
236
- ...baseResult,
237
- recommendations: [
238
- {
239
- id: "r1",
240
- title: "Reduce duplicate dependency versions",
241
- rationale: "dup",
242
- impact: "high",
243
- priorityScore: 95,
244
- },
245
- ],
246
- explain: {
247
- reasons: [
248
- {
249
- id: "e1",
250
- layer: "security",
251
- title: "graph.vulnerable packages in tree",
252
- scoreImpact: 10,
253
- },
254
- ],
255
- },
256
- }, { mergePosture: "needs_review", hasFullResult: true });
257
- if (result.operationalObservations.length === 1) {
258
- expect(result.supportingLine).toBe("Vulnerable transitive packages detected");
259
- }
260
- });
261
- it("returns silence on denormalized path without signals", () => {
262
- const result = deriveCardOperationalObservations(null, {
263
- mergePosture: "safe",
264
- hasFullResult: false,
265
- });
266
- expect(result.operationalObservations).toEqual([]);
267
- });
268
- });
@@ -1,33 +0,0 @@
1
- import type { ScanResult, ScoreLayer } from "./types.js";
2
- export declare const SCAN_DETAIL_RISK_BAND_MODERATE_MIN = 40;
3
- export declare const SCAN_DETAIL_RISK_BAND_HIGH_MIN = 80;
4
- export type ScanDetailOverallRiskBand = "low" | "moderate" | "high";
5
- export type ScanDetailLayerConcernLevel = "low" | "medium" | "high";
6
- export type ScanDetailSignalLayer = {
7
- layer: ScoreLayer;
8
- label: string;
9
- score: number;
10
- concernLevel: ScanDetailLayerConcernLevel;
11
- concernLabel: string;
12
- };
13
- /** @deprecated Use ScanDetailSignalLayer */
14
- export type ScanDetailRiskLayer = ScanDetailSignalLayer;
15
- export type RiskScoreGaugeModel = {
16
- fillPercent: number;
17
- band: ScanDetailOverallRiskBand;
18
- ariaLabel: string;
19
- };
20
- export type ScanDetailSignalSummary = {
21
- score: number;
22
- overallBand: ScanDetailOverallRiskBand;
23
- overallLabel: string;
24
- gauge: RiskScoreGaugeModel;
25
- layers: ScanDetailSignalLayer[];
26
- };
27
- /** @deprecated Use ScanDetailSignalSummary */
28
- export type ScanDetailRiskSummary = ScanDetailSignalSummary;
29
- export declare function deriveOverallRiskBand(score: number | null | undefined): ScanDetailOverallRiskBand;
30
- export declare function overallSignalBandLabel(band: ScanDetailOverallRiskBand): string;
31
- export declare function deriveRiskScoreGauge(score: number, band: ScanDetailOverallRiskBand): RiskScoreGaugeModel;
32
- export declare function deriveSignalSummary(result: ScanResult): ScanDetailSignalSummary | null;
33
- //# sourceMappingURL=deriveRiskScoreSummary.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"deriveRiskScoreSummary.d.ts","sourceRoot":"","sources":["../src/deriveRiskScoreSummary.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,eAAO,MAAM,kCAAkC,KAAK,CAAC;AACrD,eAAO,MAAM,8BAA8B,KAAK,CAAC;AAgBjD,MAAM,MAAM,yBAAyB,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;AACpE,MAAM,MAAM,2BAA2B,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEpE,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,2BAA2B,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAExD,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,yBAAyB,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,yBAAyB,CAAC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,qBAAqB,EAAE,CAAC;CACjC,CAAC;AAEF,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAO5D,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,yBAAyB,CAM3B;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,yBAAyB,GAC9B,MAAM,CAKR;AAgBD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,yBAAyB,GAC9B,mBAAmB,CASrB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GACjB,uBAAuB,GAAG,IAAI,CAoChC"}