@mergesignal/shared 0.2.5 → 0.2.7

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 (41) hide show
  1. package/dist/deriveScanNarrative.d.ts +10 -0
  2. package/dist/deriveScanNarrative.d.ts.map +1 -0
  3. package/dist/deriveScanNarrative.js +267 -0
  4. package/dist/deriveScanNarrative.test.d.ts +2 -0
  5. package/dist/deriveScanNarrative.test.d.ts.map +1 -0
  6. package/dist/deriveScanNarrative.test.js +109 -0
  7. package/dist/extractRepositoryContextFacts.d.ts +16 -0
  8. package/dist/extractRepositoryContextFacts.d.ts.map +1 -0
  9. package/dist/extractRepositoryContextFacts.js +71 -0
  10. package/dist/fixtures/repoIntelligenceFixtures.d.ts +8 -0
  11. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -0
  12. package/dist/fixtures/repoIntelligenceFixtures.js +47 -0
  13. package/dist/index.d.ts +5 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -0
  16. package/dist/prCheckRunPresentation.js +1 -1
  17. package/dist/presentScanCardSummary.d.ts +16 -0
  18. package/dist/presentScanCardSummary.d.ts.map +1 -0
  19. package/dist/presentScanCardSummary.js +207 -0
  20. package/dist/productMessaging.d.ts +1 -1
  21. package/dist/productMessaging.js +1 -1
  22. package/dist/repoIntelligenceSchema.d.ts +123 -0
  23. package/dist/repoIntelligenceSchema.d.ts.map +1 -0
  24. package/dist/repoIntelligenceSchema.js +174 -0
  25. package/dist/scanCardSummary.d.ts +12 -2
  26. package/dist/scanCardSummary.d.ts.map +1 -1
  27. package/dist/scanCardSummary.js +16 -57
  28. package/dist/scanCardSummary.test.js +13 -0
  29. package/dist/scanDetailViewModel.d.ts +14 -0
  30. package/dist/scanDetailViewModel.d.ts.map +1 -1
  31. package/dist/scanDetailViewModel.js +119 -30
  32. package/dist/scanDetailViewModel.test.js +2 -2
  33. package/dist/scanNarrativeFacts.d.ts +79 -0
  34. package/dist/scanNarrativeFacts.d.ts.map +1 -0
  35. package/dist/scanNarrativeFacts.js +18 -0
  36. package/dist/scanSurfaceCopy.d.ts +26 -0
  37. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  38. package/dist/scanSurfaceCopy.js +26 -0
  39. package/dist/types.d.ts +20 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind } from "./repoIntelligenceSchema.js";
2
+ import { type ScanNarrativeFacts } from "./scanNarrativeFacts.js";
3
+ import type { ScanResult } from "./types.js";
4
+ /**
5
+ * Consumer-agnostic scan intelligence — structured facts only (no UI copy).
6
+ */
7
+ export declare function deriveScanNarrative(result: ScanResult | null | undefined): ScanNarrativeFacts;
8
+ /** @internal Test helper — normalize loose engine strings. */
9
+ export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
10
+ //# sourceMappingURL=deriveScanNarrative.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAM5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAQL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAIV,UAAU,EACX,MAAM,YAAY,CAAC;AAqPpB;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACpC,kBAAkB,CA8GpB;AAED,8DAA8D;AAC9D,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,CAAC"}
@@ -0,0 +1,267 @@
1
+ import { extractRepositoryContextFacts } from "./extractRepositoryContextFacts.js";
2
+ import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind, parseRepoIntelligence, readBlastLevel, readReachabilityFromLoose, readSurfaceFromLoose, } from "./repoIntelligenceSchema.js";
3
+ import { EMPTY_SCAN_NARRATIVE_FACTS, } from "./scanNarrativeFacts.js";
4
+ import { mergePostureFromDecision } from "./riskVocabulary.js";
5
+ import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
6
+ function parseChangedPackages(changed) {
7
+ const all = Array.isArray(changed)
8
+ ? changed.map((p) => p.trim()).filter(Boolean)
9
+ : [];
10
+ return {
11
+ primary: all[0] ?? null,
12
+ others: all.slice(1),
13
+ all,
14
+ };
15
+ }
16
+ function blastLevelFromChangedCount(count) {
17
+ if (count >= 8)
18
+ return "wide";
19
+ if (count >= 3)
20
+ return "moderate";
21
+ return "narrow";
22
+ }
23
+ function extractTier1FromRepoIntelligence(ri, changed) {
24
+ const primary = changed.primary;
25
+ const pkgIntel = primary && ri.packages?.[primary] ? ri.packages[primary] : undefined;
26
+ const runtimeRaw = pkgIntel?.runtimeSurface ?? ri.runtimeSurface ?? undefined;
27
+ const reachRaw = pkgIntel?.reachability ?? ri.reachability ?? undefined;
28
+ const runtimeKind = readSurfaceFromLoose(runtimeRaw) ?? (primary ? "unknown" : null);
29
+ const reachKind = readReachabilityFromLoose(reachRaw);
30
+ const usageEntry = pkgIntel?.usage ??
31
+ pkgIntel?.packageUsage ??
32
+ ri.packageUsage?.find((u) => (u.packageName ?? u.package) === primary ||
33
+ changed.all.includes(u.packageName ?? u.package ?? ""));
34
+ const paths = [
35
+ ...(usageEntry?.paths ?? []),
36
+ ...(usageEntry?.criticalPaths ?? []),
37
+ ...(usageEntry?.files ?? []),
38
+ ].filter(Boolean);
39
+ const runtimeSurface = runtimeKind != null
40
+ ? {
41
+ kind: runtimeKind,
42
+ evidence: {
43
+ packages: primary ? [primary] : changed.all.slice(0, 3),
44
+ paths: paths.length > 0 ? paths.slice(0, 8) : undefined,
45
+ },
46
+ }
47
+ : null;
48
+ const reachability = reachKind
49
+ ? {
50
+ kind: reachKind,
51
+ evidence: {
52
+ paths: paths.length > 0 ? paths.slice(0, 8) : undefined,
53
+ frameworks: ri.frameworks?.length ? ri.frameworks : undefined,
54
+ },
55
+ }
56
+ : null;
57
+ const blastFromRi = ri.blastRadius;
58
+ const blastLevel = readBlastLevel(blastFromRi?.level) ??
59
+ (changed.all.length > 0
60
+ ? blastLevelFromChangedCount(changed.all.length)
61
+ : null);
62
+ const blastRadius = blastLevel
63
+ ? {
64
+ level: blastLevel,
65
+ changedPackageCount: blastFromRi?.changedPackageCount ?? changed.all.length,
66
+ factors: blastFromRi?.factors,
67
+ }
68
+ : null;
69
+ const areasRaw = ri.applicationAreas ?? ri.affectedAreas ?? [];
70
+ const usageAreas = usageEntry?.areas ?? pkgIntel?.areas ?? [];
71
+ const affectedAreas = [];
72
+ for (const area of areasRaw) {
73
+ affectedAreas.push({ id: area.id, label: area.label });
74
+ }
75
+ for (const label of usageAreas) {
76
+ const id = label.toLowerCase().replace(/\s+/g, "_");
77
+ if (!affectedAreas.some((a) => a.id === id)) {
78
+ affectedAreas.push({ id, label });
79
+ }
80
+ }
81
+ const hotspots = [];
82
+ const riHotspots = ri.hotspots ?? pkgIntel?.hotspots ?? [];
83
+ for (const h of riHotspots) {
84
+ const name = h.packageName ?? primary;
85
+ if (!name)
86
+ continue;
87
+ hotspots.push({
88
+ packageName: name,
89
+ source: h.source === "graph" ? "graph" : "code",
90
+ depth: h.depth,
91
+ paths: h.paths,
92
+ });
93
+ }
94
+ return {
95
+ runtimeSurface,
96
+ reachability,
97
+ blastRadius,
98
+ affectedAreas: affectedAreas.slice(0, 6),
99
+ hotspots: hotspots.slice(0, 8),
100
+ };
101
+ }
102
+ function hasMeaningfulTier1(tier1) {
103
+ return (tier1.runtimeSurface != null ||
104
+ tier1.reachability != null ||
105
+ tier1.blastRadius != null ||
106
+ tier1.affectedAreas.length > 0 ||
107
+ tier1.hotspots.length > 0);
108
+ }
109
+ function insightScope(scope) {
110
+ if (scope === "changed" || scope === "all")
111
+ return scope;
112
+ return "unknown";
113
+ }
114
+ function guidanceFromInsight(insight, index) {
115
+ return {
116
+ kind: "insight",
117
+ id: `insight:${index}:${insight.type}`,
118
+ scope: insightScope(insight.scope),
119
+ priority: insight.priority,
120
+ message: insight.message,
121
+ context: insight.context,
122
+ remediation: insight.remediation,
123
+ affectedFiles: insight.affectedFiles,
124
+ };
125
+ }
126
+ function guidanceFromFinding(finding) {
127
+ return {
128
+ kind: "finding",
129
+ id: finding.id,
130
+ scope: "unknown",
131
+ priority: finding.severity,
132
+ message: finding.title,
133
+ context: finding.description,
134
+ remediation: finding.recommendation,
135
+ };
136
+ }
137
+ function guidanceFromRecommendation(rec) {
138
+ return {
139
+ kind: "recommendation",
140
+ id: rec.id,
141
+ scope: "unknown",
142
+ priority: String(rec.priorityScore ?? rec.rank ?? "medium"),
143
+ message: rec.title,
144
+ context: rec.rationale,
145
+ remediation: rec.impact,
146
+ };
147
+ }
148
+ function collectReviewerGuidance(result) {
149
+ const guidance = [];
150
+ const insights = Array.isArray(result.insights) ? result.insights : [];
151
+ const changedFirst = [
152
+ ...insights.filter((i) => i.scope === "changed"),
153
+ ...insights.filter((i) => i.scope !== "changed"),
154
+ ];
155
+ changedFirst.forEach((insight, index) => {
156
+ guidance.push(guidanceFromInsight(insight, index));
157
+ });
158
+ const findings = Array.isArray(result.findings) ? result.findings : [];
159
+ const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
160
+ const sortedFindings = [...findings].sort((a, b) => (severityOrder[b.severity] ?? 0) - (severityOrder[a.severity] ?? 0));
161
+ for (const finding of sortedFindings) {
162
+ guidance.push(guidanceFromFinding(finding));
163
+ }
164
+ const recs = Array.isArray(result.recommendations)
165
+ ? result.recommendations
166
+ : [];
167
+ for (const rec of recs) {
168
+ guidance.push(guidanceFromRecommendation(rec));
169
+ }
170
+ return guidance;
171
+ }
172
+ function resolveAvailabilityMode(tier1, tier2, codeIntelligenceAvailable) {
173
+ if (tier1 && codeIntelligenceAvailable)
174
+ return "pr_intelligence";
175
+ if (tier2)
176
+ return "insights";
177
+ return "graph_fallback";
178
+ }
179
+ /**
180
+ * Consumer-agnostic scan intelligence — structured facts only (no UI copy).
181
+ */
182
+ export function deriveScanNarrative(result) {
183
+ if (!result) {
184
+ return { ...EMPTY_SCAN_NARRATIVE_FACTS };
185
+ }
186
+ const changedPackages = parseChangedPackages(result.changedPackages);
187
+ const mergePosture = mergePostureFromDecision(result.decision?.recommendation) ?? null;
188
+ const riskIndex = typeof result.totalScore === "number" && Number.isFinite(result.totalScore)
189
+ ? result.totalScore
190
+ : null;
191
+ const codeIntelligenceAvailable = result.analysisPreparation?.codeIntelligenceAvailable !== false;
192
+ const parsedRi = parseRepoIntelligence(result.repoIntelligence);
193
+ const corpusGate = codeIntelligenceAvailable && parsedRi != null;
194
+ let tier1Blocks = {
195
+ runtimeSurface: null,
196
+ reachability: null,
197
+ blastRadius: null,
198
+ affectedAreas: [],
199
+ hotspots: [],
200
+ };
201
+ if (corpusGate && parsedRi) {
202
+ tier1Blocks = extractTier1FromRepoIntelligence(parsedRi, changedPackages);
203
+ }
204
+ else if (changedPackages.all.length > 0) {
205
+ tier1Blocks = {
206
+ runtimeSurface: null,
207
+ reachability: null,
208
+ blastRadius: {
209
+ level: blastLevelFromChangedCount(changedPackages.all.length),
210
+ changedPackageCount: changedPackages.all.length,
211
+ },
212
+ affectedAreas: [],
213
+ hotspots: [],
214
+ };
215
+ }
216
+ const tier1 = corpusGate && hasMeaningfulTier1(tier1Blocks);
217
+ const reviewerGuidance = collectReviewerGuidance(result);
218
+ const tier2 = reviewerGuidance.some((g) => g.scope === "changed" || g.kind === "insight");
219
+ const repositoryContext = extractRepositoryContextFacts(result);
220
+ const tier3 = repositoryContext.length > 0;
221
+ const mode = resolveAvailabilityMode(tier1, tier2 || reviewerGuidance.length > 0, codeIntelligenceAvailable);
222
+ const decisionConf = result.decision?.confidence;
223
+ const confidence = {
224
+ decision: decisionConf === "low" ||
225
+ decisionConf === "medium" ||
226
+ decisionConf === "high"
227
+ ? decisionConf
228
+ : null,
229
+ };
230
+ if (!tier1 && mode === "graph_fallback" && changedPackages.all.length > 0) {
231
+ tier1Blocks.blastRadius = {
232
+ level: blastLevelFromChangedCount(changedPackages.all.length),
233
+ changedPackageCount: changedPackages.all.length,
234
+ };
235
+ }
236
+ let affectedAreas = tier1Blocks.affectedAreas;
237
+ if (affectedAreas.length === 0) {
238
+ affectedAreas = selectTopAffectedAreas(result, { max: 2 }).map((label) => ({
239
+ id: label.toLowerCase().replace(/\s+/g, "_").slice(0, 48),
240
+ label,
241
+ }));
242
+ }
243
+ return {
244
+ availability: {
245
+ mode,
246
+ codeIntelligenceAvailable,
247
+ tiersPresent: {
248
+ tier1,
249
+ tier2: reviewerGuidance.length > 0,
250
+ tier3,
251
+ },
252
+ },
253
+ changedPackages,
254
+ runtimeSurface: tier1Blocks.runtimeSurface,
255
+ reachability: tier1Blocks.reachability,
256
+ blastRadius: tier1Blocks.blastRadius,
257
+ affectedAreas,
258
+ hotspots: tier1Blocks.hotspots,
259
+ reviewerGuidance,
260
+ confidence,
261
+ repositoryContext,
262
+ mergePosture,
263
+ riskIndex,
264
+ };
265
+ }
266
+ /** @internal Test helper — normalize loose engine strings. */
267
+ export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deriveScanNarrative.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveScanNarrative.test.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
3
+ import { fixtureRepoIntelligenceEmpty, fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.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("deriveScanNarrative", () => {
16
+ it("returns denormalized empty facts for null result", () => {
17
+ const facts = deriveScanNarrative(null);
18
+ expect(facts.availability.mode).toBe("denormalized");
19
+ expect(facts.changedPackages.all).toEqual([]);
20
+ });
21
+ it("populates tier1 from repoIntelligence when corpus available", () => {
22
+ const result = {
23
+ ...baseResult,
24
+ changedPackages: ["fastify"],
25
+ analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
26
+ repoIntelligence: fixtureRepoIntelligenceFastify,
27
+ };
28
+ const facts = deriveScanNarrative(result);
29
+ expect(facts.availability.mode).toBe("pr_intelligence");
30
+ expect(facts.availability.codeIntelligenceAvailable).toBe(true);
31
+ expect(facts.changedPackages.primary).toBe("fastify");
32
+ expect(facts.runtimeSurface?.kind).toBe("runtime");
33
+ expect(facts.reachability?.kind).toBe("on_runtime_paths");
34
+ expect(facts.blastRadius?.level).toBe("moderate");
35
+ expect(facts.affectedAreas.map((a) => a.id)).toContain("api");
36
+ expect(facts.hotspots[0]?.source).toBe("code");
37
+ });
38
+ it("prefers changed-scope insights for reviewer guidance ordering", () => {
39
+ const result = {
40
+ ...baseResult,
41
+ changedPackages: ["lodash"],
42
+ insights: [
43
+ {
44
+ type: "dependency_risk",
45
+ priority: "medium",
46
+ confidence: "likely",
47
+ scope: "all",
48
+ message: "Repository-wide duplicate paths",
49
+ context: "c",
50
+ remediation: "r",
51
+ },
52
+ {
53
+ type: "usage_risk",
54
+ priority: "high",
55
+ confidence: "confirmed",
56
+ scope: "changed",
57
+ message: "lodash used in billing export",
58
+ context: "c2",
59
+ remediation: "r2",
60
+ },
61
+ ],
62
+ };
63
+ const facts = deriveScanNarrative(result);
64
+ expect(facts.reviewerGuidance[0]?.scope).toBe("changed");
65
+ expect(facts.reviewerGuidance[0]?.message).toContain("billing");
66
+ });
67
+ it("uses graph_fallback with structured repository context", () => {
68
+ const result = {
69
+ ...baseResult,
70
+ explain: {
71
+ reasons: [
72
+ {
73
+ id: "1",
74
+ layer: "ecosystem",
75
+ title: "graph.transitive volume",
76
+ scoreImpact: 18,
77
+ },
78
+ ],
79
+ },
80
+ };
81
+ const facts = deriveScanNarrative(result);
82
+ expect(facts.availability.mode).toBe("graph_fallback");
83
+ expect(facts.repositoryContext.length).toBeGreaterThan(0);
84
+ expect(facts.repositoryContext[0]?.family).toBe("transitive_volume");
85
+ });
86
+ it("populates build-only runtime surface from repoIntelligence", () => {
87
+ const result = {
88
+ ...baseResult,
89
+ changedPackages: ["typescript"],
90
+ analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
91
+ repoIntelligence: fixtureRepoIntelligenceTypescript,
92
+ };
93
+ const facts = deriveScanNarrative(result);
94
+ expect(facts.availability.mode).toBe("pr_intelligence");
95
+ expect(facts.runtimeSurface?.kind).toBe("build");
96
+ expect(facts.reachability?.kind).toBe("build_only");
97
+ expect(facts.blastRadius?.level).toBe("narrow");
98
+ });
99
+ it("treats empty packages-only repoIntelligence as absent", () => {
100
+ const result = {
101
+ ...baseResult,
102
+ changedPackages: ["react"],
103
+ analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
104
+ repoIntelligence: fixtureRepoIntelligenceEmpty,
105
+ };
106
+ const facts = deriveScanNarrative(result);
107
+ expect(facts.availability.tiersPresent.tier1).toBe(false);
108
+ });
109
+ });
@@ -0,0 +1,16 @@
1
+ import { type ObservationSignalFamily } from "./cardObservationCatalog.js";
2
+ import type { RepositoryContextSource } from "./scanNarrativeFacts.js";
3
+ import type { ScanResult } from "./types.js";
4
+ export type RepositoryContextFact = {
5
+ family: ObservationSignalFamily;
6
+ source: RepositoryContextSource;
7
+ refs?: {
8
+ packageNames?: string[];
9
+ metric?: Record<string, number>;
10
+ };
11
+ };
12
+ /**
13
+ * Tier 3 structured signal refs for the intelligence layer (no catalog phrases).
14
+ */
15
+ export declare function extractRepositoryContextFacts(result: ScanResult): RepositoryContextFact[];
16
+ //# sourceMappingURL=extractRepositoryContextFacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractRepositoryContextFacts.d.ts","sourceRoot":"","sources":["../src/extractRepositoryContextFacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,uBAAuB,CAAC;IAChC,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACrE,CAAC;AAYF;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,UAAU,GACjB,qBAAqB,EAAE,CAiEzB"}
@@ -0,0 +1,71 @@
1
+ import { mapExplainReasonToCatalogPhrase, mapFindingToCatalogPhrase, mapGraphInsightsToCatalogPhrases, mapRecommendationToCatalogPhrase, matchSignalFamily, } from "./cardObservationCatalog.js";
2
+ function addFact(bucket, seen, fact) {
3
+ if (seen.has(fact.family))
4
+ return;
5
+ seen.add(fact.family);
6
+ bucket.push(fact);
7
+ }
8
+ /**
9
+ * Tier 3 structured signal refs for the intelligence layer (no catalog phrases).
10
+ */
11
+ export function extractRepositoryContextFacts(result) {
12
+ const facts = [];
13
+ const seen = new Set();
14
+ const gi = result.graphInsights;
15
+ if (gi && typeof gi === "object") {
16
+ for (const { family } of mapGraphInsightsToCatalogPhrases(gi)) {
17
+ const metric = {};
18
+ if (typeof gi.nodes === "number")
19
+ metric.nodes = gi.nodes;
20
+ if (typeof gi.maxDepth === "number")
21
+ metric.maxDepth = gi.maxDepth;
22
+ const vuln = Array.isArray(gi.vulnerable) ? gi.vulnerable : [];
23
+ const hotspots = Array.isArray(gi.hotspots) ? gi.hotspots : [];
24
+ addFact(facts, seen, {
25
+ family,
26
+ source: "graphInsights",
27
+ refs: {
28
+ packageNames: [
29
+ ...vuln.map((v) => v.packageName),
30
+ ...hotspots.map((h) => h.packageName),
31
+ ].slice(0, 12),
32
+ metric: Object.keys(metric).length > 0 ? metric : undefined,
33
+ },
34
+ });
35
+ }
36
+ }
37
+ if (Array.isArray(result.explain?.reasons)) {
38
+ for (const reason of result.explain.reasons) {
39
+ const mapped = mapExplainReasonToCatalogPhrase(reason);
40
+ if (!mapped) {
41
+ const family = matchSignalFamily(`${reason.id} ${reason.title} ${reason.layer}`);
42
+ if (family) {
43
+ addFact(facts, seen, { family, source: "explain" });
44
+ }
45
+ continue;
46
+ }
47
+ addFact(facts, seen, { family: mapped.family, source: "explain" });
48
+ }
49
+ }
50
+ if (Array.isArray(result.findings)) {
51
+ for (const finding of result.findings) {
52
+ const mapped = mapFindingToCatalogPhrase(finding);
53
+ if (mapped) {
54
+ addFact(facts, seen, {
55
+ family: mapped.family,
56
+ source: "finding",
57
+ refs: { packageNames: [finding.packageName] },
58
+ });
59
+ }
60
+ }
61
+ }
62
+ if (Array.isArray(result.recommendations)) {
63
+ for (const rec of result.recommendations) {
64
+ const mapped = mapRecommendationToCatalogPhrase(rec);
65
+ if (mapped) {
66
+ addFact(facts, seen, { family: mapped.family, source: "finding" });
67
+ }
68
+ }
69
+ }
70
+ return facts;
71
+ }
@@ -0,0 +1,8 @@
1
+ import type { RepoIntelligence } from "../repoIntelligenceSchema.js";
2
+ /** Corpus-enabled runtime framework upgrade (auth/API paths). */
3
+ export declare const fixtureRepoIntelligenceFastify: RepoIntelligence;
4
+ /** Build-only toolchain change — no runtime paths. */
5
+ export declare const fixtureRepoIntelligenceTypescript: RepoIntelligence;
6
+ /** Empty corpus stub (engine-test-fixture default). */
7
+ export declare const fixtureRepoIntelligenceEmpty: RepoIntelligence;
8
+ //# sourceMappingURL=repoIntelligenceFixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repoIntelligenceFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,EAAE,gBA8B5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,EAAE,gBAS/C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,EAAE,gBAE1C,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** Corpus-enabled runtime framework upgrade (auth/API paths). */
2
+ export const fixtureRepoIntelligenceFastify = {
3
+ packages: {
4
+ fastify: {
5
+ runtimeSurface: "runtime",
6
+ reachability: "on_runtime_paths",
7
+ usage: {
8
+ packageName: "fastify",
9
+ paths: ["apps/api/src/server.ts", "apps/api/src/routes/account.ts"],
10
+ criticalPaths: ["apps/api/src/middleware/auth.ts"],
11
+ areas: ["API", "Auth middleware"],
12
+ },
13
+ },
14
+ },
15
+ blastRadius: {
16
+ level: "moderate",
17
+ factors: ["middleware_ordering", "route_handlers"],
18
+ changedPackageCount: 1,
19
+ },
20
+ applicationAreas: [
21
+ { id: "api", label: "API routes" },
22
+ { id: "auth", label: "Auth middleware" },
23
+ ],
24
+ hotspots: [
25
+ {
26
+ packageName: "fastify",
27
+ source: "code",
28
+ paths: ["apps/api/src/middleware/auth.ts"],
29
+ },
30
+ ],
31
+ frameworks: ["fastify"],
32
+ };
33
+ /** Build-only toolchain change — no runtime paths. */
34
+ export const fixtureRepoIntelligenceTypescript = {
35
+ packages: {
36
+ typescript: {
37
+ runtimeSurface: "build",
38
+ reachability: "build_only",
39
+ },
40
+ },
41
+ blastRadius: { level: "narrow", changedPackageCount: 1 },
42
+ applicationAreas: [],
43
+ };
44
+ /** Empty corpus stub (engine-test-fixture default). */
45
+ export const fixtureRepoIntelligenceEmpty = {
46
+ packages: {},
47
+ };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,11 @@ export * from "./formatCardExposureDisplay.js";
11
11
  export * from "./cardObservationCatalog.js";
12
12
  export * from "./deriveCardOperationalObservations.js";
13
13
  export * from "./deriveCardDisplaySummary.js";
14
+ export * from "./scanNarrativeFacts.js";
15
+ export * from "./repoIntelligenceSchema.js";
16
+ export * from "./deriveScanNarrative.js";
17
+ export * from "./extractRepositoryContextFacts.js";
18
+ export * from "./presentScanCardSummary.js";
14
19
  export * from "./scanCardSummary.js";
15
20
  export * from "./deriveScanDetailRecommendations.js";
16
21
  export * from "./deriveRiskScoreSummary.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -11,6 +11,11 @@ export * from "./formatCardExposureDisplay.js";
11
11
  export * from "./cardObservationCatalog.js";
12
12
  export * from "./deriveCardOperationalObservations.js";
13
13
  export * from "./deriveCardDisplaySummary.js";
14
+ export * from "./scanNarrativeFacts.js";
15
+ export * from "./repoIntelligenceSchema.js";
16
+ export * from "./deriveScanNarrative.js";
17
+ export * from "./extractRepositoryContextFacts.js";
18
+ export * from "./presentScanCardSummary.js";
14
19
  export * from "./scanCardSummary.js";
15
20
  export * from "./deriveScanDetailRecommendations.js";
16
21
  export * from "./deriveRiskScoreSummary.js";
@@ -53,7 +53,7 @@ function collectWhyBullets(result, max) {
53
53
  if (s && !out.includes(s))
54
54
  out.push(s);
55
55
  if (out.length >= max)
56
- return out;
56
+ return out.slice(0, max);
57
57
  }
58
58
  }
59
59
  const reasons = result.explain?.reasons;
@@ -0,0 +1,16 @@
1
+ import { type ScanCardSummary, type ScanPipelineStatus } from "./scanCardSummary.js";
2
+ import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
3
+ import { type CardExposureDisplay } from "./formatCardExposureDisplay.js";
4
+ import { type MergePosture } from "./riskVocabulary.js";
5
+ import type { ScanResult } from "./types.js";
6
+ export type PresentScanCardSummaryOptions = {
7
+ pipelineStatus?: ScanPipelineStatus;
8
+ findings?: ScanResult["findings"];
9
+ };
10
+ /**
11
+ * Dashboard card presentation DTO from consumer-agnostic facts.
12
+ */
13
+ export declare function presentScanCardSummary(facts: ScanNarrativeFacts, options?: PresentScanCardSummaryOptions): ScanCardSummary;
14
+ export type { CardExposureDisplay, MergePosture };
15
+ export declare function exposureFromFacts(facts: ScanNarrativeFacts): CardExposureDisplay | null;
16
+ //# sourceMappingURL=presentScanCardSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentScanCardSummary.d.ts","sourceRoot":"","sources":["../src/presentScanCardSummary.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,6BAA6B,GAAG;IAC1C,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;CACnC,CAAC;AA2HF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,6BAAkC,GAC1C,eAAe,CAkGjB;AAED,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAElD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,GAAG,IAAI,CAE5B"}