@mergesignal/shared 0.2.6 → 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 +10 -0
- package/dist/deriveScanNarrative.d.ts.map +1 -0
- package/dist/deriveScanNarrative.js +370 -0
- package/dist/deriveScanNarrative.test.d.ts +2 -0
- package/dist/deriveScanNarrative.test.d.ts.map +1 -0
- package/dist/deriveScanNarrative.test.js +164 -0
- package/dist/extractRepositoryContextFacts.d.ts +16 -0
- package/dist/extractRepositoryContextFacts.d.ts.map +1 -0
- package/dist/extractRepositoryContextFacts.js +71 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts +10 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -0
- package/dist/fixtures/repoIntelligenceFixtures.js +93 -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 +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -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 +16 -0
- package/dist/presentScanCardSummary.d.ts.map +1 -0
- package/dist/presentScanCardSummary.js +212 -0
- package/dist/productMessaging.d.ts +1 -1
- package/dist/productMessaging.js +1 -1
- package/dist/repoIntelligenceSchema.d.ts +123 -0
- package/dist/repoIntelligenceSchema.d.ts.map +1 -0
- package/dist/repoIntelligenceSchema.js +174 -0
- package/dist/riskVocabulary.js +1 -1
- package/dist/scanCardSummary.d.ts +21 -3
- package/dist/scanCardSummary.d.ts.map +1 -1
- package/dist/scanCardSummary.js +17 -58
- package/dist/scanCardSummary.test.js +25 -8
- package/dist/scanDetailViewModel.d.ts +22 -0
- package/dist/scanDetailViewModel.d.ts.map +1 -1
- package/dist/scanDetailViewModel.js +247 -31
- package/dist/scanDetailViewModel.test.js +29 -3
- package/dist/scanNarrativeFacts.d.ts +92 -0
- package/dist/scanNarrativeFacts.d.ts.map +1 -0
- package/dist/scanNarrativeFacts.js +24 -0
- package/dist/scanSurfaceCopy.d.ts +49 -23
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +49 -23
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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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"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* GitHub Check Run markdown — policy → sections → dumb renderer.
|
|
3
3
|
* Thin projection of ScanResult for PR decision support (not a presentation framework).
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { collectWhyBullets } from "./collectNarrativeWhyBullets.js";
|
|
6
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
6
7
|
import { formatInsight } from "./formatInsight.js";
|
|
7
|
-
import {
|
|
8
|
+
import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
|
|
9
|
+
import { selectReviewerGuidance } from "./narrativePresentation.js";
|
|
10
|
+
import { layerDriverSummary, sortPRInsightsForDisplay, sortRecommendationsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
|
|
8
11
|
import { mergePostureLabel } from "./riskVocabulary.js";
|
|
9
12
|
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
10
13
|
// --- Caps (change only with tests) ---
|
|
@@ -40,43 +43,17 @@ export function formatScanDashboardUrl(webAppOrigin, scanId) {
|
|
|
40
43
|
export function buildPrCheckRunTitle(opts = {}) {
|
|
41
44
|
const base = scanSurfaceCopy.checkRun.titleBase;
|
|
42
45
|
if (opts.baselineOnly) {
|
|
43
|
-
return `${base}
|
|
46
|
+
return `${base} - ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
|
|
44
47
|
}
|
|
45
48
|
return base;
|
|
46
49
|
}
|
|
47
|
-
function collectWhyBullets(result, max) {
|
|
48
|
-
const out = [];
|
|
49
|
-
const reasoning = result.decision?.reasoning;
|
|
50
|
-
if (Array.isArray(reasoning)) {
|
|
51
|
-
for (const r of reasoning) {
|
|
52
|
-
const s = humanizeEngineSurfaceText(String(r).trim());
|
|
53
|
-
if (s && !out.includes(s))
|
|
54
|
-
out.push(s);
|
|
55
|
-
if (out.length >= max)
|
|
56
|
-
return out;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const reasons = result.explain?.reasons;
|
|
60
|
-
if (Array.isArray(reasons) && out.length < max) {
|
|
61
|
-
for (const r of reasons) {
|
|
62
|
-
const title = String(r?.title ?? r?.id ?? "").trim();
|
|
63
|
-
const readable = humanizeEngineSurfaceText(title);
|
|
64
|
-
if (readable && !out.includes(readable))
|
|
65
|
-
out.push(readable);
|
|
66
|
-
if (out.length >= max)
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (out.length === 0) {
|
|
71
|
-
const one = deriveScanSummaryText(result);
|
|
72
|
-
if (one)
|
|
73
|
-
out.push(one);
|
|
74
|
-
}
|
|
75
|
-
return out.slice(0, max);
|
|
76
|
-
}
|
|
77
50
|
/** Strong drivers for optional repo-context line (full mode only). */
|
|
78
|
-
export function hasStrongRepoGraphDrivers(result) {
|
|
79
|
-
|
|
51
|
+
export function hasStrongRepoGraphDrivers(result, facts) {
|
|
52
|
+
if (facts?.availability.mode === "pr_intelligence" ||
|
|
53
|
+
facts?.availability.tiersPresent.tier1) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const phrases = collectWhyBullets(facts ?? deriveScanNarrative(result), result, CHECK_RUN_MAX_REPO_DRIVER_PHRASES);
|
|
80
57
|
return phrases.some((p) => p.trim().length >= 12);
|
|
81
58
|
}
|
|
82
59
|
function hasActionableBullets(result) {
|
|
@@ -92,7 +69,7 @@ function insightToBullet(insight) {
|
|
|
92
69
|
const msg = truncateWithEllipsis(f.message, CHECK_RUN_ACTION_BULLET_MAX_CHARS);
|
|
93
70
|
const action = f.action.trim();
|
|
94
71
|
if (action && action.length < 120) {
|
|
95
|
-
return `${msg}
|
|
72
|
+
return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
|
|
96
73
|
}
|
|
97
74
|
return msg;
|
|
98
75
|
}
|
|
@@ -100,7 +77,7 @@ function recommendationToBullet(rec) {
|
|
|
100
77
|
const title = String(rec.title ?? "").trim();
|
|
101
78
|
const rat = String(rec.rationale ?? "").trim();
|
|
102
79
|
const pkgs = Array.isArray(rec.packages) && rec.packages.length > 0
|
|
103
|
-
? ` (${rec.packages.slice(0, 3).join(", ")}${rec.packages.length > 3 ? ",
|
|
80
|
+
? ` (${rec.packages.slice(0, 3).join(", ")}${rec.packages.length > 3 ? ", ..." : ""})`
|
|
104
81
|
: "";
|
|
105
82
|
if (rat && (!title || rat.length > title.length)) {
|
|
106
83
|
return truncateWithEllipsis(rat, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkgs;
|
|
@@ -113,6 +90,31 @@ function findingToBullet(f) {
|
|
|
113
90
|
const pkg = f.packageName ? ` (\`${f.packageName}\`)` : "";
|
|
114
91
|
return (truncateWithEllipsis(title || f.description, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkg);
|
|
115
92
|
}
|
|
93
|
+
function guidanceToActionBullet(g) {
|
|
94
|
+
const msg = truncateWithEllipsis(g.message.trim(), CHECK_RUN_ACTION_BULLET_MAX_CHARS);
|
|
95
|
+
const action = g.remediation?.trim();
|
|
96
|
+
if (action && action.length < 120) {
|
|
97
|
+
return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
|
|
98
|
+
}
|
|
99
|
+
return normalizeGeneratedText(msg);
|
|
100
|
+
}
|
|
101
|
+
function buildActionBulletsFromFacts(facts, result, max) {
|
|
102
|
+
const prIntel = facts.availability.mode === "pr_intelligence" ||
|
|
103
|
+
facts.availability.tiersPresent.tier1;
|
|
104
|
+
if (!prIntel) {
|
|
105
|
+
return buildActionBullets(result, max);
|
|
106
|
+
}
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const g of selectReviewerGuidance(facts, { scope: "changed", max })) {
|
|
109
|
+
out.push(guidanceToActionBullet(g));
|
|
110
|
+
if (out.length >= max)
|
|
111
|
+
return out.slice(0, max);
|
|
112
|
+
}
|
|
113
|
+
if (out.length >= max)
|
|
114
|
+
return out.slice(0, max);
|
|
115
|
+
const legacy = buildActionBullets(result, max - out.length);
|
|
116
|
+
return [...out, ...legacy].slice(0, max);
|
|
117
|
+
}
|
|
116
118
|
function buildActionBullets(result, max) {
|
|
117
119
|
const out = [];
|
|
118
120
|
const sortedI = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
|
|
@@ -156,12 +158,13 @@ function layerScoreRows(result) {
|
|
|
156
158
|
}
|
|
157
159
|
return rows;
|
|
158
160
|
}
|
|
159
|
-
export function deriveCheckRunPolicy(result, ctx) {
|
|
161
|
+
export function deriveCheckRunPolicy(result, ctx, facts) {
|
|
162
|
+
const resolvedFacts = facts ?? deriveScanNarrative(result);
|
|
160
163
|
const baseline = ctx.baseline;
|
|
161
|
-
const actionBullets =
|
|
164
|
+
const actionBullets = buildActionBulletsFromFacts(resolvedFacts, result, CHECK_RUN_MAX_ACTION_BULLETS);
|
|
162
165
|
const layerRows = layerScoreRows(result);
|
|
163
166
|
const showRepoGraphContext = !baseline &&
|
|
164
|
-
hasStrongRepoGraphDrivers(result) &&
|
|
167
|
+
hasStrongRepoGraphDrivers(result, resolvedFacts) &&
|
|
165
168
|
typeof result.totalScore === "number" &&
|
|
166
169
|
Number.isFinite(result.totalScore);
|
|
167
170
|
const showLayerScores = !baseline && layerRows.length > 0;
|
|
@@ -189,14 +192,15 @@ function buildLeadSection(result, policy) {
|
|
|
189
192
|
: null;
|
|
190
193
|
return { kind: "lead", posture, riskIndexLine };
|
|
191
194
|
}
|
|
192
|
-
export function buildPrCheckRunSections(policy, result, ctx) {
|
|
195
|
+
export function buildPrCheckRunSections(policy, result, ctx, facts) {
|
|
196
|
+
const resolvedFacts = facts ?? deriveScanNarrative(result);
|
|
193
197
|
const sections = {};
|
|
194
198
|
sections.lead = buildLeadSection(result, policy);
|
|
195
|
-
const why = collectWhyBullets(result, policy.maxWhyBullets);
|
|
199
|
+
const why = collectWhyBullets(resolvedFacts, result, policy.maxWhyBullets);
|
|
196
200
|
if (why.length > 0) {
|
|
197
201
|
sections.why = { kind: "why", bullets: why };
|
|
198
202
|
}
|
|
199
|
-
const actions =
|
|
203
|
+
const actions = buildActionBulletsFromFacts(resolvedFacts, result, policy.maxActionBullets);
|
|
200
204
|
if (actions.length > 0) {
|
|
201
205
|
sections.actions = { kind: "actions", bullets: actions };
|
|
202
206
|
}
|
|
@@ -212,7 +216,7 @@ export function buildPrCheckRunSections(policy, result, ctx) {
|
|
|
212
216
|
sections.repoContext = {
|
|
213
217
|
kind: "repoContext",
|
|
214
218
|
score,
|
|
215
|
-
driverPhrases: collectWhyBullets(result, policy.maxRepoDriverPhrases),
|
|
219
|
+
driverPhrases: collectWhyBullets(resolvedFacts, result, policy.maxRepoDriverPhrases),
|
|
216
220
|
};
|
|
217
221
|
}
|
|
218
222
|
if (policy.showLayerScores) {
|
|
@@ -270,8 +274,8 @@ export function renderCheckRunMarkdown(sections) {
|
|
|
270
274
|
break;
|
|
271
275
|
}
|
|
272
276
|
case "repoContext": {
|
|
273
|
-
const drivers = section.driverPhrases.join("
|
|
274
|
-
parts.push(`${scanSurfaceCopy.checkRun.repoContextLabel} ${section.score}/100
|
|
277
|
+
const drivers = section.driverPhrases.join(" | ");
|
|
278
|
+
parts.push(normalizeGeneratedText(`${scanSurfaceCopy.checkRun.repoContextLabel} ${section.score}/100 - ${drivers}`));
|
|
275
279
|
parts.push("");
|
|
276
280
|
break;
|
|
277
281
|
}
|
|
@@ -280,7 +284,7 @@ export function renderCheckRunMarkdown(sections) {
|
|
|
280
284
|
parts.push(`<summary>${scanSurfaceCopy.checkRun.layerScoresDetailsSummary}</summary>`);
|
|
281
285
|
parts.push("");
|
|
282
286
|
for (const row of section.rows) {
|
|
283
|
-
parts.push(`- **${row.label}** ${row.score}/100
|
|
287
|
+
parts.push(normalizeGeneratedText(`- **${row.label}** ${row.score}/100 - ${row.driver}`));
|
|
284
288
|
}
|
|
285
289
|
parts.push("");
|
|
286
290
|
parts.push("</details>");
|
|
@@ -297,15 +301,16 @@ export function renderCheckRunMarkdown(sections) {
|
|
|
297
301
|
}
|
|
298
302
|
}
|
|
299
303
|
}
|
|
300
|
-
let md = parts.join("\n").trimEnd();
|
|
304
|
+
let md = normalizeGeneratedText(parts.join("\n").trimEnd());
|
|
301
305
|
if (md.length > CHECK_RUN_SOFT_MAX_CHARS) {
|
|
302
|
-
md = md.slice(0, CHECK_RUN_SOFT_MAX_CHARS - 1) + "
|
|
306
|
+
md = md.slice(0, CHECK_RUN_SOFT_MAX_CHARS - 1) + "...";
|
|
303
307
|
}
|
|
304
308
|
return md;
|
|
305
309
|
}
|
|
306
310
|
export function buildPrCheckRunSummaryMarkdown(ctx) {
|
|
307
|
-
const
|
|
308
|
-
const
|
|
311
|
+
const facts = deriveScanNarrative(ctx.result);
|
|
312
|
+
const policy = deriveCheckRunPolicy(ctx.result, { baseline: ctx.baseline }, facts);
|
|
313
|
+
const sections = buildPrCheckRunSections(policy, ctx.result, ctx, facts);
|
|
309
314
|
return renderCheckRunMarkdown(sections);
|
|
310
315
|
}
|
|
311
316
|
/** Whether baseline scan has no actionable PR bullets (for tests / callers). */
|
|
@@ -38,7 +38,7 @@ describe("buildPrCheckRunTitle", () => {
|
|
|
38
38
|
expect(buildPrCheckRunTitle()).not.toContain("—");
|
|
39
39
|
});
|
|
40
40
|
it("appends baseline suffix when requested", () => {
|
|
41
|
-
expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change
|
|
41
|
+
expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change - baseline scan only");
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
describe("formatScanDashboardUrl", () => {
|
|
@@ -271,7 +271,7 @@ describe("buildPrCheckRunSummaryMarkdown", () => {
|
|
|
271
271
|
baseline: false,
|
|
272
272
|
});
|
|
273
273
|
if (md.includes("Layer scores")) {
|
|
274
|
-
expect(md).toMatch(/Maintainability.*\/100
|
|
274
|
+
expect(md).toMatch(/Maintainability.*\/100 -/);
|
|
275
275
|
expect(md).not.toMatch(/\| Security \| 0 \|/);
|
|
276
276
|
}
|
|
277
277
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
2
|
+
import type { PRDecision, PRInsight, ScanResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* PR comment markdown from narrative facts (compressed dashboard story).
|
|
5
|
+
*/
|
|
6
|
+
export declare function presentGitHubPrComment(facts: ScanNarrativeFacts, result: ScanResult): string;
|
|
7
|
+
/** Backward-compatible entry: derives facts then presents. */
|
|
8
|
+
export declare function renderInsightsAsMarkdown(insights: PRInsight[], decision: PRDecision): string;
|
|
9
|
+
//# sourceMappingURL=presentGitHubPrComment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentGitHubPrComment.d.ts","sourceRoot":"","sources":["../src/presentGitHubPrComment.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoBpE;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,UAAU,GACjB,MAAM,CA6CR;AAED,8DAA8D;AAC9D,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,EAAE,EACrB,QAAQ,EAAE,UAAU,GACnB,MAAM,CAgBR"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
2
|
+
import { composeVerificationPrompt, formatChangedPackagesShort, formatUsageSummaryLine, selectReviewerGuidance, } from "./narrativePresentation.js";
|
|
3
|
+
import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
|
|
4
|
+
import { MERGE_POSTURE_LABEL, mergePostureFromDecision, } from "./riskVocabulary.js";
|
|
5
|
+
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
6
|
+
function renderGuidanceBlock(message, where, action) {
|
|
7
|
+
return [
|
|
8
|
+
normalizeGeneratedText(message),
|
|
9
|
+
"",
|
|
10
|
+
"**Where it shows up**",
|
|
11
|
+
"",
|
|
12
|
+
normalizeGeneratedText(where),
|
|
13
|
+
"",
|
|
14
|
+
"**What to do**",
|
|
15
|
+
"",
|
|
16
|
+
normalizeGeneratedText(action),
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* PR comment markdown from narrative facts (compressed dashboard story).
|
|
21
|
+
*/
|
|
22
|
+
export function presentGitHubPrComment(facts, result) {
|
|
23
|
+
const posture = facts.mergePosture ??
|
|
24
|
+
mergePostureFromDecision(result.decision?.recommendation);
|
|
25
|
+
const title = posture
|
|
26
|
+
? `**${MERGE_POSTURE_LABEL[posture]}**`
|
|
27
|
+
: `**${scanSurfaceCopy.checkRun.mergePostureUnavailable}**`;
|
|
28
|
+
const introLines = [];
|
|
29
|
+
const changed = formatChangedPackagesShort(facts, 3);
|
|
30
|
+
if (changed)
|
|
31
|
+
introLines.push(`Changed: ${changed}`);
|
|
32
|
+
const usage = formatUsageSummaryLine(facts, 1);
|
|
33
|
+
if (usage)
|
|
34
|
+
introLines.push(usage);
|
|
35
|
+
const verify = composeVerificationPrompt(facts);
|
|
36
|
+
if (verify)
|
|
37
|
+
introLines.push(`Verify: ${verify}`);
|
|
38
|
+
const guidance = selectReviewerGuidance(facts, { scope: "changed", max: 3 });
|
|
39
|
+
if (guidance.length === 0) {
|
|
40
|
+
const fallback = selectReviewerGuidance(facts, { max: 3 });
|
|
41
|
+
guidance.push(...fallback.slice(0, 3 - guidance.length));
|
|
42
|
+
}
|
|
43
|
+
const blocks = guidance.map((g) => {
|
|
44
|
+
const where = g.context?.trim() ||
|
|
45
|
+
facts.packageUsage
|
|
46
|
+
.flatMap((u) => u.paths.slice(0, 1))
|
|
47
|
+
.filter(Boolean)[0] ||
|
|
48
|
+
"See scan detail for affected paths.";
|
|
49
|
+
const action = g.remediation?.trim() ||
|
|
50
|
+
composeVerificationPrompt(facts) ||
|
|
51
|
+
"Review before merge.";
|
|
52
|
+
return renderGuidanceBlock(g.message, where, action);
|
|
53
|
+
});
|
|
54
|
+
const parts = [title];
|
|
55
|
+
if (introLines.length > 0) {
|
|
56
|
+
parts.push("", introLines.join("\n"));
|
|
57
|
+
}
|
|
58
|
+
if (blocks.length > 0) {
|
|
59
|
+
parts.push("", ...blocks);
|
|
60
|
+
}
|
|
61
|
+
return parts.join("\n\n---\n\n").trimEnd();
|
|
62
|
+
}
|
|
63
|
+
/** Backward-compatible entry: derives facts then presents. */
|
|
64
|
+
export function renderInsightsAsMarkdown(insights, decision) {
|
|
65
|
+
const result = {
|
|
66
|
+
totalScore: 0,
|
|
67
|
+
layerScores: {
|
|
68
|
+
security: 0,
|
|
69
|
+
maintainability: 0,
|
|
70
|
+
ecosystem: 0,
|
|
71
|
+
upgradeImpact: 0,
|
|
72
|
+
},
|
|
73
|
+
findings: [],
|
|
74
|
+
generatedAt: new Date().toISOString(),
|
|
75
|
+
insights,
|
|
76
|
+
decision,
|
|
77
|
+
};
|
|
78
|
+
const facts = deriveScanNarrative(result);
|
|
79
|
+
return presentGitHubPrComment(facts, result);
|
|
80
|
+
}
|