@mergesignal/shared 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/cardObservationCatalog.d.ts +55 -0
  2. package/dist/cardObservationCatalog.d.ts.map +1 -0
  3. package/dist/cardObservationCatalog.js +320 -0
  4. package/dist/cardSummaryCopy.d.ts +12 -0
  5. package/dist/cardSummaryCopy.d.ts.map +1 -0
  6. package/dist/cardSummaryCopy.js +36 -0
  7. package/dist/deriveCardDisplaySummary.d.ts +16 -0
  8. package/dist/deriveCardDisplaySummary.d.ts.map +1 -0
  9. package/dist/deriveCardDisplaySummary.js +56 -0
  10. package/dist/deriveCardDisplaySummary.test.d.ts +2 -0
  11. package/dist/deriveCardDisplaySummary.test.d.ts.map +1 -0
  12. package/dist/deriveCardDisplaySummary.test.js +89 -0
  13. package/dist/deriveCardOperationalObservations.d.ts +19 -0
  14. package/dist/deriveCardOperationalObservations.d.ts.map +1 -0
  15. package/dist/deriveCardOperationalObservations.js +180 -0
  16. package/dist/deriveCardOperationalObservations.test.d.ts +2 -0
  17. package/dist/deriveCardOperationalObservations.test.d.ts.map +1 -0
  18. package/dist/deriveCardOperationalObservations.test.js +268 -0
  19. package/dist/formatCardAreaLabels.d.ts +8 -0
  20. package/dist/formatCardAreaLabels.d.ts.map +1 -0
  21. package/dist/formatCardAreaLabels.js +65 -0
  22. package/dist/formatCardEvidenceCounts.d.ts +7 -0
  23. package/dist/formatCardEvidenceCounts.d.ts.map +1 -0
  24. package/dist/formatCardEvidenceCounts.js +19 -0
  25. package/dist/formatCardExposureDisplay.d.ts +20 -0
  26. package/dist/formatCardExposureDisplay.d.ts.map +1 -0
  27. package/dist/formatCardExposureDisplay.js +56 -0
  28. package/dist/formatCardExposureDisplay.test.d.ts +2 -0
  29. package/dist/formatCardExposureDisplay.test.d.ts.map +1 -0
  30. package/dist/formatCardExposureDisplay.test.js +36 -0
  31. package/dist/index.d.ts +9 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +9 -0
  34. package/dist/productMessaging.d.ts +85 -0
  35. package/dist/productMessaging.d.ts.map +1 -0
  36. package/dist/productMessaging.js +109 -0
  37. package/dist/productMessaging.test.d.ts +2 -0
  38. package/dist/productMessaging.test.d.ts.map +1 -0
  39. package/dist/productMessaging.test.js +97 -0
  40. package/dist/riskVocabulary.d.ts +8 -2
  41. package/dist/riskVocabulary.d.ts.map +1 -1
  42. package/dist/riskVocabulary.js +26 -9
  43. package/dist/scanCardSummary.d.ts +9 -0
  44. package/dist/scanCardSummary.d.ts.map +1 -1
  45. package/dist/scanCardSummary.js +19 -29
  46. package/dist/scanCardSummary.test.js +45 -8
  47. package/dist/truncateCardSummary.d.ts +5 -0
  48. package/dist/truncateCardSummary.d.ts.map +1 -0
  49. package/dist/truncateCardSummary.js +23 -0
  50. package/package.json +1 -1
@@ -0,0 +1,55 @@
1
+ import type { DependencyGraphInsights, ExplainReason, Finding, Recommendation, ScoreLayer } from "./types.js";
2
+ /**
3
+ * CATALOG_EXPANSION_CHECKLIST (G1):
4
+ * - Names a specific structural/topological concept (chains, paths, versions,
5
+ * blast radius, hotspots, surface area, transitive volume)?
6
+ * - Would NOT apply to an arbitrary PR without graph/dependency context?
7
+ * - ≤48 chars, non-sentence fragment, no imperatives, no digits?
8
+ * - Maps from a distinct signal family (not a catch-all)?
9
+ * If any answer is no → do not add.
10
+ */
11
+ export declare const CARD_OBSERVATION_MAX_LENGTH = 48;
12
+ export declare const CARD_OBSERVATION_CATALOG: readonly ["Large upgrade blast radius", "High transitive dependency volume", "Broad package surface area", "Duplicate dependency versions detected", "Vulnerable transitive packages detected", "Stale ecosystem dependencies detected", "Transitive dependency hotspots detected", "Deep indirect dependency chains", "Overlapping dependency paths detected"];
13
+ export type CatalogPhrase = (typeof CARD_OBSERVATION_CATALOG)[number];
14
+ /** Signal family keys — one catalog phrase per family on a card. */
15
+ export type ObservationSignalFamily = "blast_radius" | "transitive_volume" | "package_surface" | "duplicate_versions" | "vulnerable_transitive" | "stale_ecosystem" | "transitive_hotspots" | "indirect_chains" | "overlapping_paths";
16
+ export declare const CARD_OBSERVATION_SIGNAL_FAMILIES: Record<ObservationSignalFamily, CatalogPhrase>;
17
+ export declare function isCatalogPhrase(text: string): text is CatalogPhrase;
18
+ export declare function containsTelemetryOrDigits(text: string): boolean;
19
+ export declare function containsImperativeOrActionLanguage(text: string): boolean;
20
+ export declare function isGenericObservation(text: string): boolean;
21
+ export declare function isSentenceLike(text: string): boolean;
22
+ export declare function enforceMaxLength(text: string, max?: number): boolean;
23
+ export declare function phraseForFamily(family: ObservationSignalFamily): CatalogPhrase;
24
+ export declare function matchSignalFamily(text: string): ObservationSignalFamily | null;
25
+ export declare function mapTextToCatalogPhrase(text: string | null | undefined): {
26
+ phrase: CatalogPhrase;
27
+ family: ObservationSignalFamily;
28
+ } | null;
29
+ export declare function mapRecommendationToCatalogPhrase(rec: Recommendation): {
30
+ phrase: CatalogPhrase;
31
+ family: ObservationSignalFamily;
32
+ } | null;
33
+ export declare function mapExplainReasonToCatalogPhrase(reason: ExplainReason): {
34
+ phrase: CatalogPhrase;
35
+ family: ObservationSignalFamily;
36
+ } | null;
37
+ export declare function mapFindingToCatalogPhrase(finding: Finding): {
38
+ phrase: CatalogPhrase;
39
+ family: ObservationSignalFamily;
40
+ } | null;
41
+ export declare function mapContributionToCatalogPhrase(id: string): {
42
+ phrase: CatalogPhrase;
43
+ family: ObservationSignalFamily;
44
+ } | null;
45
+ export declare function mapGraphInsightsToCatalogPhrases(gi: DependencyGraphInsights | null | undefined): Array<{
46
+ phrase: CatalogPhrase;
47
+ family: ObservationSignalFamily;
48
+ }>;
49
+ export declare function mapLayerToCatalogPhrase(layer: ScoreLayer): {
50
+ phrase: CatalogPhrase;
51
+ family: ObservationSignalFamily;
52
+ } | null;
53
+ /** Validate catalog integrity at module load (tests also cover). */
54
+ export declare function validateCatalogIntegrity(): void;
55
+ //# sourceMappingURL=cardObservationCatalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardObservationCatalog.d.ts","sourceRoot":"","sources":["../src/cardObservationCatalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,uBAAuB,EACvB,aAAa,EACb,OAAO,EACP,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AAEH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,wBAAwB,iWAU3B,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,oEAAoE;AACpE,MAAM,MAAM,uBAAuB,GAC/B,cAAc,GACd,mBAAmB,GACnB,iBAAiB,GACjB,oBAAoB,GACpB,uBAAuB,GACvB,iBAAiB,GACjB,qBAAqB,GACrB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB,eAAO,MAAM,gCAAgC,EAAE,MAAM,CACnD,uBAAuB,EACvB,aAAa,CAWd,CAAC;AAoIF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAEnE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIxE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUpD;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,GAAG,SAA8B,GAChC,OAAO,CAET;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,uBAAuB,GAC9B,aAAa,CAEf;AAYD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GACX,uBAAuB,GAAG,IAAI,CAOhC;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAMnE;AAED,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,cAAc,GAClB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAGnE;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,aAAa,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAGnE;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,OAAO,GACf;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAuBnE;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,GACT;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAEnE;AAED,wBAAgB,gCAAgC,CAC9C,EAAE,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,GAC7C,KAAK,CAAC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAgCnE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,GAChB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAA;CAAE,GAAG,IAAI,CAUnE;AAED,oEAAoE;AACpE,wBAAgB,wBAAwB,IAAI,IAAI,CAe/C"}
@@ -0,0 +1,320 @@
1
+ import { humanizeEngineSurfaceText } from "./actionsStepSummary.js";
2
+ /**
3
+ * CATALOG_EXPANSION_CHECKLIST (G1):
4
+ * - Names a specific structural/topological concept (chains, paths, versions,
5
+ * blast radius, hotspots, surface area, transitive volume)?
6
+ * - Would NOT apply to an arbitrary PR without graph/dependency context?
7
+ * - ≤48 chars, non-sentence fragment, no imperatives, no digits?
8
+ * - Maps from a distinct signal family (not a catch-all)?
9
+ * If any answer is no → do not add.
10
+ */
11
+ export const CARD_OBSERVATION_MAX_LENGTH = 48;
12
+ export const CARD_OBSERVATION_CATALOG = [
13
+ "Large upgrade blast radius",
14
+ "High transitive dependency volume",
15
+ "Broad package surface area",
16
+ "Duplicate dependency versions detected",
17
+ "Vulnerable transitive packages detected",
18
+ "Stale ecosystem dependencies detected",
19
+ "Transitive dependency hotspots detected",
20
+ "Deep indirect dependency chains",
21
+ "Overlapping dependency paths detected",
22
+ ];
23
+ export const CARD_OBSERVATION_SIGNAL_FAMILIES = {
24
+ blast_radius: "Large upgrade blast radius",
25
+ transitive_volume: "High transitive dependency volume",
26
+ package_surface: "Broad package surface area",
27
+ duplicate_versions: "Duplicate dependency versions detected",
28
+ vulnerable_transitive: "Vulnerable transitive packages detected",
29
+ stale_ecosystem: "Stale ecosystem dependencies detected",
30
+ transitive_hotspots: "Transitive dependency hotspots detected",
31
+ indirect_chains: "Deep indirect dependency chains",
32
+ overlapping_paths: "Overlapping dependency paths detected",
33
+ };
34
+ const IMPERATIVE_VERBS = /^(reduce|upgrade|review|consider|mitigate|minimize|minimise|avoid|improve|optimize|optimise|ensure|verify|audit|check|address|fix|resolve)\b/i;
35
+ const GENERIC_OBSERVATION_PATTERNS = [
36
+ /\bruntime issue/i,
37
+ /\bdependency concern/i,
38
+ /\becosystem risk/i,
39
+ /\bmerge risk/i,
40
+ /\boperational review recommended/i,
41
+ /\boperational change detected/i,
42
+ /\bruntime-relevant dependency change/i,
43
+ /\blimited operational change/i,
44
+ /\bhigh-confidence merge risk/i,
45
+ /\bdependency change\b/i,
46
+ /\bpotential runtime impact/i,
47
+ /\bno merge blocker/i,
48
+ ];
49
+ const TELEMETRY_PATTERNS = [
50
+ /\d/,
51
+ /\bgraph\./i,
52
+ /\bscoreimpact\b/i,
53
+ /\bfan[\s_-]?in\b/i,
54
+ /\bmax depth\b/i,
55
+ /\bnode count\b/i,
56
+ /\bthreshold\b/i,
57
+ /\bmaint-stale\b/i,
58
+ /\becosystem\.package\s*surface\b/i,
59
+ ];
60
+ const SIGNAL_FAMILY_PATTERNS = [
61
+ {
62
+ family: "duplicate_versions",
63
+ patterns: [
64
+ /\bduplicat/i,
65
+ /\boverlapping dependency paths/i,
66
+ /\bsemver overlap/i,
67
+ /\bmultiple majors/i,
68
+ /\bgraph\.duplicates?\b/i,
69
+ ],
70
+ },
71
+ {
72
+ family: "vulnerable_transitive",
73
+ patterns: [
74
+ /\bvulnerab/i,
75
+ /\bosv\b/i,
76
+ /\badvisory\b/i,
77
+ /\bknown vulnerable packages/i,
78
+ /\bgraph\.vulnerable\b/i,
79
+ /\bsecurity_vulnerability\b/i,
80
+ ],
81
+ },
82
+ {
83
+ family: "stale_ecosystem",
84
+ patterns: [
85
+ /\bstale\b/i,
86
+ /\boutdated\b/i,
87
+ /\bdeprecated release/i,
88
+ /\bmaint-stale\b/i,
89
+ /\becosystem_registry\b/i,
90
+ /\bstale releases\b/i,
91
+ ],
92
+ },
93
+ {
94
+ family: "transitive_hotspots",
95
+ patterns: [
96
+ /\bhotspot/i,
97
+ /\bgraph\.hotspot\b/i,
98
+ /\bmany import paths converge/i,
99
+ /\bshared packages see many inbound/i,
100
+ ],
101
+ },
102
+ {
103
+ family: "indirect_chains",
104
+ patterns: [
105
+ /\bindirect dependency depth\b/i,
106
+ /\bdependency chain depth\b/i,
107
+ /\bgraph\.depth\b/i,
108
+ /\bgraph\.hidden\b/i,
109
+ /\bhidden transitive paths\b/i,
110
+ /\bdeepest resolved chain\b/i,
111
+ /\blong indirect/i,
112
+ ],
113
+ },
114
+ {
115
+ family: "transitive_volume",
116
+ patterns: [
117
+ /\btransitive dependency volume\b/i,
118
+ /\bgraph\.transitive\b/i,
119
+ /\btransitive volume\b/i,
120
+ /\btransitive churn\b/i,
121
+ /\bindirect dependencies\b/i,
122
+ ],
123
+ },
124
+ {
125
+ family: "package_surface",
126
+ patterns: [
127
+ /\bpackage surface\b/i,
128
+ /\bpackage footprint\b/i,
129
+ /\bbroad declared package\b/i,
130
+ /\becosystem\.package\s*surface\b/i,
131
+ /\bdeclared package footprint\b/i,
132
+ /\btransitive surface area\b/i,
133
+ /\bdependency surface area\b/i,
134
+ /\btransitive dependency surface\b/i,
135
+ ],
136
+ },
137
+ {
138
+ family: "blast_radius",
139
+ patterns: [
140
+ /\bblast radius\b/i,
141
+ /\bupgrade impact\b/i,
142
+ /\bbreaking change\b/i,
143
+ /\bmajor version\b/i,
144
+ /\bupgradeImpact\b/i,
145
+ /\blarge upgrade\b/i,
146
+ ],
147
+ },
148
+ {
149
+ family: "overlapping_paths",
150
+ patterns: [
151
+ /\boverlapping dependency paths to the same packages\b/i,
152
+ /\bmultiple paths to the same package\b/i,
153
+ ],
154
+ },
155
+ ];
156
+ export function isCatalogPhrase(text) {
157
+ return CARD_OBSERVATION_CATALOG.includes(text);
158
+ }
159
+ export function containsTelemetryOrDigits(text) {
160
+ const t = text.trim();
161
+ if (!t)
162
+ return true;
163
+ return TELEMETRY_PATTERNS.some((re) => re.test(t));
164
+ }
165
+ export function containsImperativeOrActionLanguage(text) {
166
+ const t = text.trim();
167
+ if (!t)
168
+ return false;
169
+ return IMPERATIVE_VERBS.test(t);
170
+ }
171
+ export function isGenericObservation(text) {
172
+ const t = text.trim();
173
+ if (!t)
174
+ return true;
175
+ return GENERIC_OBSERVATION_PATTERNS.some((re) => re.test(t));
176
+ }
177
+ export function isSentenceLike(text) {
178
+ const t = text.trim();
179
+ if (!t)
180
+ return false;
181
+ if (/\.\s+\S/.test(t))
182
+ return true;
183
+ if (/\s—\s/.test(t))
184
+ return true;
185
+ if (/;\s/.test(t))
186
+ return true;
187
+ if (/\bbecause\b/i.test(t))
188
+ return true;
189
+ if (/\bwhich means\b/i.test(t))
190
+ return true;
191
+ if (/\bso that\b/i.test(t))
192
+ return true;
193
+ return false;
194
+ }
195
+ export function enforceMaxLength(text, max = CARD_OBSERVATION_MAX_LENGTH) {
196
+ return text.trim().length <= max;
197
+ }
198
+ export function phraseForFamily(family) {
199
+ return CARD_OBSERVATION_SIGNAL_FAMILIES[family];
200
+ }
201
+ function normalizeMatchText(...parts) {
202
+ return parts
203
+ .filter((p) => typeof p === "string" && p.trim().length > 0)
204
+ .map((p) => humanizeEngineSurfaceText(p))
205
+ .join(" ")
206
+ .replace(/\s+/g, " ")
207
+ .trim()
208
+ .toLowerCase();
209
+ }
210
+ export function matchSignalFamily(text) {
211
+ const normalized = normalizeMatchText(text);
212
+ if (!normalized)
213
+ return null;
214
+ for (const { family, patterns } of SIGNAL_FAMILY_PATTERNS) {
215
+ if (patterns.some((re) => re.test(normalized)))
216
+ return family;
217
+ }
218
+ return null;
219
+ }
220
+ export function mapTextToCatalogPhrase(text) {
221
+ if (!text?.trim())
222
+ return null;
223
+ const family = matchSignalFamily(text);
224
+ if (!family)
225
+ return null;
226
+ const phrase = phraseForFamily(family);
227
+ return { phrase, family };
228
+ }
229
+ export function mapRecommendationToCatalogPhrase(rec) {
230
+ const combined = `${rec.title} ${rec.rationale}`;
231
+ return mapTextToCatalogPhrase(combined);
232
+ }
233
+ export function mapExplainReasonToCatalogPhrase(reason) {
234
+ const combined = `${reason.id} ${reason.title} ${reason.layer}`;
235
+ return mapTextToCatalogPhrase(combined);
236
+ }
237
+ export function mapFindingToCatalogPhrase(finding) {
238
+ const categoryMap = {
239
+ security_vulnerability: "vulnerable_transitive",
240
+ ecosystem_registry: "stale_ecosystem",
241
+ breaking_change: "blast_radius",
242
+ major_version: "blast_radius",
243
+ deprecation: "stale_ecosystem",
244
+ };
245
+ const combined = `${finding.title} ${finding.description} ${finding.category ?? ""}`;
246
+ const fromText = mapTextToCatalogPhrase(combined);
247
+ if (fromText)
248
+ return fromText;
249
+ const fromCategory = finding.category
250
+ ? categoryMap[finding.category]
251
+ : undefined;
252
+ if (fromCategory) {
253
+ return { phrase: phraseForFamily(fromCategory), family: fromCategory };
254
+ }
255
+ return null;
256
+ }
257
+ export function mapContributionToCatalogPhrase(id) {
258
+ return mapTextToCatalogPhrase(id);
259
+ }
260
+ export function mapGraphInsightsToCatalogPhrases(gi) {
261
+ if (!gi || typeof gi !== "object")
262
+ return [];
263
+ const out = [];
264
+ const seen = new Set();
265
+ function add(family) {
266
+ if (seen.has(family))
267
+ return;
268
+ seen.add(family);
269
+ out.push({ phrase: phraseForFamily(family), family });
270
+ }
271
+ const vulnCount = Array.isArray(gi.vulnerable) ? gi.vulnerable.length : 0;
272
+ const hotspotCount = Array.isArray(gi.hotspots) ? gi.hotspots.length : 0;
273
+ const depth = typeof gi.maxDepth === "number" && Number.isFinite(gi.maxDepth)
274
+ ? gi.maxDepth
275
+ : null;
276
+ const nodes = typeof gi.nodes === "number" && Number.isFinite(gi.nodes) ? gi.nodes : 0;
277
+ if (vulnCount > 0)
278
+ add("vulnerable_transitive");
279
+ if (hotspotCount >= 4)
280
+ add("transitive_hotspots");
281
+ if (depth !== null && depth >= 8)
282
+ add("indirect_chains");
283
+ else if (depth !== null && depth >= 5)
284
+ add("indirect_chains");
285
+ if (nodes >= 1200)
286
+ add("transitive_volume");
287
+ else if (nodes >= 600 && out.length < 2)
288
+ add("transitive_volume");
289
+ return out;
290
+ }
291
+ export function mapLayerToCatalogPhrase(layer) {
292
+ const layerMap = {
293
+ upgradeImpact: "blast_radius",
294
+ ecosystem: "package_surface",
295
+ security: "vulnerable_transitive",
296
+ maintainability: "stale_ecosystem",
297
+ };
298
+ const family = layerMap[layer];
299
+ if (!family)
300
+ return null;
301
+ return { phrase: phraseForFamily(family), family };
302
+ }
303
+ /** Validate catalog integrity at module load (tests also cover). */
304
+ export function validateCatalogIntegrity() {
305
+ for (const phrase of CARD_OBSERVATION_CATALOG) {
306
+ if (!enforceMaxLength(phrase)) {
307
+ throw new Error(`Catalog phrase exceeds max length: ${phrase}`);
308
+ }
309
+ if (containsTelemetryOrDigits(phrase)) {
310
+ throw new Error(`Catalog phrase contains telemetry/digits: ${phrase}`);
311
+ }
312
+ if (containsImperativeOrActionLanguage(phrase)) {
313
+ throw new Error(`Catalog phrase contains imperative: ${phrase}`);
314
+ }
315
+ if (isSentenceLike(phrase)) {
316
+ throw new Error(`Catalog phrase is sentence-like: ${phrase}`);
317
+ }
318
+ }
319
+ }
320
+ validateCatalogIntegrity();
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Dashboard PR card copy — small fixed denylist (not an open-ended taxonomy).
3
+ * Detail pages and check runs may show fuller engine text.
4
+ */
5
+ /** Reasoning/summary lines too generic for card display. */
6
+ export declare const GENERIC_CARD_SUMMARY_PHRASES: readonly string[];
7
+ /** Area labels that repeat across many PRs — prefer omit over show. */
8
+ export declare const GENERIC_CARD_AREA_PHRASES: readonly string[];
9
+ export declare const SAFE_LOW_FINDINGS_SUMMARY = "Minor findings only";
10
+ /** @deprecated Card UI omits this; kept for denormalized comparison only. */
11
+ export declare const LEGACY_SAFE_SUMMARY = "No merge blockers detected";
12
+ //# sourceMappingURL=cardSummaryCopy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardSummaryCopy.d.ts","sourceRoot":"","sources":["../src/cardSummaryCopy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAezD,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAUtD,CAAC;AAEF,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,+BAA+B,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Dashboard PR card copy — small fixed denylist (not an open-ended taxonomy).
3
+ * Detail pages and check runs may show fuller engine text.
4
+ */
5
+ /** Reasoning/summary lines too generic for card display. */
6
+ export const GENERIC_CARD_SUMMARY_PHRASES = [
7
+ "dependency change",
8
+ "high-confidence merge risk",
9
+ "high-confidence merge risks",
10
+ "no high-confidence merge risk",
11
+ "no high-confidence merge risks",
12
+ "from this pr dependency change",
13
+ "pr dependency change",
14
+ "potential runtime impact detected",
15
+ "potential runtime impact",
16
+ "merge risk from this pr",
17
+ "no merge blockers detected",
18
+ "analysis could not be completed",
19
+ "scan data unavailable",
20
+ "waiting for results",
21
+ ];
22
+ /** Area labels that repeat across many PRs — prefer omit over show. */
23
+ export const GENERIC_CARD_AREA_PHRASES = [
24
+ "auth flows",
25
+ "api handlers",
26
+ "api routes",
27
+ "critical paths",
28
+ "core services",
29
+ "security",
30
+ "maintainability",
31
+ "ecosystem",
32
+ "upgrade impact",
33
+ ];
34
+ export const SAFE_LOW_FINDINGS_SUMMARY = "Minor findings only";
35
+ /** @deprecated Card UI omits this; kept for denormalized comparison only. */
36
+ export const LEGACY_SAFE_SUMMARY = "No merge blockers detected";
@@ -0,0 +1,16 @@
1
+ import type { FindingCountSummary } from "./scanCardSummary.js";
2
+ import type { MergePosture } from "./riskVocabulary.js";
3
+ /** True when summary text is too generic for dashboard cards. */
4
+ export declare function isGenericCardSummary(text: string | null | undefined): boolean;
5
+ export type DeriveCardDisplaySummaryInput = {
6
+ mergePosture: MergePosture | null;
7
+ rawSummary: string | null | undefined;
8
+ findingCounts: FindingCountSummary | null;
9
+ topAffectedAreas: string[];
10
+ };
11
+ /**
12
+ * Dashboard card summary line — operational, concise, posture-aware.
13
+ * Returns null for quiet safe cards (posture-only display).
14
+ */
15
+ export declare function deriveCardDisplaySummary(input: DeriveCardDisplaySummaryInput): string | null;
16
+ //# sourceMappingURL=deriveCardDisplaySummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveCardDisplaySummary.d.ts","sourceRoot":"","sources":["../src/deriveCardDisplaySummary.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAexD,iEAAiE;AACjE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAO7E;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,6BAA6B,GACnC,MAAM,GAAG,IAAI,CA+Bf"}
@@ -0,0 +1,56 @@
1
+ import { GENERIC_CARD_SUMMARY_PHRASES, LEGACY_SAFE_SUMMARY, SAFE_LOW_FINDINGS_SUMMARY, } from "./cardSummaryCopy.js";
2
+ import { joinCardAreaLabels } from "./formatCardAreaLabels.js";
3
+ import { truncateCardSummary } from "./truncateCardSummary.js";
4
+ function hasActionableFindings(counts) {
5
+ return counts.critical + counts.high + counts.medium + counts.low > 0;
6
+ }
7
+ function hasOnlyLowFindings(counts) {
8
+ return (counts.low > 0 &&
9
+ counts.critical === 0 &&
10
+ counts.high === 0 &&
11
+ counts.medium === 0);
12
+ }
13
+ /** True when summary text is too generic for dashboard cards. */
14
+ export function isGenericCardSummary(text) {
15
+ if (!text?.trim())
16
+ return true;
17
+ const normalized = text.trim().toLowerCase();
18
+ if (normalized.length < 12)
19
+ return true;
20
+ return GENERIC_CARD_SUMMARY_PHRASES.some((phrase) => normalized.includes(phrase));
21
+ }
22
+ /**
23
+ * Dashboard card summary line — operational, concise, posture-aware.
24
+ * Returns null for quiet safe cards (posture-only display).
25
+ */
26
+ export function deriveCardDisplaySummary(input) {
27
+ const { mergePosture, rawSummary, findingCounts, topAffectedAreas } = input;
28
+ if (!mergePosture)
29
+ return rawSummary?.trim() || null;
30
+ const counts = findingCounts ?? {
31
+ critical: 0,
32
+ high: 0,
33
+ medium: 0,
34
+ low: 0,
35
+ };
36
+ if (mergePosture === "safe") {
37
+ if (!hasActionableFindings(counts)) {
38
+ const areasLine = joinCardAreaLabels(topAffectedAreas);
39
+ if (areasLine)
40
+ return truncateCardSummary(areasLine, 90);
41
+ return null;
42
+ }
43
+ if (hasOnlyLowFindings(counts))
44
+ return SAFE_LOW_FINDINGS_SUMMARY;
45
+ }
46
+ const raw = rawSummary?.trim() ?? null;
47
+ if (raw && raw !== LEGACY_SAFE_SUMMARY && !isGenericCardSummary(raw)) {
48
+ return truncateCardSummary(raw);
49
+ }
50
+ const areasLine = joinCardAreaLabels(topAffectedAreas);
51
+ if (areasLine)
52
+ return truncateCardSummary(areasLine, 90);
53
+ if (mergePosture === "safe")
54
+ return null;
55
+ return null;
56
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deriveCardDisplaySummary.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveCardDisplaySummary.test.d.ts","sourceRoot":"","sources":["../src/deriveCardDisplaySummary.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,89 @@
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
+ });
@@ -0,0 +1,19 @@
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
@@ -0,0 +1 @@
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"}