@mergesignal/shared 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actionsStepSummary.d.ts.map +1 -1
- package/dist/actionsStepSummary.js +2 -29
- package/dist/collectNarrativeWhyBullets.d.ts +12 -0
- package/dist/collectNarrativeWhyBullets.d.ts.map +1 -0
- package/dist/collectNarrativeWhyBullets.js +104 -0
- package/dist/deriveScanNarrative.d.ts.map +1 -1
- package/dist/deriveScanNarrative.js +157 -54
- package/dist/deriveScanNarrative.test.js +56 -1
- package/dist/fixtures/repoIntelligenceFixtures.d.ts +2 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
- package/dist/fixtures/repoIntelligenceFixtures.js +46 -0
- package/dist/formatInsight.d.ts +2 -2
- package/dist/formatInsight.d.ts.map +1 -1
- package/dist/formatInsight.js +2 -7
- package/dist/formatInsight.test.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/narrativeParity.test.d.ts +2 -0
- package/dist/narrativeParity.test.d.ts.map +1 -0
- package/dist/narrativeParity.test.js +64 -0
- package/dist/narrativePresentation.d.ts +50 -0
- package/dist/narrativePresentation.d.ts.map +1 -0
- package/dist/narrativePresentation.js +180 -0
- package/dist/narrativePresentation.test.d.ts +2 -0
- package/dist/narrativePresentation.test.d.ts.map +1 -0
- package/dist/narrativePresentation.test.js +53 -0
- package/dist/normalizeGeneratedText.d.ts +8 -0
- package/dist/normalizeGeneratedText.d.ts.map +1 -0
- package/dist/normalizeGeneratedText.js +31 -0
- package/dist/normalizeGeneratedText.test.d.ts +2 -0
- package/dist/normalizeGeneratedText.test.d.ts.map +1 -0
- package/dist/normalizeGeneratedText.test.js +18 -0
- package/dist/prCheckRunPresentation.d.ts +4 -3
- package/dist/prCheckRunPresentation.d.ts.map +1 -1
- package/dist/prCheckRunPresentation.js +56 -51
- package/dist/prCheckRunPresentation.test.js +2 -2
- package/dist/presentGitHubPrComment.d.ts +9 -0
- package/dist/presentGitHubPrComment.d.ts.map +1 -0
- package/dist/presentGitHubPrComment.js +80 -0
- package/dist/presentScanCardSummary.d.ts.map +1 -1
- package/dist/presentScanCardSummary.js +69 -64
- package/dist/riskVocabulary.js +1 -1
- package/dist/scanCardSummary.d.ts +9 -1
- package/dist/scanCardSummary.d.ts.map +1 -1
- package/dist/scanCardSummary.js +1 -1
- package/dist/scanCardSummary.test.js +12 -8
- package/dist/scanDetailViewModel.d.ts +9 -1
- package/dist/scanDetailViewModel.d.ts.map +1 -1
- package/dist/scanDetailViewModel.js +164 -37
- package/dist/scanDetailViewModel.test.js +27 -1
- package/dist/scanNarrativeFacts.d.ts +21 -8
- package/dist/scanNarrativeFacts.d.ts.map +1 -1
- package/dist/scanNarrativeFacts.js +6 -0
- package/dist/scanSurfaceCopy.d.ts +26 -26
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +26 -26
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export * from "./repoIntelligenceSchema.js";
|
|
|
16
16
|
export * from "./deriveScanNarrative.js";
|
|
17
17
|
export * from "./extractRepositoryContextFacts.js";
|
|
18
18
|
export * from "./presentScanCardSummary.js";
|
|
19
|
+
export * from "./narrativePresentation.js";
|
|
20
|
+
export * from "./normalizeGeneratedText.js";
|
|
21
|
+
export * from "./collectNarrativeWhyBullets.js";
|
|
22
|
+
export * from "./presentGitHubPrComment.js";
|
|
19
23
|
export * from "./scanCardSummary.js";
|
|
20
24
|
export * from "./deriveScanDetailRecommendations.js";
|
|
21
25
|
export * from "./deriveRiskScoreSummary.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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"}
|
|
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,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,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
|
@@ -16,6 +16,10 @@ export * from "./repoIntelligenceSchema.js";
|
|
|
16
16
|
export * from "./deriveScanNarrative.js";
|
|
17
17
|
export * from "./extractRepositoryContextFacts.js";
|
|
18
18
|
export * from "./presentScanCardSummary.js";
|
|
19
|
+
export * from "./narrativePresentation.js";
|
|
20
|
+
export * from "./normalizeGeneratedText.js";
|
|
21
|
+
export * from "./collectNarrativeWhyBullets.js";
|
|
22
|
+
export * from "./presentGitHubPrComment.js";
|
|
19
23
|
export * from "./scanCardSummary.js";
|
|
20
24
|
export * from "./deriveScanDetailRecommendations.js";
|
|
21
25
|
export * from "./deriveRiskScoreSummary.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrativeParity.test.d.ts","sourceRoot":"","sources":["../src/narrativeParity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
+
import { buildPrCheckRunSummaryMarkdown } from "./prCheckRunPresentation.js";
|
|
4
|
+
import { deriveScanCardSummary } from "./scanCardSummary.js";
|
|
5
|
+
import { deriveScanDetailViewModel } from "./scanDetailViewModel.js";
|
|
6
|
+
import { presentGitHubPrComment } from "./presentGitHubPrComment.js";
|
|
7
|
+
import { fixtureRepoIntelligenceFastify } from "./fixtures/repoIntelligenceFixtures.js";
|
|
8
|
+
const ORIGIN = "https://app.example.com";
|
|
9
|
+
const SCAN_ID = "22222222-2222-4222-8222-222222222222";
|
|
10
|
+
describe("narrative parity across consumers", () => {
|
|
11
|
+
it("card, detail, GitHub share primary package and reachability story", () => {
|
|
12
|
+
const result = {
|
|
13
|
+
totalScore: 55,
|
|
14
|
+
layerScores: {
|
|
15
|
+
security: 10,
|
|
16
|
+
maintainability: 20,
|
|
17
|
+
ecosystem: 30,
|
|
18
|
+
upgradeImpact: 15,
|
|
19
|
+
},
|
|
20
|
+
findings: [],
|
|
21
|
+
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
22
|
+
changedPackages: ["fastify"],
|
|
23
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
24
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
25
|
+
decision: {
|
|
26
|
+
recommendation: "needs_review",
|
|
27
|
+
confidence: "medium",
|
|
28
|
+
reasoning: [],
|
|
29
|
+
},
|
|
30
|
+
insights: [
|
|
31
|
+
{
|
|
32
|
+
type: "usage_risk",
|
|
33
|
+
priority: "high",
|
|
34
|
+
confidence: "confirmed",
|
|
35
|
+
scope: "changed",
|
|
36
|
+
message: "Review fastify middleware ordering",
|
|
37
|
+
context: "apps/api/src/middleware/auth.ts",
|
|
38
|
+
remediation: "Run auth smoke tests",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
const card = deriveScanCardSummary(result, "done");
|
|
43
|
+
const detail = deriveScanDetailViewModel(result, {
|
|
44
|
+
scanId: SCAN_ID,
|
|
45
|
+
status: "done",
|
|
46
|
+
});
|
|
47
|
+
const checkRun = buildPrCheckRunSummaryMarkdown({
|
|
48
|
+
result,
|
|
49
|
+
scanId: SCAN_ID,
|
|
50
|
+
webAppOrigin: ORIGIN,
|
|
51
|
+
baseline: false,
|
|
52
|
+
});
|
|
53
|
+
const prComment = presentGitHubPrComment(deriveScanNarrative(result), result);
|
|
54
|
+
expect(card.changedPackagesDisplay).toContain("fastify");
|
|
55
|
+
expect(detail?.narrativeContext.changedPackagesDisplay).toContain("fastify");
|
|
56
|
+
expect(card.reachabilityLabel).toBe("On runtime paths");
|
|
57
|
+
expect(detail?.narrativeContext.reachabilityLabel).toBe("On runtime paths");
|
|
58
|
+
const asciiOnly = /^[\x00-\x7F]*$/;
|
|
59
|
+
expect(asciiOnly.test(checkRun)).toBe(true);
|
|
60
|
+
expect(asciiOnly.test(prComment)).toBe(true);
|
|
61
|
+
expect(checkRun.toLowerCase()).toContain("fastify");
|
|
62
|
+
expect(prComment.toLowerCase()).toContain("fastify");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
2
|
+
export type SummarizePackageUsageOptions = {
|
|
3
|
+
maxPaths?: number;
|
|
4
|
+
maxPackages?: number;
|
|
5
|
+
};
|
|
6
|
+
export type PackageUsageSummary = {
|
|
7
|
+
pathCount: number;
|
|
8
|
+
pathSamples: string[];
|
|
9
|
+
areaLabels: string[];
|
|
10
|
+
packageNames: string[];
|
|
11
|
+
};
|
|
12
|
+
export type ReachabilitySummary = {
|
|
13
|
+
kindLabel: string | null;
|
|
14
|
+
pathSamples: string[];
|
|
15
|
+
frameworks: string[];
|
|
16
|
+
};
|
|
17
|
+
export type BlastRadiusSummary = {
|
|
18
|
+
levelLabel: string | null;
|
|
19
|
+
factors: string[];
|
|
20
|
+
changedPackageCount: number | null;
|
|
21
|
+
};
|
|
22
|
+
export declare function selectPrimaryChangedPackage(facts: ScanNarrativeFacts): string | null;
|
|
23
|
+
export declare function formatChangedPackagesShort(facts: ScanNarrativeFacts, maxNames?: number): string | null;
|
|
24
|
+
export declare function formatChangedPackagesDetail(facts: ScanNarrativeFacts): string[];
|
|
25
|
+
export declare function summarizePackageUsage(facts: ScanNarrativeFacts, options?: SummarizePackageUsageOptions): PackageUsageSummary | null;
|
|
26
|
+
declare function labelRuntimeSurface(facts: ScanNarrativeFacts): string | null;
|
|
27
|
+
declare function labelReachabilityKind(facts: ScanNarrativeFacts): string | null;
|
|
28
|
+
declare function labelBlastRadiusLevel(facts: ScanNarrativeFacts): string | null;
|
|
29
|
+
export declare function summarizeReachability(facts: ScanNarrativeFacts, maxPaths?: number): ReachabilitySummary;
|
|
30
|
+
export declare function summarizeBlastRadius(facts: ScanNarrativeFacts, maxFactors?: number): BlastRadiusSummary;
|
|
31
|
+
export declare function summarizeHotspots(facts: ScanNarrativeFacts, max?: number): Array<{
|
|
32
|
+
packageName: string;
|
|
33
|
+
source: "code" | "graph";
|
|
34
|
+
pathSample: string | null;
|
|
35
|
+
}>;
|
|
36
|
+
export type SelectReviewerGuidanceOptions = {
|
|
37
|
+
scope?: "changed" | "all" | "any";
|
|
38
|
+
max?: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function selectReviewerGuidance(facts: ScanNarrativeFacts, options?: SelectReviewerGuidanceOptions): ScanNarrativeFacts["reviewerGuidance"];
|
|
41
|
+
export declare function composeVerificationPrompt(facts: ScanNarrativeFacts): string | null;
|
|
42
|
+
export declare function formatUsageSummaryLine(facts: ScanNarrativeFacts, maxPaths?: number): string | null;
|
|
43
|
+
export declare function formatBlastRadiusDetailLine(facts: ScanNarrativeFacts, maxFactors?: number): string | null;
|
|
44
|
+
export declare function formatFrameworksSummary(facts: ScanNarrativeFacts, max?: number): string | null;
|
|
45
|
+
export declare function composeContextLineFromFacts(facts: ScanNarrativeFacts, options?: {
|
|
46
|
+
includePathSample?: boolean;
|
|
47
|
+
maxAreas?: number;
|
|
48
|
+
}): string | null;
|
|
49
|
+
export { labelRuntimeSurface, labelReachabilityKind, labelBlastRadiusLevel };
|
|
50
|
+
//# sourceMappingURL=narrativePresentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrativePresentation.d.ts","sourceRoot":"","sources":["../src/narrativePresentation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
2
|
+
export function selectPrimaryChangedPackage(facts) {
|
|
3
|
+
return facts.changedPackages.primary;
|
|
4
|
+
}
|
|
5
|
+
export function formatChangedPackagesShort(facts, maxNames = 2) {
|
|
6
|
+
const { primary, others } = facts.changedPackages;
|
|
7
|
+
if (!primary)
|
|
8
|
+
return null;
|
|
9
|
+
if (others.length === 0)
|
|
10
|
+
return primary;
|
|
11
|
+
if (maxNames >= 2 && others.length === 1) {
|
|
12
|
+
return `${primary}, ${others[0]}`;
|
|
13
|
+
}
|
|
14
|
+
return `${primary} +${others.length}`;
|
|
15
|
+
}
|
|
16
|
+
export function formatChangedPackagesDetail(facts) {
|
|
17
|
+
return facts.changedPackages.all;
|
|
18
|
+
}
|
|
19
|
+
export function summarizePackageUsage(facts, options = {}) {
|
|
20
|
+
const maxPaths = options.maxPaths ?? 3;
|
|
21
|
+
const maxPackages = options.maxPackages ?? 5;
|
|
22
|
+
const rows = facts.packageUsage.slice(0, maxPackages);
|
|
23
|
+
if (rows.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
const pathSamples = [];
|
|
26
|
+
let pathCount = 0;
|
|
27
|
+
const areaLabels = [];
|
|
28
|
+
const packageNames = [];
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
packageNames.push(row.packageName);
|
|
31
|
+
const rowPaths = [...row.paths, ...row.criticalPaths, ...row.files].filter(Boolean);
|
|
32
|
+
pathCount += rowPaths.length;
|
|
33
|
+
for (const p of rowPaths) {
|
|
34
|
+
if (pathSamples.length >= maxPaths)
|
|
35
|
+
break;
|
|
36
|
+
if (!pathSamples.includes(p))
|
|
37
|
+
pathSamples.push(p);
|
|
38
|
+
}
|
|
39
|
+
for (const a of row.areas) {
|
|
40
|
+
if (!areaLabels.includes(a))
|
|
41
|
+
areaLabels.push(a);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (pathCount === 0 && areaLabels.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
return { pathCount, pathSamples, areaLabels, packageNames };
|
|
47
|
+
}
|
|
48
|
+
function labelRuntimeSurface(facts) {
|
|
49
|
+
const rs = facts.runtimeSurface;
|
|
50
|
+
if (!rs)
|
|
51
|
+
return null;
|
|
52
|
+
return scanSurfaceCopy.narrativeCard.runtimeSurface[rs.kind];
|
|
53
|
+
}
|
|
54
|
+
function labelReachabilityKind(facts) {
|
|
55
|
+
const r = facts.reachability;
|
|
56
|
+
if (!r)
|
|
57
|
+
return null;
|
|
58
|
+
return scanSurfaceCopy.narrativeCard.reachability[r.kind];
|
|
59
|
+
}
|
|
60
|
+
function labelBlastRadiusLevel(facts) {
|
|
61
|
+
const br = facts.blastRadius;
|
|
62
|
+
if (!br)
|
|
63
|
+
return null;
|
|
64
|
+
return scanSurfaceCopy.narrativeCard.blastRadius[br.level];
|
|
65
|
+
}
|
|
66
|
+
export function summarizeReachability(facts, maxPaths = 1) {
|
|
67
|
+
const kindLabel = labelReachabilityKind(facts);
|
|
68
|
+
const paths = facts.reachability?.evidence.paths ?? [];
|
|
69
|
+
const frameworks = facts.reachability?.evidence.frameworks ?? facts.frameworks;
|
|
70
|
+
return {
|
|
71
|
+
kindLabel,
|
|
72
|
+
pathSamples: paths.slice(0, maxPaths),
|
|
73
|
+
frameworks: frameworks.slice(0, 3),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function summarizeBlastRadius(facts, maxFactors = 3) {
|
|
77
|
+
const br = facts.blastRadius;
|
|
78
|
+
return {
|
|
79
|
+
levelLabel: labelBlastRadiusLevel(facts),
|
|
80
|
+
factors: (br?.factors ?? []).slice(0, maxFactors),
|
|
81
|
+
changedPackageCount: br?.changedPackageCount ?? null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function summarizeHotspots(facts, max = 5) {
|
|
85
|
+
return facts.hotspots.slice(0, max).map((h) => ({
|
|
86
|
+
packageName: h.packageName,
|
|
87
|
+
source: h.source,
|
|
88
|
+
pathSample: h.paths[0] ?? null,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
export function selectReviewerGuidance(facts, options = {}) {
|
|
92
|
+
const max = options.max ?? 6;
|
|
93
|
+
let list = facts.reviewerGuidance;
|
|
94
|
+
if (options.scope === "changed") {
|
|
95
|
+
list = list.filter((g) => g.scope === "changed");
|
|
96
|
+
}
|
|
97
|
+
else if (options.scope === "all") {
|
|
98
|
+
list = list.filter((g) => g.scope === "all");
|
|
99
|
+
}
|
|
100
|
+
return list.slice(0, max);
|
|
101
|
+
}
|
|
102
|
+
export function composeVerificationPrompt(facts) {
|
|
103
|
+
const changed = selectReviewerGuidance(facts, { scope: "changed", max: 1 });
|
|
104
|
+
const remediation = changed[0]?.remediation?.trim();
|
|
105
|
+
if (remediation)
|
|
106
|
+
return remediation;
|
|
107
|
+
const usage = summarizePackageUsage(facts, { maxPaths: 1 });
|
|
108
|
+
if (usage?.pathSamples[0]) {
|
|
109
|
+
return `Confirm behavior where this dependency is used (${usage.pathSamples[0]}).`;
|
|
110
|
+
}
|
|
111
|
+
const any = selectReviewerGuidance(facts, { max: 1 });
|
|
112
|
+
return any[0]?.remediation?.trim() ?? null;
|
|
113
|
+
}
|
|
114
|
+
export function formatUsageSummaryLine(facts, maxPaths = 1) {
|
|
115
|
+
const usage = summarizePackageUsage(facts, { maxPaths });
|
|
116
|
+
if (!usage)
|
|
117
|
+
return null;
|
|
118
|
+
if (usage.pathSamples.length > 0) {
|
|
119
|
+
const sample = usage.pathSamples[0];
|
|
120
|
+
if (usage.pathCount <= 1) {
|
|
121
|
+
return `Used in ${sample}`;
|
|
122
|
+
}
|
|
123
|
+
return `Used in ${usage.pathCount} paths (${sample})`;
|
|
124
|
+
}
|
|
125
|
+
if (usage.areaLabels.length > 0) {
|
|
126
|
+
return `Used in ${usage.areaLabels.slice(0, 2).join(", ")}`;
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
export function formatBlastRadiusDetailLine(facts, maxFactors = 1) {
|
|
131
|
+
const summary = summarizeBlastRadius(facts, maxFactors);
|
|
132
|
+
if (summary.factors.length === 0)
|
|
133
|
+
return null;
|
|
134
|
+
const factor = summary.factors[0].replace(/_/g, " ");
|
|
135
|
+
if (summary.levelLabel) {
|
|
136
|
+
return `${summary.levelLabel}: ${factor}`;
|
|
137
|
+
}
|
|
138
|
+
return factor;
|
|
139
|
+
}
|
|
140
|
+
export function formatFrameworksSummary(facts, max = 2) {
|
|
141
|
+
const frameworks = facts.frameworks.slice(0, max);
|
|
142
|
+
if (frameworks.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
if (frameworks.length === 1)
|
|
145
|
+
return frameworks[0];
|
|
146
|
+
return frameworks.join(", ");
|
|
147
|
+
}
|
|
148
|
+
export function composeContextLineFromFacts(facts, options = {}) {
|
|
149
|
+
const parts = [];
|
|
150
|
+
const runtime = labelRuntimeSurface(facts);
|
|
151
|
+
const reach = summarizeReachability(facts, options.includePathSample ? 1 : 0);
|
|
152
|
+
const blast = labelBlastRadiusLevel(facts);
|
|
153
|
+
if (runtime)
|
|
154
|
+
parts.push(runtime);
|
|
155
|
+
if (reach.kindLabel) {
|
|
156
|
+
if (options.includePathSample && reach.pathSamples[0]) {
|
|
157
|
+
parts.push(`${reach.kindLabel} (${reach.pathSamples[0]})`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
parts.push(reach.kindLabel);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (blast)
|
|
164
|
+
parts.push(blast);
|
|
165
|
+
const maxAreas = options.maxAreas ?? 2;
|
|
166
|
+
const areaLabels = [];
|
|
167
|
+
for (const area of facts.affectedAreas) {
|
|
168
|
+
if (areaLabels.length >= maxAreas)
|
|
169
|
+
break;
|
|
170
|
+
if (!areaLabels.includes(area.label))
|
|
171
|
+
areaLabels.push(area.label);
|
|
172
|
+
}
|
|
173
|
+
if (areaLabels.length > 0) {
|
|
174
|
+
parts.push(areaLabels.join(scanSurfaceCopy.narrativeCard.areasSeparator));
|
|
175
|
+
}
|
|
176
|
+
if (parts.length === 0)
|
|
177
|
+
return null;
|
|
178
|
+
return parts.join(scanSurfaceCopy.narrativeCard.contextSeparator);
|
|
179
|
+
}
|
|
180
|
+
export { labelRuntimeSurface, labelReachabilityKind, labelBlastRadiusLevel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrativePresentation.test.d.ts","sourceRoot":"","sources":["../src/narrativePresentation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
+
import { fixtureRepoIntelligenceFastify } from "./fixtures/repoIntelligenceFixtures.js";
|
|
4
|
+
import { composeVerificationPrompt, formatChangedPackagesShort, formatUsageSummaryLine, summarizePackageUsage, } from "./narrativePresentation.js";
|
|
5
|
+
const baseResult = {
|
|
6
|
+
totalScore: 10,
|
|
7
|
+
layerScores: {
|
|
8
|
+
security: 1,
|
|
9
|
+
maintainability: 2,
|
|
10
|
+
ecosystem: 3,
|
|
11
|
+
upgradeImpact: 4,
|
|
12
|
+
},
|
|
13
|
+
findings: [],
|
|
14
|
+
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
15
|
+
};
|
|
16
|
+
describe("narrativePresentation", () => {
|
|
17
|
+
it("formats changed packages with second name when only one other", () => {
|
|
18
|
+
const facts = deriveScanNarrative({
|
|
19
|
+
...baseResult,
|
|
20
|
+
changedPackages: ["lodash", "axios"],
|
|
21
|
+
});
|
|
22
|
+
expect(formatChangedPackagesShort(facts, 2)).toBe("lodash, axios");
|
|
23
|
+
});
|
|
24
|
+
it("summarizes package usage paths from facts", () => {
|
|
25
|
+
const facts = deriveScanNarrative({
|
|
26
|
+
...baseResult,
|
|
27
|
+
changedPackages: ["fastify"],
|
|
28
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
29
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
30
|
+
});
|
|
31
|
+
const usage = summarizePackageUsage(facts);
|
|
32
|
+
expect(usage?.pathCount).toBeGreaterThan(0);
|
|
33
|
+
expect(formatUsageSummaryLine(facts)).toMatch(/Used in/);
|
|
34
|
+
});
|
|
35
|
+
it("composes verification from remediation when present", () => {
|
|
36
|
+
const facts = deriveScanNarrative({
|
|
37
|
+
...baseResult,
|
|
38
|
+
changedPackages: ["pkg"],
|
|
39
|
+
insights: [
|
|
40
|
+
{
|
|
41
|
+
type: "usage_risk",
|
|
42
|
+
priority: "high",
|
|
43
|
+
confidence: "confirmed",
|
|
44
|
+
scope: "changed",
|
|
45
|
+
message: "Risk in billing",
|
|
46
|
+
context: "billing",
|
|
47
|
+
remediation: "Run export job smoke test",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
expect(composeVerificationPrompt(facts)).toBe("Run export job smoke test");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforce ASCII-safe generated user-facing text (hyphens, separators, ellipsis).
|
|
3
|
+
*/
|
|
4
|
+
export declare function normalizeGeneratedText(text: string): string;
|
|
5
|
+
export declare function normalizeGeneratedTextNullable(text: string | null | undefined): string | null;
|
|
6
|
+
/** Normalize every string field on a shallow object (presenter DTOs). */
|
|
7
|
+
export declare function normalizeGeneratedStrings<T extends Record<string, unknown>>(obj: T): T;
|
|
8
|
+
//# sourceMappingURL=normalizeGeneratedText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeGeneratedText.d.ts","sourceRoot":"","sources":["../src/normalizeGeneratedText.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO3D;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,MAAM,GAAG,IAAI,CAIf;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzE,GAAG,EAAE,CAAC,GACL,CAAC,CAYH"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const TYPOGRAPHIC_DASH = /[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/g;
|
|
2
|
+
/**
|
|
3
|
+
* Enforce ASCII-safe generated user-facing text (hyphens, separators, ellipsis).
|
|
4
|
+
*/
|
|
5
|
+
export function normalizeGeneratedText(text) {
|
|
6
|
+
return text
|
|
7
|
+
.replace(TYPOGRAPHIC_DASH, "-")
|
|
8
|
+
.replace(/\u00B7/g, " | ")
|
|
9
|
+
.replace(/\u2026/g, "...")
|
|
10
|
+
.replace(/–/gi, "-")
|
|
11
|
+
.replace(/—/gi, "-");
|
|
12
|
+
}
|
|
13
|
+
export function normalizeGeneratedTextNullable(text) {
|
|
14
|
+
if (text == null)
|
|
15
|
+
return null;
|
|
16
|
+
const normalized = normalizeGeneratedText(text);
|
|
17
|
+
return normalized.length > 0 ? normalized : null;
|
|
18
|
+
}
|
|
19
|
+
/** Normalize every string field on a shallow object (presenter DTOs). */
|
|
20
|
+
export function normalizeGeneratedStrings(obj) {
|
|
21
|
+
const out = { ...obj };
|
|
22
|
+
for (const [key, value] of Object.entries(out)) {
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
out[key] = normalizeGeneratedText(value);
|
|
25
|
+
}
|
|
26
|
+
else if (Array.isArray(value)) {
|
|
27
|
+
out[key] = value.map((item) => typeof item === "string" ? normalizeGeneratedText(item) : item);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeGeneratedText.test.d.ts","sourceRoot":"","sources":["../src/normalizeGeneratedText.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
|
|
3
|
+
import { scanSurfaceCopyFlat } from "./scanSurfaceCopy.js";
|
|
4
|
+
describe("normalizeGeneratedText", () => {
|
|
5
|
+
it("replaces typographic dashes with ASCII hyphen-minus", () => {
|
|
6
|
+
expect(normalizeGeneratedText("foo – bar — baz")).toBe("foo - bar - baz");
|
|
7
|
+
expect(normalizeGeneratedText("a—b–c")).toBe("a-b-c");
|
|
8
|
+
});
|
|
9
|
+
it("leaves ASCII text unchanged", () => {
|
|
10
|
+
expect(normalizeGeneratedText("Safe - Needs review")).toBe("Safe - Needs review");
|
|
11
|
+
});
|
|
12
|
+
it("scanSurfaceCopy flat strings are ASCII-safe", () => {
|
|
13
|
+
const asciiOnly = /^[\x00-\x7F]*$/;
|
|
14
|
+
for (const value of Object.values(scanSurfaceCopyFlat())) {
|
|
15
|
+
expect(asciiOnly.test(value), value).toBe(true);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
1
2
|
import type { ScanResult } from "./types.js";
|
|
2
3
|
export declare const CHECK_RUN_MAX_WHY_BULLETS = 3;
|
|
3
4
|
export declare const CHECK_RUN_MAX_ACTION_BULLETS = 3;
|
|
@@ -57,9 +58,9 @@ export type CheckRunSection = {
|
|
|
57
58
|
export declare function formatScanDashboardUrl(webAppOrigin: string, scanId: string): string;
|
|
58
59
|
export declare function buildPrCheckRunTitle(opts?: PrCheckRunTitleOptions): string;
|
|
59
60
|
/** Strong drivers for optional repo-context line (full mode only). */
|
|
60
|
-
export declare function hasStrongRepoGraphDrivers(result: ScanResult): boolean;
|
|
61
|
-
export declare function deriveCheckRunPolicy(result: ScanResult, ctx: Pick<PrCheckRunSummaryContext, "baseline"
|
|
62
|
-
export declare function buildPrCheckRunSections(policy: CheckRunPolicy, result: ScanResult, ctx: PrCheckRunSummaryContext): CheckRunSection[];
|
|
61
|
+
export declare function hasStrongRepoGraphDrivers(result: ScanResult, facts?: ScanNarrativeFacts): boolean;
|
|
62
|
+
export declare function deriveCheckRunPolicy(result: ScanResult, ctx: Pick<PrCheckRunSummaryContext, "baseline">, facts?: ScanNarrativeFacts): CheckRunPolicy;
|
|
63
|
+
export declare function buildPrCheckRunSections(policy: CheckRunPolicy, result: ScanResult, ctx: PrCheckRunSummaryContext, facts?: ScanNarrativeFacts): CheckRunSection[];
|
|
63
64
|
export declare function renderCheckRunMarkdown(sections: CheckRunSection[]): string;
|
|
64
65
|
export declare function buildPrCheckRunSummaryMarkdown(ctx: PrCheckRunSummaryContext): string;
|
|
65
66
|
/** Whether baseline scan has no actionable PR bullets (for tests / callers). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prCheckRunPresentation.d.ts","sourceRoot":"","sources":["../src/prCheckRunPresentation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prCheckRunPresentation.d.ts","sourceRoot":"","sources":["../src/prCheckRunPresentation.ts"],"names":[],"mappings":"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"}
|