@mergesignal/shared 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/affectedAreaLinkage.d.ts +14 -0
- package/dist/affectedAreaLinkage.d.ts.map +1 -0
- package/dist/affectedAreaLinkage.js +100 -0
- package/dist/assessmentLabels.d.ts +1 -1
- package/dist/assessmentLabels.d.ts.map +1 -1
- package/dist/assessmentPresentationUtils.d.ts +6 -0
- package/dist/assessmentPresentationUtils.d.ts.map +1 -0
- package/dist/assessmentPresentationUtils.js +17 -0
- package/dist/assessmentProjection.d.ts +2 -2
- package/dist/assessmentProjection.d.ts.map +1 -1
- package/dist/assessmentProjection.js +42 -13
- package/dist/deriveScanNarrative.d.ts.map +1 -1
- package/dist/deriveScanNarrative.js +92 -9
- package/dist/deriveScanNarrative.test.js +74 -0
- package/dist/engineWireRegression.test.js +1 -1
- package/dist/fixtures/assessmentFixtures.d.ts +2 -1
- package/dist/fixtures/assessmentFixtures.d.ts.map +1 -1
- package/dist/fixtures/assessmentFixtures.js +78 -23
- package/dist/fixtures/assessmentScopeFixtures.d.ts +17 -0
- package/dist/fixtures/assessmentScopeFixtures.d.ts.map +1 -0
- package/dist/fixtures/assessmentScopeFixtures.js +42 -0
- package/dist/formatCardExposureDisplay.d.ts +5 -3
- package/dist/formatCardExposureDisplay.d.ts.map +1 -1
- package/dist/formatCardExposureDisplay.js +12 -5
- package/dist/formatCardExposureDisplay.test.js +9 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/presentation/buildGitHubCheckRunOutput.test.js +16 -41
- package/dist/presentation/compose/narrativeChannels.js +1 -1
- package/dist/presentation/dto/assessmentPresentationFields.d.ts +1 -2
- package/dist/presentation/dto/assessmentPresentationFields.d.ts.map +1 -1
- package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -1
- package/dist/presentation/fixtures/scanResultFixtures.js +19 -1
- package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -1
- package/dist/presentation/orchestration/buildScanPresentationBundle.js +12 -5
- package/dist/presentation/orchestration/buildScanPresentationBundle.test.d.ts +2 -0
- package/dist/presentation/orchestration/buildScanPresentationBundle.test.d.ts.map +1 -0
- package/dist/presentation/orchestration/buildScanPresentationBundle.test.js +99 -0
- package/dist/presentation/orchestration/scanPresentationBundle.d.ts +1 -1
- package/dist/presentation/orchestration/scanPresentationBundle.d.ts.map +1 -1
- package/dist/presentation/presentation.test.js +1 -1
- package/dist/presentation/presentationPersonas.test.js +0 -5
- package/dist/presentation/profile/buildProfileFromAssessment.d.ts +1 -1
- package/dist/presentation/profile/buildProfileFromAssessment.d.ts.map +1 -1
- package/dist/presentation/profile/buildProfileFromAssessment.js +13 -2
- package/dist/presentation/surfaceParity.test.js +73 -5
- package/dist/repoIntelligenceSchema.d.ts +3 -3
- package/dist/riskSignals.d.ts +19 -0
- package/dist/riskSignals.d.ts.map +1 -0
- package/dist/riskSignals.js +32 -0
- package/dist/scanNarrativeFacts.d.ts +25 -7
- package/dist/scanNarrativeFacts.d.ts.map +1 -1
- package/dist/scanNarrativeFacts.js +4 -1
- package/dist/scanPresentationBundle.golden.test.d.ts +2 -0
- package/dist/scanPresentationBundle.golden.test.d.ts.map +1 -0
- package/dist/scanPresentationBundle.golden.test.js +78 -0
- package/dist/scanResultSchema.d.ts +53 -4
- package/dist/scanResultSchema.d.ts.map +1 -1
- package/dist/scanResultSchema.js +1 -1
- package/dist/scanResultSchema.test.js +26 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/dist/assessmentSchema.d.ts +0 -155
- package/dist/assessmentSchema.d.ts.map +0 -1
- package/dist/assessmentSchema.js +0 -110
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AffectedAreaFact, ChangedPackageSemantics, NarrativePackageUsage, ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
2
|
+
import type { Finding } from "./types.js";
|
|
3
|
+
export declare function areaIdFromLabel(label: string): string;
|
|
4
|
+
export declare function enrichAffectedAreaFacts(areas: Array<{
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}>, ctx: {
|
|
8
|
+
findings: Finding[];
|
|
9
|
+
packageUsage: NarrativePackageUsage[];
|
|
10
|
+
changedPackageSemantics: ChangedPackageSemantics[];
|
|
11
|
+
hotspots: ScanNarrativeFacts["hotspots"];
|
|
12
|
+
applicationAreaIds: ReadonlySet<string>;
|
|
13
|
+
}): AffectedAreaFact[];
|
|
14
|
+
//# sourceMappingURL=affectedAreaLinkage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"affectedAreaLinkage.d.ts","sourceRoot":"","sources":["../src/affectedAreaLinkage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EAErB,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAQ1C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAuGD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EAC3C,GAAG,EAAE;IACH,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,qBAAqB,EAAE,CAAC;IACtC,uBAAuB,EAAE,uBAAuB,EAAE,CAAC;IACnD,QAAQ,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACzC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACzC,GACA,gBAAgB,EAAE,CAwBpB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const EVIDENCE_STRENGTH_RANK = {
|
|
2
|
+
high: 3,
|
|
3
|
+
medium: 2,
|
|
4
|
+
low: 1,
|
|
5
|
+
};
|
|
6
|
+
export function areaIdFromLabel(label) {
|
|
7
|
+
return label.toLowerCase().replace(/\s+/g, "_").slice(0, 48);
|
|
8
|
+
}
|
|
9
|
+
function packagesForArea(area, packageUsage, applicationAreaIds) {
|
|
10
|
+
const linked = new Set();
|
|
11
|
+
for (const row of packageUsage) {
|
|
12
|
+
for (const usageArea of row.areas) {
|
|
13
|
+
if (areaIdFromLabel(usageArea) === area.id ||
|
|
14
|
+
usageArea.toLowerCase() === area.label.toLowerCase()) {
|
|
15
|
+
linked.add(row.packageName);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (applicationAreaIds.has(area.id)) {
|
|
20
|
+
for (const row of packageUsage) {
|
|
21
|
+
linked.add(row.packageName);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...linked].sort((a, b) => a.localeCompare(b));
|
|
25
|
+
}
|
|
26
|
+
function findingIdsForPackages(findings, packages) {
|
|
27
|
+
if (packages.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const pkgSet = new Set(packages);
|
|
30
|
+
return findings.filter((f) => pkgSet.has(f.packageName)).map((f) => f.id);
|
|
31
|
+
}
|
|
32
|
+
function pathsForPackages(packageUsage, packages) {
|
|
33
|
+
const pkgSet = new Set(packages);
|
|
34
|
+
const paths = [];
|
|
35
|
+
for (const row of packageUsage) {
|
|
36
|
+
if (!pkgSet.has(row.packageName))
|
|
37
|
+
continue;
|
|
38
|
+
paths.push(...row.paths, ...row.criticalPaths, ...row.files);
|
|
39
|
+
}
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
return paths.filter((p) => {
|
|
42
|
+
if (!p || seen.has(p))
|
|
43
|
+
return false;
|
|
44
|
+
seen.add(p);
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function evidenceStrengthForPackages(semantics, packages) {
|
|
49
|
+
const pkgSet = new Set(packages);
|
|
50
|
+
let best = null;
|
|
51
|
+
let bestRank = 0;
|
|
52
|
+
for (const row of semantics) {
|
|
53
|
+
if (!pkgSet.has(row.packageName) || !row.evidenceStrength)
|
|
54
|
+
continue;
|
|
55
|
+
const rank = EVIDENCE_STRENGTH_RANK[row.evidenceStrength];
|
|
56
|
+
if (rank > bestRank) {
|
|
57
|
+
bestRank = rank;
|
|
58
|
+
best = row.evidenceStrength;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return best;
|
|
62
|
+
}
|
|
63
|
+
function hotspotPackagesForPackages(hotspots, packages) {
|
|
64
|
+
const pkgSet = new Set(packages);
|
|
65
|
+
const linked = new Set();
|
|
66
|
+
for (const hotspot of hotspots) {
|
|
67
|
+
if (pkgSet.has(hotspot.packageName)) {
|
|
68
|
+
linked.add(hotspot.packageName);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return [...linked].sort((a, b) => a.localeCompare(b));
|
|
72
|
+
}
|
|
73
|
+
function verificationFocusForPackages(semantics, packages) {
|
|
74
|
+
const pkgSet = new Set(packages);
|
|
75
|
+
const focus = new Set();
|
|
76
|
+
for (const row of semantics) {
|
|
77
|
+
if (!pkgSet.has(row.packageName))
|
|
78
|
+
continue;
|
|
79
|
+
for (const item of row.verificationFocus) {
|
|
80
|
+
if (item.trim())
|
|
81
|
+
focus.add(item.trim());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...focus].sort((a, b) => a.localeCompare(b));
|
|
85
|
+
}
|
|
86
|
+
export function enrichAffectedAreaFacts(areas, ctx) {
|
|
87
|
+
return areas.map((area) => {
|
|
88
|
+
const packages = packagesForArea(area, ctx.packageUsage, ctx.applicationAreaIds);
|
|
89
|
+
return {
|
|
90
|
+
id: area.id,
|
|
91
|
+
label: area.label,
|
|
92
|
+
packages,
|
|
93
|
+
findingIds: findingIdsForPackages(ctx.findings, packages),
|
|
94
|
+
paths: pathsForPackages(ctx.packageUsage, packages),
|
|
95
|
+
evidenceStrength: evidenceStrengthForPackages(ctx.changedPackageSemantics, packages),
|
|
96
|
+
hotspotPackages: hotspotPackagesForPackages(ctx.hotspots, packages),
|
|
97
|
+
verificationFocus: verificationFocusForPackages(ctx.changedPackageSemantics, packages),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MergeConcernKind } from "
|
|
1
|
+
import type { MergeConcernKind } from "@mergesignal/contracts";
|
|
2
2
|
/** Display labels for factor tokens (mirrors engine factor-registry labels). */
|
|
3
3
|
export declare const ASSESSMENT_FACTOR_LABELS: Record<string, string>;
|
|
4
4
|
export declare const MERGE_CONCERN_LABELS: Record<MergeConcernKind, string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assessmentLabels.d.ts","sourceRoot":"","sources":["../src/assessmentLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"assessmentLabels.d.ts","sourceRoot":"","sources":["../src/assessmentLabels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB3D,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAcjE,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AssessmentPresentationPublic, AssessmentPresentationWire, NarrativeIntensity } from "@mergesignal/contracts";
|
|
2
|
+
/** Public presentation subset — excludes engine policy fields. */
|
|
3
|
+
export declare function toPublicPresentation(wire: AssessmentPresentationWire): AssessmentPresentationPublic;
|
|
4
|
+
/** Map engine narrative intensity to presentation density (formatting only). */
|
|
5
|
+
export declare function narrativeIntensityToDensity(intensity: NarrativeIntensity): "minimal" | "standard" | "rich";
|
|
6
|
+
//# sourceMappingURL=assessmentPresentationUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assessmentPresentationUtils.d.ts","sourceRoot":"","sources":["../src/assessmentPresentationUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EACnB,MAAM,wBAAwB,CAAC;AAEhC,kEAAkE;AAClE,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,0BAA0B,GAC/B,4BAA4B,CAO9B;AAED,gFAAgF;AAChF,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,kBAAkB,GAC5B,SAAS,GAAG,UAAU,GAAG,MAAM,CAIjC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Public presentation subset — excludes engine policy fields. */
|
|
2
|
+
export function toPublicPresentation(wire) {
|
|
3
|
+
return {
|
|
4
|
+
narrativeIntensity: wire.narrativeIntensity,
|
|
5
|
+
reachVisibility: wire.reachVisibility,
|
|
6
|
+
verificationIntensity: wire.verificationIntensity,
|
|
7
|
+
reportMode: wire.reportMode,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Map engine narrative intensity to presentation density (formatting only). */
|
|
11
|
+
export function narrativeIntensityToDensity(intensity) {
|
|
12
|
+
if (intensity === "minimal")
|
|
13
|
+
return "minimal";
|
|
14
|
+
if (intensity === "elevated")
|
|
15
|
+
return "rich";
|
|
16
|
+
return "standard";
|
|
17
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ScanResult } from "./types.js";
|
|
2
2
|
import { type RepoIntelligence } from "./repoIntelligenceSchema.js";
|
|
3
|
-
import type { Assessment, AssessmentPresentationPublic, ReachVisibility } from "
|
|
3
|
+
import type { Assessment, AssessmentPresentationPublic, ReachVisibility } from "@mergesignal/contracts";
|
|
4
4
|
import type { PresentationStatus } from "./presentation/dto/types.js";
|
|
5
5
|
export declare function collectVerificationFocus(result: ScanResult): string[];
|
|
6
6
|
export declare function reachVisibilityLabel(visibility: ReachVisibility): string | undefined;
|
|
7
|
-
export declare function formatAssessmentHeadline(assessment: Assessment,
|
|
7
|
+
export declare function formatAssessmentHeadline(assessment: Assessment, status: PresentationStatus): string;
|
|
8
8
|
export declare function projectReasoningLines(result: ScanResult): string[];
|
|
9
9
|
export declare function projectInsightLines(assessment: Assessment, result: ScanResult): string[];
|
|
10
10
|
export declare function projectVerificationActions(assessment: Assessment, presentation: AssessmentPresentationPublic, result: ScanResult, max: number): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assessmentProjection.d.ts","sourceRoot":"","sources":["../src/assessmentProjection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC5B,eAAe,EAChB,MAAM,
|
|
1
|
+
{"version":3,"file":"assessmentProjection.d.ts","sourceRoot":"","sources":["../src/assessmentProjection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC5B,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAgCtE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAkBrE;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,eAAe,GAC1B,MAAM,GAAG,SAAS,CAWpB;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,kBAAkB,GACzB,MAAM,CAiDR;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAIlE;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,UAAU,GACjB,MAAM,EAAE,CAaV;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,4BAA4B,EAC1C,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,EAAE,CAIV;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI,GAAG,MAAM,EAAE,CAI3E"}
|
|
@@ -2,14 +2,47 @@ import { safeParseRepoIntelligence, } from "./repoIntelligenceSchema.js";
|
|
|
2
2
|
import { labelAssessmentFactor, MERGE_CONCERN_LABELS, } from "./assessmentLabels.js";
|
|
3
3
|
import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
|
|
4
4
|
import { applyPostureVocabularyGuardHeadline } from "./presentation/intent/applyPostureVocabularyGuard.js";
|
|
5
|
+
function focalDisplayPackage(assessment) {
|
|
6
|
+
const anchors = assessment.reviewFocalPoint.anchors;
|
|
7
|
+
if (anchors.length === 0)
|
|
8
|
+
return "dependency";
|
|
9
|
+
const token = anchors[0];
|
|
10
|
+
if (token === "dependency_graph")
|
|
11
|
+
return "dependencies";
|
|
12
|
+
if (token.includes("+"))
|
|
13
|
+
return token.split("+")[0];
|
|
14
|
+
return token;
|
|
15
|
+
}
|
|
16
|
+
function focalHeadlinePackages(assessment) {
|
|
17
|
+
const focal = assessment.reviewFocalPoint;
|
|
18
|
+
if (focal.episodeShape === "multi_anchor") {
|
|
19
|
+
return focal.anchors.join(", ");
|
|
20
|
+
}
|
|
21
|
+
return focalDisplayPackage(assessment);
|
|
22
|
+
}
|
|
23
|
+
/** Copy-formatting only — fills {surface} in runtime headline template. */
|
|
24
|
+
function surfaceHintFromAssessment(assessment) {
|
|
25
|
+
const factors = assessment.factors;
|
|
26
|
+
const anchor = focalDisplayPackage(assessment);
|
|
27
|
+
if (anchor === "bullmq" || factors.includes("queue_infrastructure")) {
|
|
28
|
+
return "workers and queues";
|
|
29
|
+
}
|
|
30
|
+
if (anchor === "next-auth" || factors.includes("auth_infrastructure")) {
|
|
31
|
+
return "authentication flows";
|
|
32
|
+
}
|
|
33
|
+
return "application code";
|
|
34
|
+
}
|
|
5
35
|
export function collectVerificationFocus(result) {
|
|
36
|
+
const scope = result.assessment?.verificationScope;
|
|
37
|
+
if (scope?.focus?.length)
|
|
38
|
+
return [...scope.focus];
|
|
39
|
+
const packages = scope?.packages ?? [];
|
|
6
40
|
const parsed = safeParseRepoIntelligence(result.repoIntelligence);
|
|
7
41
|
if (!parsed.ok)
|
|
8
42
|
return [];
|
|
9
|
-
const changed = new Set(result.changedPackages ?? []);
|
|
10
43
|
const labels = [];
|
|
11
44
|
const seen = new Set();
|
|
12
|
-
for (const pkg of
|
|
45
|
+
for (const pkg of packages) {
|
|
13
46
|
const row = parsed.value.packages[pkg];
|
|
14
47
|
for (const focus of row?.verificationFocus ?? []) {
|
|
15
48
|
const t = focus.trim();
|
|
@@ -33,13 +66,13 @@ export function reachVisibilityLabel(visibility) {
|
|
|
33
66
|
return undefined;
|
|
34
67
|
}
|
|
35
68
|
}
|
|
36
|
-
export function formatAssessmentHeadline(assessment,
|
|
69
|
+
export function formatAssessmentHeadline(assessment, status) {
|
|
37
70
|
const copy = scanSurfaceCopy.presentation;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
71
|
+
const primaryPkg = focalHeadlinePackages(assessment);
|
|
72
|
+
if (assessment.reviewFocalPoint.episodeShape === "structural") {
|
|
73
|
+
const headline = copy.defaultUpgradeHeadline.replace("{package}", "dependencies");
|
|
74
|
+
return applyPostureVocabularyGuardHeadline(headline, status, null);
|
|
75
|
+
}
|
|
43
76
|
if (assessment.primaryConcern === "insufficient_evidence") {
|
|
44
77
|
return applyPostureVocabularyGuardHeadline(copy.limitedContextHeadline, status, primaryPkg);
|
|
45
78
|
}
|
|
@@ -50,11 +83,7 @@ export function formatAssessmentHeadline(assessment, result, status) {
|
|
|
50
83
|
return applyPostureVocabularyGuardHeadline(headline, status, primaryPkg);
|
|
51
84
|
}
|
|
52
85
|
if (assessment.primaryConcern === "confirmed_runtime_usage") {
|
|
53
|
-
const surface = assessment
|
|
54
|
-
? "workers and queues"
|
|
55
|
-
: assessment.factors.includes("auth_infrastructure")
|
|
56
|
-
? "authentication flows"
|
|
57
|
-
: "application code";
|
|
86
|
+
const surface = surfaceHintFromAssessment(assessment);
|
|
58
87
|
const headline = copy.runtimeUpgradeHeadline
|
|
59
88
|
.replace("{package}", primaryPkg)
|
|
60
89
|
.replace("{surface}", surface);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"AAYA,OAAO,EAgBL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAMV,UAAU,EACX,MAAM,YAAY,CAAC;AAoiBpB;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACpC,kBAAkB,CAsIpB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { extractRepositoryContextFacts } from "./extractRepositoryContextFacts.js";
|
|
2
|
+
import { areaIdFromLabel, enrichAffectedAreaFacts, } from "./affectedAreaLinkage.js";
|
|
2
3
|
import { readBlastLevel, safeParseRepoIntelligence, } from "./repoIntelligenceSchema.js";
|
|
3
4
|
import { isRuntimeNarrativeSafe } from "./repoIntelligenceSemantics.js";
|
|
5
|
+
import { deriveRiskSignals } from "./riskSignals.js";
|
|
4
6
|
import { EMPTY_SCAN_NARRATIVE_FACTS, NARRATIVE_FACT_MAX_AREAS, NARRATIVE_FACT_MAX_HOTSPOTS, NARRATIVE_FACT_MAX_PATHS, } from "./scanNarrativeFacts.js";
|
|
5
7
|
import { mergePostureFromDecision } from "./riskVocabulary.js";
|
|
6
8
|
import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
|
|
@@ -14,6 +16,25 @@ function parseChangedPackages(changed) {
|
|
|
14
16
|
all,
|
|
15
17
|
};
|
|
16
18
|
}
|
|
19
|
+
function changedPackagesFromAssessment(result) {
|
|
20
|
+
const assessment = result.assessment;
|
|
21
|
+
const focal = assessment?.reviewFocalPoint;
|
|
22
|
+
// Follow-up: remove parseChangedPackages fallback after historical scan re-scan window.
|
|
23
|
+
if (focal?.anchors?.length && focal.anchors[0] !== "dependency_graph") {
|
|
24
|
+
const token = focal.anchors[0];
|
|
25
|
+
const primary = token.includes("+") ? token.split("+")[0] : token;
|
|
26
|
+
const supporting = focal.supportingPackages ?? [];
|
|
27
|
+
const all = [
|
|
28
|
+
...new Set([primary, ...supporting, ...(result.changedPackages ?? [])]),
|
|
29
|
+
].sort((a, b) => a.localeCompare(b));
|
|
30
|
+
return {
|
|
31
|
+
primary,
|
|
32
|
+
others: all.filter((p) => p !== primary),
|
|
33
|
+
all,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return parseChangedPackages(result.changedPackages);
|
|
37
|
+
}
|
|
17
38
|
function blastLevelFromChangedCount(count) {
|
|
18
39
|
if (count >= 8)
|
|
19
40
|
return "wide";
|
|
@@ -205,13 +226,15 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
205
226
|
}
|
|
206
227
|
: null;
|
|
207
228
|
const areasRaw = ri.applicationAreas ?? ri.affectedAreas ?? [];
|
|
229
|
+
const applicationAreaIds = new Set();
|
|
208
230
|
const affectedAreas = [];
|
|
209
231
|
for (const area of areasRaw) {
|
|
232
|
+
applicationAreaIds.add(area.id);
|
|
210
233
|
affectedAreas.push({ id: area.id, label: area.label });
|
|
211
234
|
}
|
|
212
235
|
for (const row of packageUsage) {
|
|
213
236
|
for (const label of row.areas) {
|
|
214
|
-
const id = label
|
|
237
|
+
const id = areaIdFromLabel(label);
|
|
215
238
|
if (!affectedAreas.some((a) => a.id === id)) {
|
|
216
239
|
affectedAreas.push({ id, label });
|
|
217
240
|
}
|
|
@@ -228,6 +251,7 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
228
251
|
blastRadius,
|
|
229
252
|
affectedAreas: affectedAreas.slice(0, NARRATIVE_FACT_MAX_AREAS),
|
|
230
253
|
hotspots: codeHotspots.slice(0, NARRATIVE_FACT_MAX_HOTSPOTS),
|
|
254
|
+
applicationAreaIds,
|
|
231
255
|
};
|
|
232
256
|
}
|
|
233
257
|
function hasMeaningfulTier1(tier1) {
|
|
@@ -322,6 +346,7 @@ function emptyTier1Blocks() {
|
|
|
322
346
|
blastRadius: null,
|
|
323
347
|
affectedAreas: [],
|
|
324
348
|
hotspots: [],
|
|
349
|
+
applicationAreaIds: new Set(),
|
|
325
350
|
};
|
|
326
351
|
}
|
|
327
352
|
function blastRadiusFromChangedCount(count) {
|
|
@@ -349,6 +374,38 @@ function resolveTrustedRepoIntelligence(result) {
|
|
|
349
374
|
}
|
|
350
375
|
return { parsed: null, parseStatus: "invalid" };
|
|
351
376
|
}
|
|
377
|
+
function preparationWarningsFromResult(result) {
|
|
378
|
+
const warnings = result.analysisPreparation?.warnings;
|
|
379
|
+
if (!Array.isArray(warnings))
|
|
380
|
+
return [];
|
|
381
|
+
return warnings.map((w) => ({
|
|
382
|
+
code: w.code,
|
|
383
|
+
message: w.message,
|
|
384
|
+
...(w.details != null ? { details: w.details } : {}),
|
|
385
|
+
}));
|
|
386
|
+
}
|
|
387
|
+
function resolveCorpusGateReason(input) {
|
|
388
|
+
if (input.corpusGate)
|
|
389
|
+
return "ok";
|
|
390
|
+
if (!input.codeIntelligenceAvailable)
|
|
391
|
+
return "no_code_intelligence";
|
|
392
|
+
if (input.repoIntelligenceParse === "invalid" ||
|
|
393
|
+
input.repoIntelligenceParse === "untrusted") {
|
|
394
|
+
return "repo_intelligence_invalid";
|
|
395
|
+
}
|
|
396
|
+
return "repo_intelligence_absent";
|
|
397
|
+
}
|
|
398
|
+
function assessmentConfidenceFromResult(result) {
|
|
399
|
+
const c = result.assessment?.confidence;
|
|
400
|
+
if (c === "low" || c === "medium" || c === "high")
|
|
401
|
+
return c;
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
function limitedContextDiagnostic(input) {
|
|
405
|
+
return (input.corpusGateReason !== "ok" ||
|
|
406
|
+
input.preparationWarnings.length > 0 ||
|
|
407
|
+
input.assessmentConfidence === "low");
|
|
408
|
+
}
|
|
352
409
|
/**
|
|
353
410
|
* Consumer-agnostic scan intelligence — structured facts only (no UI copy).
|
|
354
411
|
*/
|
|
@@ -356,25 +413,31 @@ export function deriveScanNarrative(result) {
|
|
|
356
413
|
if (!result) {
|
|
357
414
|
return { ...EMPTY_SCAN_NARRATIVE_FACTS };
|
|
358
415
|
}
|
|
359
|
-
const changedPackages =
|
|
416
|
+
const changedPackages = changedPackagesFromAssessment(result);
|
|
360
417
|
const mergePosture = mergePostureFromDecision(result.decision?.recommendation) ?? null;
|
|
361
|
-
const
|
|
362
|
-
? result.totalScore
|
|
363
|
-
: null;
|
|
418
|
+
const preparationWarnings = preparationWarningsFromResult(result);
|
|
364
419
|
const codeIntelligenceAvailable = result.analysisPreparation?.codeIntelligenceAvailable !== false;
|
|
365
420
|
const { parsed: parsedRi, parseStatus: repoIntelligenceParse } = resolveTrustedRepoIntelligence(result);
|
|
366
421
|
const corpusGate = codeIntelligenceAvailable &&
|
|
367
422
|
repoIntelligenceParse === "ok" &&
|
|
368
423
|
parsedRi != null;
|
|
424
|
+
const corpusGateReason = resolveCorpusGateReason({
|
|
425
|
+
codeIntelligenceAvailable,
|
|
426
|
+
repoIntelligenceParse,
|
|
427
|
+
corpusGate,
|
|
428
|
+
});
|
|
369
429
|
let tier1Blocks = emptyTier1Blocks();
|
|
430
|
+
let applicationAreaIds = tier1Blocks.applicationAreaIds;
|
|
370
431
|
if (corpusGate && parsedRi) {
|
|
371
432
|
tier1Blocks = extractTier1FromRepoIntelligence(parsedRi, changedPackages);
|
|
433
|
+
applicationAreaIds = tier1Blocks.applicationAreaIds;
|
|
372
434
|
}
|
|
373
435
|
else if (changedPackages.all.length > 0) {
|
|
374
436
|
tier1Blocks = {
|
|
375
437
|
...emptyTier1Blocks(),
|
|
376
438
|
blastRadius: blastRadiusFromChangedCount(changedPackages.all.length),
|
|
377
439
|
};
|
|
440
|
+
applicationAreaIds = tier1Blocks.applicationAreaIds;
|
|
378
441
|
}
|
|
379
442
|
tier1Blocks.hotspots = mergeGraphHotspots(tier1Blocks.hotspots, result.graphInsights?.hotspots);
|
|
380
443
|
const tier1 = corpusGate && hasMeaningfulTier1(tier1Blocks);
|
|
@@ -384,23 +447,40 @@ export function deriveScanNarrative(result) {
|
|
|
384
447
|
const tier3 = repositoryContext.length > 0;
|
|
385
448
|
const mode = resolveAvailabilityMode(tier1, tier2 || reviewerGuidance.length > 0, codeIntelligenceAvailable);
|
|
386
449
|
const decisionConf = result.decision?.confidence;
|
|
450
|
+
const assessmentConfidence = assessmentConfidenceFromResult(result);
|
|
387
451
|
const confidence = {
|
|
388
452
|
decision: decisionConf === "low" ||
|
|
389
453
|
decisionConf === "medium" ||
|
|
390
454
|
decisionConf === "high"
|
|
391
455
|
? decisionConf
|
|
392
456
|
: null,
|
|
457
|
+
assessment: assessmentConfidence,
|
|
458
|
+
limitedContext: limitedContextDiagnostic({
|
|
459
|
+
corpusGateReason,
|
|
460
|
+
preparationWarnings,
|
|
461
|
+
assessmentConfidence,
|
|
462
|
+
}),
|
|
393
463
|
};
|
|
394
464
|
if (!tier1 && mode === "graph_fallback" && changedPackages.all.length > 0) {
|
|
395
465
|
tier1Blocks.blastRadius = blastRadiusFromChangedCount(changedPackages.all.length);
|
|
396
466
|
}
|
|
397
|
-
let
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
id: label
|
|
467
|
+
let affectedAreaSeeds = tier1Blocks.affectedAreas;
|
|
468
|
+
if (affectedAreaSeeds.length === 0 && mode !== "pr_intelligence") {
|
|
469
|
+
affectedAreaSeeds = selectTopAffectedAreas(result, { max: 2 }).map((label) => ({
|
|
470
|
+
id: areaIdFromLabel(label),
|
|
401
471
|
label,
|
|
402
472
|
}));
|
|
403
473
|
}
|
|
474
|
+
const findings = Array.isArray(result.findings) ? result.findings : [];
|
|
475
|
+
const affectedAreas = enrichAffectedAreaFacts(affectedAreaSeeds, {
|
|
476
|
+
findings,
|
|
477
|
+
packageUsage: tier1Blocks.packageUsage,
|
|
478
|
+
changedPackageSemantics: tier1Blocks.changedPackageSemantics,
|
|
479
|
+
hotspots: tier1Blocks.hotspots,
|
|
480
|
+
applicationAreaIds,
|
|
481
|
+
});
|
|
482
|
+
const riskSignals = deriveRiskSignals(result);
|
|
483
|
+
const riskIndex = riskSignals?.riskIndex ?? null;
|
|
404
484
|
return {
|
|
405
485
|
availability: {
|
|
406
486
|
mode,
|
|
@@ -411,6 +491,8 @@ export function deriveScanNarrative(result) {
|
|
|
411
491
|
tier3,
|
|
412
492
|
},
|
|
413
493
|
repoIntelligenceParse,
|
|
494
|
+
preparationWarnings,
|
|
495
|
+
corpusGateReason,
|
|
414
496
|
},
|
|
415
497
|
changedPackages,
|
|
416
498
|
packageUsage: tier1Blocks.packageUsage,
|
|
@@ -427,5 +509,6 @@ export function deriveScanNarrative(result) {
|
|
|
427
509
|
repositoryContext,
|
|
428
510
|
mergePosture,
|
|
429
511
|
riskIndex,
|
|
512
|
+
riskSignals,
|
|
430
513
|
};
|
|
431
514
|
}
|
|
@@ -3,6 +3,7 @@ import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
|
3
3
|
import { legacyEngineRepoIntelligenceJwt } from "./fixtures/legacyEngineRepoIntelligence.js";
|
|
4
4
|
import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceMultiPackage, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.js";
|
|
5
5
|
import { analysisPreparationWithInvalidRepoIntel, analysisPreparationWithValidRepoIntel, } from "./fixtures/repoIntelligenceTestHelpers.js";
|
|
6
|
+
import { assessmentFastifyRuntime } from "./fixtures/assessmentFixtures.js";
|
|
6
7
|
const baseResult = {
|
|
7
8
|
totalScore: 10,
|
|
8
9
|
layerScores: {
|
|
@@ -64,6 +65,18 @@ describe("deriveScanNarrative", () => {
|
|
|
64
65
|
expect(facts.affectedAreas.map((a) => a.id)).toContain("api");
|
|
65
66
|
expect(facts.hotspots[0]?.source).toBe("code");
|
|
66
67
|
});
|
|
68
|
+
it("changedPackages.primary follows reviewFocalPoint anchor, not changedPackages order", () => {
|
|
69
|
+
const result = {
|
|
70
|
+
...baseResult,
|
|
71
|
+
changedPackages: ["typescript", "fastify"],
|
|
72
|
+
assessment: assessmentFastifyRuntime,
|
|
73
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
74
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
75
|
+
};
|
|
76
|
+
const facts = deriveScanNarrative(result);
|
|
77
|
+
expect(facts.changedPackages.primary).toBe("fastify");
|
|
78
|
+
expect(result.changedPackages?.[0]).toBe("typescript");
|
|
79
|
+
});
|
|
67
80
|
it("prefers changed-scope insights for reviewer guidance ordering", () => {
|
|
68
81
|
const result = {
|
|
69
82
|
...baseResult,
|
|
@@ -197,4 +210,65 @@ describe("deriveScanNarrative", () => {
|
|
|
197
210
|
expect(facts.availability.repoIntelligenceParse).toBe("absent");
|
|
198
211
|
expect(facts.availability.tiersPresent.tier1).toBe(false);
|
|
199
212
|
});
|
|
213
|
+
it("projects preparation warnings and corpus gate reason", () => {
|
|
214
|
+
const result = {
|
|
215
|
+
...baseResult,
|
|
216
|
+
changedPackages: ["lodash"],
|
|
217
|
+
analysisPreparation: {
|
|
218
|
+
codeIntelligenceAvailable: false,
|
|
219
|
+
warnings: [
|
|
220
|
+
{
|
|
221
|
+
code: "base_lockfile_missing",
|
|
222
|
+
message: "Base lockfile not available",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
const facts = deriveScanNarrative(result);
|
|
228
|
+
expect(facts.availability.preparationWarnings).toHaveLength(1);
|
|
229
|
+
expect(facts.availability.preparationWarnings[0]?.code).toBe("base_lockfile_missing");
|
|
230
|
+
expect(facts.availability.corpusGateReason).toBe("no_code_intelligence");
|
|
231
|
+
expect(facts.confidence.limitedContext).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
it("links affected areas to packages, findings, paths, and verification", () => {
|
|
234
|
+
const result = {
|
|
235
|
+
...baseResult,
|
|
236
|
+
changedPackages: ["fastify"],
|
|
237
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
238
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
239
|
+
findings: [
|
|
240
|
+
{
|
|
241
|
+
id: "f1",
|
|
242
|
+
title: "Auth middleware",
|
|
243
|
+
description: "d",
|
|
244
|
+
severity: "high",
|
|
245
|
+
packageName: "fastify",
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
const facts = deriveScanNarrative(result);
|
|
250
|
+
const apiArea = facts.affectedAreas.find((a) => a.id === "api");
|
|
251
|
+
expect(apiArea).toBeDefined();
|
|
252
|
+
expect(apiArea?.packages).toContain("fastify");
|
|
253
|
+
expect(apiArea?.findingIds).toContain("f1");
|
|
254
|
+
expect(apiArea?.paths.length).toBeGreaterThan(0);
|
|
255
|
+
expect(apiArea?.evidenceStrength).toBe("high");
|
|
256
|
+
expect(apiArea?.hotspotPackages).toContain("fastify");
|
|
257
|
+
expect(apiArea?.verificationFocus).toContain("routes");
|
|
258
|
+
});
|
|
259
|
+
it("derives riskSignals with exposure layers", () => {
|
|
260
|
+
const facts = deriveScanNarrative(baseResult);
|
|
261
|
+
expect(facts.riskSignals?.riskIndex).toBe(10);
|
|
262
|
+
expect(facts.riskSignals?.exposure).toBe("minimal");
|
|
263
|
+
expect(facts.riskSignals?.layers).toHaveLength(4);
|
|
264
|
+
expect(facts.riskIndex).toBe(10);
|
|
265
|
+
});
|
|
266
|
+
it("projects assessment confidence verbatim", () => {
|
|
267
|
+
const result = {
|
|
268
|
+
...baseResult,
|
|
269
|
+
assessment: assessmentFastifyRuntime,
|
|
270
|
+
};
|
|
271
|
+
const facts = deriveScanNarrative(result);
|
|
272
|
+
expect(facts.confidence.assessment).toBe("medium");
|
|
273
|
+
});
|
|
200
274
|
});
|
|
@@ -70,8 +70,8 @@ describe("engine wire regression (canonical fixtures)", () => {
|
|
|
70
70
|
const facts = deriveScanNarrative(result);
|
|
71
71
|
expect(facts.availability.mode).toBe("pr_intelligence");
|
|
72
72
|
expect(facts.packageUsage.map((u) => u.packageName)).toEqual([
|
|
73
|
-
"lodash",
|
|
74
73
|
"axios",
|
|
74
|
+
"lodash",
|
|
75
75
|
]);
|
|
76
76
|
});
|
|
77
77
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Assessment } from "
|
|
1
|
+
import type { Assessment } from "@mergesignal/contracts";
|
|
2
2
|
export declare const assessmentTypescriptPatch: Assessment;
|
|
3
3
|
export declare const assessmentPrettier: Assessment;
|
|
4
4
|
export declare const assessmentEslint: Assessment;
|
|
@@ -6,6 +6,7 @@ export declare const assessmentVitest: Assessment;
|
|
|
6
6
|
export declare const assessmentFastifyRuntime: Assessment;
|
|
7
7
|
export declare const assessmentBullmq: Assessment;
|
|
8
8
|
export declare const assessmentNextAuth: Assessment;
|
|
9
|
+
/** PR #26 — focal fastify despite typescript in changedPackages order. */
|
|
9
10
|
export declare const assessmentMixedTypescriptFastify: Assessment;
|
|
10
11
|
export declare const assessmentLimitedContext: Assessment;
|
|
11
12
|
export declare const assessmentStub: Assessment;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assessmentFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/assessmentFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"assessmentFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/assessmentFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA0BzD,eAAO,MAAM,yBAAyB,EAAE,UAevC,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,UAUhC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAS9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAe9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAyBtC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UA6B9B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,UAyBhC,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,gCAAgC,EAAE,UAgC9C,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UA2BtC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UA6B5B,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,UAqBnC,CAAC"}
|