@mergesignal/shared 0.5.0 → 0.7.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/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 +20 -1
- package/dist/deriveScanNarrative.test.js +13 -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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/presentation/buildGitHubCheckRunOutput.d.ts +20 -0
- package/dist/presentation/buildGitHubCheckRunOutput.d.ts.map +1 -0
- package/dist/presentation/buildGitHubCheckRunOutput.js +42 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts +2 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.d.ts.map +1 -0
- package/dist/presentation/buildGitHubCheckRunOutput.test.js +163 -0
- 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/index.d.ts +2 -1
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +2 -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/presenters/presentDashboardCard.d.ts +1 -0
- package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -1
- package/dist/presentation/presenters/presentDashboardCard.js +16 -0
- 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/scanCardPresentationState.d.ts +8 -2
- package/dist/scanCardPresentationState.d.ts.map +1 -1
- 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/scanSurfaceCopy.d.ts +2 -0
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +2 -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,163 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { assessmentFastifyRuntime, assessmentTypescriptPatch, } from "../fixtures/assessmentFixtures.js";
|
|
3
|
+
import { withAssessmentScope, minimalReviewFocalPoint, emptyReachScope, emptyVerificationScope, reachScopeFor, } from "../fixtures/assessmentScopeFixtures.js";
|
|
4
|
+
import { buildScanPresentationBundle, presentDashboardCard } from "./index.js";
|
|
5
|
+
import { buildGitHubCheckRunOutput } from "./buildGitHubCheckRunOutput.js";
|
|
6
|
+
const safeAssessment = assessmentTypescriptPatch;
|
|
7
|
+
const baselineAssessment = withAssessmentScope({
|
|
8
|
+
posture: "safe",
|
|
9
|
+
confidence: "medium",
|
|
10
|
+
primaryConcern: "graph_baseline_only",
|
|
11
|
+
concerns: [
|
|
12
|
+
{
|
|
13
|
+
kind: "graph_baseline_only",
|
|
14
|
+
rank: 1,
|
|
15
|
+
evidenceRefs: ["fixture:baseline"],
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
factors: ["graph_baseline_only"],
|
|
19
|
+
changeClasses: ["tooling_maintenance"],
|
|
20
|
+
presentation: {
|
|
21
|
+
narrativeIntensity: "minimal",
|
|
22
|
+
reachVisibility: "hidden",
|
|
23
|
+
verificationIntensity: "none",
|
|
24
|
+
insightEmissionFloor: "none",
|
|
25
|
+
reportMode: "lightweight_pr_graph_baseline",
|
|
26
|
+
},
|
|
27
|
+
}, {
|
|
28
|
+
reviewFocalPoint: minimalReviewFocalPoint(["@types/minimist"]),
|
|
29
|
+
reachScope: emptyReachScope(),
|
|
30
|
+
verificationScope: emptyVerificationScope(),
|
|
31
|
+
});
|
|
32
|
+
const needsReviewAssessment = assessmentFastifyRuntime;
|
|
33
|
+
const riskyAssessment = withAssessmentScope({
|
|
34
|
+
posture: "risky",
|
|
35
|
+
confidence: "high",
|
|
36
|
+
primaryConcern: "breaking_or_major",
|
|
37
|
+
concerns: [
|
|
38
|
+
{
|
|
39
|
+
kind: "breaking_or_major",
|
|
40
|
+
rank: 1,
|
|
41
|
+
packages: ["react"],
|
|
42
|
+
evidenceRefs: ["fixture:breaking"],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
factors: ["breaking_or_major"],
|
|
46
|
+
changeClasses: ["breaking_change"],
|
|
47
|
+
presentation: {
|
|
48
|
+
narrativeIntensity: "elevated",
|
|
49
|
+
reachVisibility: "prominent",
|
|
50
|
+
verificationIntensity: "required",
|
|
51
|
+
insightEmissionFloor: "full",
|
|
52
|
+
reportMode: "high_signal_pr",
|
|
53
|
+
},
|
|
54
|
+
}, {
|
|
55
|
+
reviewFocalPoint: minimalReviewFocalPoint(["react"]),
|
|
56
|
+
reachScope: reachScopeFor(["react"], "high"),
|
|
57
|
+
verificationScope: emptyVerificationScope(),
|
|
58
|
+
});
|
|
59
|
+
function assertNoLegacyCheckRunMarkers(summary) {
|
|
60
|
+
expect(summary).not.toContain("Risk index");
|
|
61
|
+
expect(summary).not.toContain("Layer scores");
|
|
62
|
+
expect(summary).not.toContain("<details>");
|
|
63
|
+
expect(summary).not.toMatch(/\d+\/100/);
|
|
64
|
+
}
|
|
65
|
+
function minimalScanResult(overrides = {}) {
|
|
66
|
+
return {
|
|
67
|
+
totalScore: 72,
|
|
68
|
+
layerScores: {
|
|
69
|
+
security: 10,
|
|
70
|
+
maintainability: 20,
|
|
71
|
+
ecosystem: 30,
|
|
72
|
+
upgradeImpact: 15,
|
|
73
|
+
},
|
|
74
|
+
findings: [],
|
|
75
|
+
methodologyVersion: "mergesignal-engine/test",
|
|
76
|
+
generatedAt: new Date().toISOString(),
|
|
77
|
+
assessment: safeAssessment,
|
|
78
|
+
decision: {
|
|
79
|
+
recommendation: "safe",
|
|
80
|
+
confidence: "medium",
|
|
81
|
+
reasoning: [],
|
|
82
|
+
},
|
|
83
|
+
...overrides,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
describe("buildGitHubCheckRunOutput", () => {
|
|
87
|
+
it("maps safe posture to success conclusion with scan link", () => {
|
|
88
|
+
const out = buildGitHubCheckRunOutput(minimalScanResult(), {
|
|
89
|
+
scanId: "scan-safe",
|
|
90
|
+
webAppOrigin: "https://app.test",
|
|
91
|
+
});
|
|
92
|
+
expect(out.conclusion).toBe("success");
|
|
93
|
+
expect(out.summary).toContain("/scan/scan-safe");
|
|
94
|
+
expect(out.summary).toContain("Full scan:");
|
|
95
|
+
assertNoLegacyCheckRunMarkers(out.summary);
|
|
96
|
+
});
|
|
97
|
+
it("maps needs_review posture to neutral conclusion", () => {
|
|
98
|
+
const out = buildGitHubCheckRunOutput(minimalScanResult({
|
|
99
|
+
assessment: needsReviewAssessment,
|
|
100
|
+
decision: {
|
|
101
|
+
recommendation: "needs_review",
|
|
102
|
+
confidence: "medium",
|
|
103
|
+
reasoning: ["Runtime usage confirmed"],
|
|
104
|
+
},
|
|
105
|
+
changedPackages: ["fastify"],
|
|
106
|
+
}), { scanId: "scan-review", webAppOrigin: "https://app.test" });
|
|
107
|
+
expect(out.conclusion).toBe("neutral");
|
|
108
|
+
expect(out.summary).toContain("/scan/scan-review");
|
|
109
|
+
assertNoLegacyCheckRunMarkers(out.summary);
|
|
110
|
+
});
|
|
111
|
+
it("maps risky posture to failure conclusion", () => {
|
|
112
|
+
const out = buildGitHubCheckRunOutput(minimalScanResult({
|
|
113
|
+
assessment: riskyAssessment,
|
|
114
|
+
decision: {
|
|
115
|
+
recommendation: "risky",
|
|
116
|
+
confidence: "high",
|
|
117
|
+
reasoning: ["Breaking change"],
|
|
118
|
+
},
|
|
119
|
+
}), { scanId: "scan-risky", webAppOrigin: "https://app.test" });
|
|
120
|
+
expect(out.conclusion).toBe("failure");
|
|
121
|
+
assertNoLegacyCheckRunMarkers(out.summary);
|
|
122
|
+
});
|
|
123
|
+
it("produces success conclusion for lightweight baseline presentation", () => {
|
|
124
|
+
const out = buildGitHubCheckRunOutput(minimalScanResult({
|
|
125
|
+
assessment: baselineAssessment,
|
|
126
|
+
reportPresentation: { mode: "lightweight_pr_graph_baseline" },
|
|
127
|
+
changedPackages: ["@types/minimist"],
|
|
128
|
+
decision: {
|
|
129
|
+
recommendation: "safe",
|
|
130
|
+
confidence: "medium",
|
|
131
|
+
reasoning: [],
|
|
132
|
+
},
|
|
133
|
+
}), { scanId: "scan-lw", webAppOrigin: "https://app.test" });
|
|
134
|
+
expect(out.conclusion).toBe("success");
|
|
135
|
+
expect(out.summary).toContain("/scan/scan-lw");
|
|
136
|
+
assertNoLegacyCheckRunMarkers(out.summary);
|
|
137
|
+
});
|
|
138
|
+
it("throws when assessment is missing", () => {
|
|
139
|
+
expect(() => buildGitHubCheckRunOutput(minimalScanResult({ assessment: undefined }), "scan-no-assessment")).toThrow(/assessment/i);
|
|
140
|
+
});
|
|
141
|
+
it("title matches dashboard card headline from the same bundle", () => {
|
|
142
|
+
const result = minimalScanResult({
|
|
143
|
+
assessment: needsReviewAssessment,
|
|
144
|
+
decision: {
|
|
145
|
+
recommendation: "needs_review",
|
|
146
|
+
confidence: "medium",
|
|
147
|
+
reasoning: ["Runtime usage confirmed"],
|
|
148
|
+
},
|
|
149
|
+
changedPackages: ["fastify"],
|
|
150
|
+
});
|
|
151
|
+
const out = buildGitHubCheckRunOutput(result, {
|
|
152
|
+
scanId: "scan-parity",
|
|
153
|
+
webAppOrigin: "https://app.test",
|
|
154
|
+
});
|
|
155
|
+
const bundle = buildScanPresentationBundle({
|
|
156
|
+
result,
|
|
157
|
+
pipelineStatus: "done",
|
|
158
|
+
});
|
|
159
|
+
expect(bundle).not.toBeNull();
|
|
160
|
+
const card = presentDashboardCard(bundle);
|
|
161
|
+
expect(out.title).toBe(card.headline);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -19,7 +19,7 @@ export function buildNarrativeChannels(bundle) {
|
|
|
19
19
|
const { assessment, presentation, profile, result } = bundle;
|
|
20
20
|
const reachLabel = reachVisibilityLabel(presentation.reachVisibility);
|
|
21
21
|
return {
|
|
22
|
-
headline: formatAssessmentHeadline(assessment,
|
|
22
|
+
headline: formatAssessmentHeadline(assessment, profile.status),
|
|
23
23
|
limitedContextMessage: profile.degradedMessage ?? undefined,
|
|
24
24
|
reachLabel,
|
|
25
25
|
insights: projectInsightLines(assessment, result),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { MergeConcernKind } from "
|
|
2
|
-
import type { NarrativeIntensity, ReachVisibility } from "../../assessmentSchema.js";
|
|
1
|
+
import type { MergeConcernKind, NarrativeIntensity, ReachVisibility } from "@mergesignal/contracts";
|
|
3
2
|
/** Assessment-aligned fields projected identically across surfaces. */
|
|
4
3
|
export type AssessmentPresentationFields = {
|
|
5
4
|
posture: "safe" | "needs_review" | "risky";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assessmentPresentationFields.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/assessmentPresentationFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"assessmentPresentationFields.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/assessmentPresentationFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAEhB,MAAM,wBAAwB,CAAC;AAEhC,uEAAuE;AACvE,MAAM,MAAM,4BAA4B,GAAG;IACzC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,OAAO,CAAC;IAC3C,cAAc,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,eAAe,EAAE,eAAe,CAAC;IACjC,kBAAkB,EAAE,kBAAkB,CAAC;CACxC,CAAC"}
|
|
@@ -14,13 +14,14 @@ export type { DashboardCardLayout } from "./dto/dashboardCardPresentation.js";
|
|
|
14
14
|
export type { DashboardCardPresentation as ScanCardPresentation } from "./dto/dashboardCardPresentation.js";
|
|
15
15
|
export type { ScanDetailsPresentation } from "./dto/scanDetailsPresentation.js";
|
|
16
16
|
export type { GitHubCheckRunPresentation, GitHubPrCommentPresentation, CliScanPresentation, } from "./dto/githubAndCliPresentation.js";
|
|
17
|
-
export { presentDashboardCard, presentPipelineDashboardCard, } from "./presenters/presentDashboardCard.js";
|
|
17
|
+
export { presentDashboardCard, presentPipelineDashboardCard, presentSurfacesIncompleteDashboardCard, } from "./presenters/presentDashboardCard.js";
|
|
18
18
|
export { presentScanDetails } from "./presenters/presentScanDetails.js";
|
|
19
19
|
export type { PresentScanDetailsContext } from "./presenters/presentScanDetails.js";
|
|
20
20
|
export { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
|
|
21
21
|
export { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
|
|
22
22
|
export { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
|
|
23
23
|
export { renderGitHubCheckRunMarkdown } from "./render/renderGitHubCheckRunMarkdown.js";
|
|
24
|
+
export { buildGitHubCheckRunOutput, resolveWebAppOrigin, type GitHubCheckRunConclusion, type GitHubCheckRunOutput, type BuildGitHubCheckRunOutputOptions, } from "./buildGitHubCheckRunOutput.js";
|
|
24
25
|
export { renderGitHubPrCommentMarkdown } from "./render/renderGitHubPrCommentMarkdown.js";
|
|
25
26
|
export { renderCliScanSummaryText } from "./render/renderCliScanSummaryText.js";
|
|
26
27
|
export { buildNarrativeChannels, projectCompactKeyPoints, composeHeadline, composeSubheadline, composeKeyPoints, composeVerificationActions, composeAffectedAreaLabels, composeEvidenceRows, composeSupportingContext, evidenceContextFromProfile, } from "./compose/narrativeCompose.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/presentation/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gDAAgD,CAAC;AAC7F,YAAY,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAC;AACvG,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,YAAY,EAAE,8BAA8B,EAAE,MAAM,8CAA8C,CAAC;AACnG,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,YAAY,EAAE,iCAAiC,EAAE,MAAM,iDAAiD,CAAC;AAEzG,YAAY,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,YAAY,EACV,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,gDAAgD;AAChD,YAAY,EAAE,yBAAyB,IAAI,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC5G,YAAY,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAChF,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,oBAAoB,EACpB,4BAA4B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/presentation/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gDAAgD,CAAC;AAC7F,YAAY,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAC;AACvG,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,YAAY,EAAE,8BAA8B,EAAE,MAAM,8CAA8C,CAAC;AACnG,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,YAAY,EAAE,iCAAiC,EAAE,MAAM,iDAAiD,CAAC;AAEzG,YAAY,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,YAAY,EACV,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,gDAAgD;AAChD,YAAY,EAAE,yBAAyB,IAAI,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC5G,YAAY,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAChF,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,sCAAsC,GACvC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,gCAAgC,GACtC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,6BAA6B,EAAE,MAAM,2CAA2C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAEhF,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
|
|
2
2
|
export { buildScanCardPresentation } from "./orchestration/buildScanCardPresentation.js";
|
|
3
3
|
export { buildScanDetailsPresentation } from "./orchestration/buildScanDetailsPresentation.js";
|
|
4
|
-
export { presentDashboardCard, presentPipelineDashboardCard, } from "./presenters/presentDashboardCard.js";
|
|
4
|
+
export { presentDashboardCard, presentPipelineDashboardCard, presentSurfacesIncompleteDashboardCard, } from "./presenters/presentDashboardCard.js";
|
|
5
5
|
export { presentScanDetails } from "./presenters/presentScanDetails.js";
|
|
6
6
|
export { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
|
|
7
7
|
export { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
|
|
8
8
|
export { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
|
|
9
9
|
export { renderGitHubCheckRunMarkdown } from "./render/renderGitHubCheckRunMarkdown.js";
|
|
10
|
+
export { buildGitHubCheckRunOutput, resolveWebAppOrigin, } from "./buildGitHubCheckRunOutput.js";
|
|
10
11
|
export { renderGitHubPrCommentMarkdown } from "./render/renderGitHubPrCommentMarkdown.js";
|
|
11
12
|
export { renderCliScanSummaryText } from "./render/renderCliScanSummaryText.js";
|
|
12
13
|
export { buildNarrativeChannels, projectCompactKeyPoints, composeHeadline, composeSubheadline, composeKeyPoints, composeVerificationActions, composeAffectedAreaLabels, composeEvidenceRows, composeSupportingContext, evidenceContextFromProfile, } from "./compose/narrativeCompose.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildScanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanPresentationBundle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"buildScanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanPresentationBundle.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,MAAM,gCAAgC,GAAG;IAC7C,MAAM,EAAE,UAAU,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gCAAgC,GACtC,sBAAsB,GAAG,IAAI,CA4B/B"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { parseAssessmentOrThrow
|
|
1
|
+
import { parseAssessmentOrThrow } from "@mergesignal/contracts";
|
|
2
|
+
import { toPublicPresentation } from "../../assessmentPresentationUtils.js";
|
|
2
3
|
import { deriveScanNarrative } from "../../deriveScanNarrative.js";
|
|
3
4
|
import { buildProfileFromAssessment } from "../profile/buildProfileFromAssessment.js";
|
|
4
5
|
/**
|
|
@@ -11,12 +12,18 @@ export function buildScanPresentationBundle(input) {
|
|
|
11
12
|
const assessment = input.result.assessment;
|
|
12
13
|
if (!assessment)
|
|
13
14
|
return null;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
let normalized;
|
|
16
|
+
try {
|
|
17
|
+
normalized = parseAssessmentOrThrow(assessment);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const presentation = toPublicPresentation(normalized.presentation);
|
|
23
|
+
const profile = buildProfileFromAssessment(normalized, presentation, input.result);
|
|
17
24
|
const facts = deriveScanNarrative(input.result);
|
|
18
25
|
return {
|
|
19
|
-
assessment,
|
|
26
|
+
assessment: normalized,
|
|
20
27
|
presentation,
|
|
21
28
|
profile,
|
|
22
29
|
facts,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildScanPresentationBundle.test.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanPresentationBundle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { parseAssessmentOrThrow } from "@mergesignal/contracts";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { assessmentTypescriptPatch } from "../../fixtures/assessmentFixtures.js";
|
|
4
|
+
import { buildScanCardPresentation } from "./buildScanCardPresentation.js";
|
|
5
|
+
import { buildScanPresentationBundle } from "./buildScanPresentationBundle.js";
|
|
6
|
+
const abi1Assessment = {
|
|
7
|
+
posture: "safe",
|
|
8
|
+
confidence: "high",
|
|
9
|
+
primaryConcern: null,
|
|
10
|
+
concerns: [],
|
|
11
|
+
factors: ["tooling_maintenance"],
|
|
12
|
+
changeClasses: ["tooling_maintenance"],
|
|
13
|
+
presentation: {
|
|
14
|
+
narrativeIntensity: "minimal",
|
|
15
|
+
reachVisibility: "hidden",
|
|
16
|
+
verificationIntensity: "advisory",
|
|
17
|
+
insightEmissionFloor: "none",
|
|
18
|
+
reportMode: "high_signal_pr",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
function minimalScanResult(assessment) {
|
|
22
|
+
return {
|
|
23
|
+
totalScore: 42,
|
|
24
|
+
layerScores: {
|
|
25
|
+
security: 10,
|
|
26
|
+
maintainability: 20,
|
|
27
|
+
ecosystem: 30,
|
|
28
|
+
upgradeImpact: 40,
|
|
29
|
+
},
|
|
30
|
+
findings: [],
|
|
31
|
+
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
32
|
+
decision: {
|
|
33
|
+
recommendation: "safe",
|
|
34
|
+
confidence: "high",
|
|
35
|
+
reasoning: [],
|
|
36
|
+
},
|
|
37
|
+
assessment,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
describe("buildScanPresentationBundle (presentation boundary)", () => {
|
|
41
|
+
it("ABI-2 assessment produces a bundle", () => {
|
|
42
|
+
const result = minimalScanResult(assessmentTypescriptPatch);
|
|
43
|
+
const bundle = buildScanPresentationBundle({
|
|
44
|
+
result,
|
|
45
|
+
pipelineStatus: "done",
|
|
46
|
+
});
|
|
47
|
+
expect(bundle).not.toBeNull();
|
|
48
|
+
expect(bundle.assessment.reviewFocalPoint.anchors).toContain("typescript");
|
|
49
|
+
});
|
|
50
|
+
it("ABI-2 assessment renders a dashboard card headline", () => {
|
|
51
|
+
const result = minimalScanResult(assessmentTypescriptPatch);
|
|
52
|
+
const card = buildScanCardPresentation({
|
|
53
|
+
pipelineStatus: "done",
|
|
54
|
+
result,
|
|
55
|
+
});
|
|
56
|
+
expect(card.headline.length).toBeGreaterThan(0);
|
|
57
|
+
});
|
|
58
|
+
it("ABI-1 assessment returns null without throwing", () => {
|
|
59
|
+
const result = minimalScanResult(abi1Assessment);
|
|
60
|
+
expect(() => buildScanPresentationBundle({
|
|
61
|
+
result,
|
|
62
|
+
pipelineStatus: "done",
|
|
63
|
+
})).not.toThrow();
|
|
64
|
+
expect(buildScanPresentationBundle({
|
|
65
|
+
result,
|
|
66
|
+
pipelineStatus: "done",
|
|
67
|
+
})).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
it("ABI-1 assessment does not mutate stored assessment JSON", () => {
|
|
70
|
+
const result = minimalScanResult(structuredClone(abi1Assessment));
|
|
71
|
+
const before = JSON.stringify(result.assessment);
|
|
72
|
+
buildScanPresentationBundle({ result, pipelineStatus: "done" });
|
|
73
|
+
expect(JSON.stringify(result.assessment)).toBe(before);
|
|
74
|
+
expect(result.assessment).not.toHaveProperty("reviewFocalPoint");
|
|
75
|
+
});
|
|
76
|
+
it("ABI-1 direct parseAssessmentOrThrow still throws (contracts strict)", () => {
|
|
77
|
+
expect(() => parseAssessmentOrThrow(abi1Assessment)).toThrow();
|
|
78
|
+
});
|
|
79
|
+
it("mixed PR index: ABI-2 ok and ABI-1 degraded without throw", () => {
|
|
80
|
+
const abi2 = minimalScanResult(assessmentTypescriptPatch);
|
|
81
|
+
const abi1 = minimalScanResult(abi1Assessment);
|
|
82
|
+
for (const result of [abi2, abi1]) {
|
|
83
|
+
expect(() => buildScanCardPresentation({
|
|
84
|
+
pipelineStatus: "done",
|
|
85
|
+
result,
|
|
86
|
+
})).not.toThrow();
|
|
87
|
+
}
|
|
88
|
+
const abi2Card = buildScanCardPresentation({
|
|
89
|
+
pipelineStatus: "done",
|
|
90
|
+
result: abi2,
|
|
91
|
+
});
|
|
92
|
+
const abi1Card = buildScanCardPresentation({
|
|
93
|
+
pipelineStatus: "done",
|
|
94
|
+
result: abi1,
|
|
95
|
+
});
|
|
96
|
+
expect(abi2Card.headline.length).toBeGreaterThan(0);
|
|
97
|
+
expect(abi1Card.pipeline?.status).toBe("failed");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Assessment, AssessmentPresentationPublic } from "
|
|
1
|
+
import type { Assessment, AssessmentPresentationPublic } from "@mergesignal/contracts";
|
|
2
2
|
import type { ScanNarrativeFacts } from "../../scanNarrativeFacts.js";
|
|
3
3
|
import type { ScanResult } from "../../types.js";
|
|
4
4
|
import type { PresentationProfile } from "../profile/presentationProfile.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/scanPresentationBundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC7B,MAAM,
|
|
1
|
+
{"version":3,"file":"scanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/scanPresentationBundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,4BAA4B,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;IAC7B;;;OAGG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;;OAGG;IACH,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC"}
|
|
@@ -66,7 +66,7 @@ describe("presentDashboardCard personas", () => {
|
|
|
66
66
|
expect(card.verdict?.posture).toBe("safe");
|
|
67
67
|
expect(card.headline).toMatch(/patch upgrade/i);
|
|
68
68
|
expect(card.headline).not.toMatch(/needs review/i);
|
|
69
|
-
expect(card.verification.length).
|
|
69
|
+
expect(card.verification.length).toBe(0);
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
describe("presentation parity across surfaces", () => {
|
|
@@ -16,8 +16,6 @@ const personas = [
|
|
|
16
16
|
density: "minimal",
|
|
17
17
|
headlineMatch: /patch upgrade/i,
|
|
18
18
|
headlineMustNot: /needs review/i,
|
|
19
|
-
verificationIncludes: ["CI", "typecheck"],
|
|
20
|
-
verificationMustNot: /auth flow/i,
|
|
21
19
|
},
|
|
22
20
|
{
|
|
23
21
|
name: "eslint",
|
|
@@ -27,7 +25,6 @@ const personas = [
|
|
|
27
25
|
density: "minimal",
|
|
28
26
|
headlineMatch: /patch upgrade/i,
|
|
29
27
|
headlineMustNot: /needs review/i,
|
|
30
|
-
verificationIncludes: ["ci", "lint"],
|
|
31
28
|
},
|
|
32
29
|
{
|
|
33
30
|
name: "prettier",
|
|
@@ -37,7 +34,6 @@ const personas = [
|
|
|
37
34
|
density: "minimal",
|
|
38
35
|
headlineMatch: /patch upgrade/i,
|
|
39
36
|
headlineMustNot: /needs review/i,
|
|
40
|
-
verificationIncludes: ["format"],
|
|
41
37
|
},
|
|
42
38
|
{
|
|
43
39
|
name: "vitest",
|
|
@@ -47,7 +43,6 @@ const personas = [
|
|
|
47
43
|
density: "minimal",
|
|
48
44
|
headlineMatch: /patch upgrade|dependency upgrade/i,
|
|
49
45
|
headlineMustNot: /needs review/i,
|
|
50
|
-
verificationIncludes: ["test"],
|
|
51
46
|
},
|
|
52
47
|
{
|
|
53
48
|
name: "fastify runtime",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DashboardCardPresentation } from "../dto/dashboardCardPresentation.js";
|
|
2
2
|
import type { ScanPresentationBundle } from "../orchestration/scanPresentationBundle.js";
|
|
3
3
|
export declare function presentDashboardCard(bundle: ScanPresentationBundle): DashboardCardPresentation;
|
|
4
|
+
export declare function presentSurfacesIncompleteDashboardCard(): DashboardCardPresentation;
|
|
4
5
|
export declare function presentPipelineDashboardCard(pipelineStatus: "queued" | "running" | "failed"): DashboardCardPresentation;
|
|
5
6
|
//# sourceMappingURL=presentDashboardCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentDashboardCard.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentDashboardCard.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAErF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAoEzF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,sBAAsB,GAC7B,yBAAyB,CA8C3B;AAED,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAC9C,yBAAyB,CAqB3B"}
|
|
1
|
+
{"version":3,"file":"presentDashboardCard.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentDashboardCard.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAErF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAoEzF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,sBAAsB,GAC7B,yBAAyB,CA8C3B;AAED,wBAAgB,sCAAsC,IAAI,yBAAyB,CAelF;AAED,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAC9C,yBAAyB,CAqB3B"}
|
|
@@ -96,6 +96,22 @@ export function presentDashboardCard(bundle) {
|
|
|
96
96
|
};
|
|
97
97
|
return normalizeDashboardCard(card);
|
|
98
98
|
}
|
|
99
|
+
export function presentSurfacesIncompleteDashboardCard() {
|
|
100
|
+
const headline = scanSurfaceCopy.pipeline.surfacesNotSynchronized;
|
|
101
|
+
return normalizeDashboardCard({
|
|
102
|
+
pipeline: {
|
|
103
|
+
status: "failed",
|
|
104
|
+
headline,
|
|
105
|
+
subheadline: scanSurfaceCopy.pipeline.surfacesNotSynchronizedDetail,
|
|
106
|
+
},
|
|
107
|
+
headline,
|
|
108
|
+
insights: [],
|
|
109
|
+
verification: [],
|
|
110
|
+
layout: "standard",
|
|
111
|
+
detailActionLabel: scanSurfaceCopy.presentation.actionLabelReview,
|
|
112
|
+
sortKey: { postureRank: -1, riskIndex: -1 },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
99
115
|
export function presentPipelineDashboardCard(pipelineStatus) {
|
|
100
116
|
const isRunning = pipelineStatus === "queued" || pipelineStatus === "running";
|
|
101
117
|
const headline = isRunning
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Assessment, AssessmentPresentationPublic } from "
|
|
1
|
+
import type { Assessment, AssessmentPresentationPublic } from "@mergesignal/contracts";
|
|
2
2
|
import type { ScanResult } from "../../types.js";
|
|
3
3
|
import type { PresentationProfile } from "./presentationProfile.js";
|
|
4
4
|
export declare function buildProfileFromAssessment(assessment: Assessment, presentation: AssessmentPresentationPublic, result: ScanResult): PresentationProfile;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildProfileFromAssessment.d.ts","sourceRoot":"","sources":["../../../src/presentation/profile/buildProfileFromAssessment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC7B,MAAM,
|
|
1
|
+
{"version":3,"file":"buildProfileFromAssessment.d.ts","sourceRoot":"","sources":["../../../src/presentation/profile/buildProfileFromAssessment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,4BAA4B,EAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAiDpE,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,4BAA4B,EAC1C,MAAM,EAAE,UAAU,GACjB,mBAAmB,CAuBrB"}
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import { narrativeIntensityToDensity } from "../../
|
|
1
|
+
import { narrativeIntensityToDensity } from "../../assessmentPresentationUtils.js";
|
|
2
2
|
import { safeParseRepoIntelligence } from "../../repoIntelligenceSchema.js";
|
|
3
3
|
import { scanSurfaceCopy } from "../../scanSurfaceCopy.js";
|
|
4
4
|
import { collectVerificationFocus } from "../../assessmentProjection.js";
|
|
5
|
+
function focalAnchorPackage(assessment) {
|
|
6
|
+
const anchors = assessment.reviewFocalPoint.anchors;
|
|
7
|
+
if (!anchors.length)
|
|
8
|
+
return null;
|
|
9
|
+
const token = anchors[0];
|
|
10
|
+
if (token === "dependency_graph")
|
|
11
|
+
return null;
|
|
12
|
+
if (token.includes("+"))
|
|
13
|
+
return token.split("+")[0];
|
|
14
|
+
return token;
|
|
15
|
+
}
|
|
5
16
|
function buildInterpretation(assessment, presentation, result) {
|
|
6
17
|
const verificationLabels = collectVerificationFocus(result);
|
|
7
|
-
const anchorPackage =
|
|
18
|
+
const anchorPackage = focalAnchorPackage(assessment);
|
|
8
19
|
const suppressRuntimeNarrative = presentation.reachVisibility === "hidden";
|
|
9
20
|
let intent = "unknown_upgrade";
|
|
10
21
|
if (assessment.primaryConcern === "insufficient_evidence") {
|
|
@@ -8,14 +8,34 @@ import { renderGitHubCheckRunMarkdown } from "./render/renderGitHubCheckRunMarkd
|
|
|
8
8
|
import { renderGitHubPrCommentMarkdown } from "./render/renderGitHubPrCommentMarkdown.js";
|
|
9
9
|
import { scanResultBullmq, scanResultEslint, scanResultFastifyRuntime, scanResultMixedTypescriptFastify, scanResultPrettier, scanResultTypescriptPatch, scanResultVitest, } from "./fixtures/presentationPersonaFixtures.js";
|
|
10
10
|
import { projectAssessmentFields } from "./projectAssessmentFields.js";
|
|
11
|
+
import { buildNarrativeChannels } from "./compose/narrativeChannels.js";
|
|
11
12
|
const SCAN_ID = "22222222-2222-4222-8222-222222222222";
|
|
12
13
|
const ORIGIN = "https://app.example.com";
|
|
14
|
+
const validationPersonasCore = [
|
|
15
|
+
{
|
|
16
|
+
id: "validation-pr-26",
|
|
17
|
+
name: "PR #26 mixed",
|
|
18
|
+
result: scanResultMixedTypescriptFastify,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "validation-pr-27",
|
|
22
|
+
name: "PR #27 fastify",
|
|
23
|
+
result: scanResultFastifyRuntime,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "validation-pr-28",
|
|
27
|
+
name: "PR #28 typescript",
|
|
28
|
+
result: scanResultTypescriptPatch,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "validation-pr-29",
|
|
32
|
+
name: "PR #29 prettier",
|
|
33
|
+
result: scanResultPrettier,
|
|
34
|
+
},
|
|
35
|
+
{ id: "validation-pr-30", name: "PR #30 bullmq", result: scanResultBullmq },
|
|
36
|
+
];
|
|
13
37
|
const validationPersonas = [
|
|
14
|
-
|
|
15
|
-
{ name: "PR #27 fastify", result: scanResultFastifyRuntime },
|
|
16
|
-
{ name: "PR #28 typescript", result: scanResultTypescriptPatch },
|
|
17
|
-
{ name: "PR #29 prettier", result: scanResultPrettier },
|
|
18
|
-
{ name: "PR #30 bullmq", result: scanResultBullmq },
|
|
38
|
+
...validationPersonasCore,
|
|
19
39
|
{ name: "eslint", result: scanResultEslint },
|
|
20
40
|
{ name: "vitest", result: scanResultVitest },
|
|
21
41
|
];
|
|
@@ -71,4 +91,52 @@ describe("surfaceParity guardrail", () => {
|
|
|
71
91
|
expect(s.details.hero.headline).toBe(s.card.headline);
|
|
72
92
|
});
|
|
73
93
|
}
|
|
94
|
+
it("validation personas #26–#30: pairwise distinct reviewFocalPoint and headlines", () => {
|
|
95
|
+
const focalPoints = [];
|
|
96
|
+
const headlines = [];
|
|
97
|
+
for (const persona of validationPersonasCore) {
|
|
98
|
+
const s = surfacesFor(persona.result);
|
|
99
|
+
focalPoints.push(persona.result.assessment.reviewFocalPoint);
|
|
100
|
+
headlines.push(s.card.headline);
|
|
101
|
+
expect(s.check.title).toBe(s.card.headline);
|
|
102
|
+
}
|
|
103
|
+
for (let i = 0; i < headlines.length; i++) {
|
|
104
|
+
for (let j = i + 1; j < headlines.length; j++) {
|
|
105
|
+
expect(headlines[i]).not.toBe(headlines[j]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (let i = 0; i < focalPoints.length; i++) {
|
|
109
|
+
for (let j = i + 1; j < focalPoints.length; j++) {
|
|
110
|
+
expect(focalPoints[i]).not.toEqual(focalPoints[j]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
it("PR #26: headline uses focal fastify, not changedPackages[0] typescript", () => {
|
|
115
|
+
const result = scanResultMixedTypescriptFastify;
|
|
116
|
+
expect(result.changedPackages?.[0]).toBe("typescript");
|
|
117
|
+
const s = surfacesFor(result);
|
|
118
|
+
expect(s.card.headline.toLowerCase()).toContain("fastify");
|
|
119
|
+
expect(s.card.headline.toLowerCase()).not.toContain("typescript");
|
|
120
|
+
});
|
|
121
|
+
it("validation personas #28–#30: reach and verification channels differ by scope", () => {
|
|
122
|
+
const channelsById = new Map(validationPersonasCore.map((p) => {
|
|
123
|
+
const bundle = buildScanPresentationBundle({
|
|
124
|
+
result: p.result,
|
|
125
|
+
pipelineStatus: "done",
|
|
126
|
+
});
|
|
127
|
+
return [p.id, buildNarrativeChannels(bundle)];
|
|
128
|
+
}));
|
|
129
|
+
const ch28 = channelsById.get("validation-pr-28");
|
|
130
|
+
const ch29 = channelsById.get("validation-pr-29");
|
|
131
|
+
const ch27 = channelsById.get("validation-pr-27");
|
|
132
|
+
const ch30 = channelsById.get("validation-pr-30");
|
|
133
|
+
const ch26 = channelsById.get("validation-pr-26");
|
|
134
|
+
expect(ch28.reachLabel).toBeUndefined();
|
|
135
|
+
expect(ch29.reachLabel).toBeUndefined();
|
|
136
|
+
expect(ch27.verification.length).toBeGreaterThan(0);
|
|
137
|
+
expect(ch30.verification.length).toBeGreaterThan(0);
|
|
138
|
+
expect(ch27.verification).not.toEqual(ch30.verification);
|
|
139
|
+
expect(ch26.verification).not.toEqual(ch27.verification);
|
|
140
|
+
expect(ch26.scopeAreas.length).toBeGreaterThan(0);
|
|
141
|
+
});
|
|
74
142
|
});
|
|
@@ -35,16 +35,17 @@ export declare const repoIntelligenceWireSchema: z.ZodObject<{
|
|
|
35
35
|
}, z.core.$strip>;
|
|
36
36
|
areas: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
37
37
|
dependencyClass: z.ZodOptional<z.ZodEnum<{
|
|
38
|
-
format: "format";
|
|
39
38
|
unknown: "unknown";
|
|
40
39
|
runtime: "runtime";
|
|
41
40
|
build: "build";
|
|
42
41
|
test: "test";
|
|
43
42
|
tooling: "tooling";
|
|
44
43
|
lint: "lint";
|
|
44
|
+
format: "format";
|
|
45
45
|
ci: "ci";
|
|
46
46
|
}>>;
|
|
47
47
|
packageRole: z.ZodOptional<z.ZodEnum<{
|
|
48
|
+
queue: "queue";
|
|
48
49
|
unknown: "unknown";
|
|
49
50
|
typechecker: "typechecker";
|
|
50
51
|
compiler: "compiler";
|
|
@@ -54,7 +55,6 @@ export declare const repoIntelligenceWireSchema: z.ZodObject<{
|
|
|
54
55
|
test_runner: "test_runner";
|
|
55
56
|
http_framework: "http_framework";
|
|
56
57
|
auth: "auth";
|
|
57
|
-
queue: "queue";
|
|
58
58
|
orm: "orm";
|
|
59
59
|
}>>;
|
|
60
60
|
lockfileKind: z.ZodOptional<z.ZodEnum<{
|
|
@@ -79,8 +79,8 @@ export declare const repoIntelligenceWireSchema: z.ZodObject<{
|
|
|
79
79
|
}>>;
|
|
80
80
|
evidenceStrength: z.ZodOptional<z.ZodEnum<{
|
|
81
81
|
low: "low";
|
|
82
|
-
medium: "medium";
|
|
83
82
|
high: "high";
|
|
83
|
+
medium: "medium";
|
|
84
84
|
}>>;
|
|
85
85
|
confidenceReason: z.ZodOptional<z.ZodString>;
|
|
86
86
|
verificationFocus: z.ZodOptional<z.ZodArray<z.ZodString>>;
|