@mergesignal/shared 0.9.0 → 0.10.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.
Files changed (48) hide show
  1. package/dist/assessmentProjection.d.ts +10 -1
  2. package/dist/assessmentProjection.d.ts.map +1 -1
  3. package/dist/assessmentProjection.js +38 -7
  4. package/dist/assessmentProjection.verificationChannel.test.d.ts +2 -0
  5. package/dist/assessmentProjection.verificationChannel.test.d.ts.map +1 -0
  6. package/dist/assessmentProjection.verificationChannel.test.js +110 -0
  7. package/dist/fixtures/assessmentFixtures.d.ts +1 -1
  8. package/dist/fixtures/assessmentFixtures.d.ts.map +1 -1
  9. package/dist/fixtures/assessmentFixtures.js +10 -10
  10. package/dist/fixtures/assessmentScopeFixtures.d.ts +4 -2
  11. package/dist/fixtures/assessmentScopeFixtures.d.ts.map +1 -1
  12. package/dist/fixtures/assessmentScopeFixtures.js +13 -3
  13. package/dist/fixtures/engineAbi4Fixtures.d.ts +13 -1
  14. package/dist/fixtures/engineAbi4Fixtures.d.ts.map +1 -1
  15. package/dist/fixtures/engineAbi4Fixtures.js +21 -0
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/prRiskWiringRegression.test.d.ts +2 -0
  20. package/dist/prRiskWiringRegression.test.d.ts.map +1 -0
  21. package/dist/prRiskWiringRegression.test.js +102 -0
  22. package/dist/presentation/dto/assessmentPresentationFields.d.ts +2 -0
  23. package/dist/presentation/dto/assessmentPresentationFields.d.ts.map +1 -1
  24. package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts.map +1 -1
  25. package/dist/presentation/fixtures/presentationPersonaFixtures.js +5 -1
  26. package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -1
  27. package/dist/presentation/fixtures/scanResultFixtures.js +28 -17
  28. package/dist/presentation/presentation.test.js +3 -1
  29. package/dist/presentation/presentationPersonas.test.js +2 -0
  30. package/dist/presentation/presenters/presentCliScanSummary.js +1 -1
  31. package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -1
  32. package/dist/presentation/presenters/presentDashboardCard.js +3 -2
  33. package/dist/presentation/profile/buildProfileFromAssessment.d.ts.map +1 -1
  34. package/dist/presentation/profile/buildProfileFromAssessment.js +2 -2
  35. package/dist/presentation/projectAssessmentFields.d.ts.map +1 -1
  36. package/dist/presentation/projectAssessmentFields.js +4 -2
  37. package/dist/presentation/surfaceParity.test.js +6 -0
  38. package/dist/presentation/verificationChannelParity.test.d.ts +2 -0
  39. package/dist/presentation/verificationChannelParity.test.d.ts.map +1 -0
  40. package/dist/presentation/verificationChannelParity.test.js +57 -0
  41. package/dist/repoIntelligenceSchema.d.ts +3 -3
  42. package/dist/riskSignals.test.d.ts +2 -0
  43. package/dist/riskSignals.test.d.ts.map +1 -0
  44. package/dist/riskSignals.test.js +32 -0
  45. package/dist/scanPresentationBundle.golden.test.js +2 -2
  46. package/dist/scanResultSchema.d.ts +6 -0
  47. package/dist/scanResultSchema.d.ts.map +1 -1
  48. package/package.json +2 -2
@@ -1,7 +1,16 @@
1
1
  import type { ScanResult } from "./types.js";
2
2
  import { type RepoIntelligence } from "./repoIntelligenceSchema.js";
3
- import type { Assessment, AssessmentPresentationPublic, ReachVisibility } from "@mergesignal/contracts";
3
+ import type { Assessment, AssessmentPresentationPublic, ReachVisibility, VerificationIntensity } from "@mergesignal/contracts";
4
4
  import type { PresentationStatus } from "./presentation/dto/types.js";
5
+ export type VerificationChannel = "runtime" | "artifact" | "none";
6
+ export declare function resolveVerificationChannel(intensity: VerificationIntensity): VerificationChannel;
7
+ export declare function collectRuntimeVerificationFocus(result: ScanResult): string[];
8
+ export declare function collectArtifactVerificationFocus(result: ScanResult): string[];
9
+ export declare function collectVerificationFocusForPresentation(presentation: AssessmentPresentationPublic, result: ScanResult): {
10
+ channel: VerificationChannel;
11
+ focus: string[];
12
+ };
13
+ /** @deprecated Use collectVerificationFocusForPresentation or channel-specific collectors. */
5
14
  export declare function collectVerificationFocus(result: ScanResult): string[];
6
15
  export declare function reachVisibilityLabel(visibility: ReachVisibility): string | undefined;
7
16
  export declare function formatAssessmentHeadline(assessment: Assessment, status: PresentationStatus): 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,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"}
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,EACf,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAmDlE,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,qBAAqB,GAC/B,mBAAmB,CAIrB;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAQ5E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAS7E;AAED,wBAAgB,uCAAuC,CACrD,YAAY,EAAE,4BAA4B,EAC1C,MAAM,EAAE,UAAU,GACjB;IAAE,OAAO,EAAE,mBAAmB,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAWnD;AAED,8FAA8F;AAC9F,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAErE;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,CAOV;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,gBAAgB,GAAG,IAAI,GAAG,MAAM,EAAE,CAI3E"}
@@ -32,12 +32,7 @@ function surfaceHintFromAssessment(assessment) {
32
32
  }
33
33
  return "application code";
34
34
  }
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 ?? [];
40
- const parsed = safeParseRepoIntelligence(result.repoIntelligence);
35
+ function collectVerificationFocusFromPackages(packages, parsed) {
41
36
  if (!parsed.ok)
42
37
  return [];
43
38
  const labels = [];
@@ -54,6 +49,42 @@ export function collectVerificationFocus(result) {
54
49
  }
55
50
  return labels;
56
51
  }
52
+ export function resolveVerificationChannel(intensity) {
53
+ if (intensity === "none")
54
+ return "none";
55
+ if (intensity === "required")
56
+ return "runtime";
57
+ return "artifact";
58
+ }
59
+ export function collectRuntimeVerificationFocus(result) {
60
+ const scope = result.assessment?.verificationScope;
61
+ if (scope?.focus?.length)
62
+ return [...scope.focus];
63
+ const packages = scope?.packages ?? [];
64
+ return collectVerificationFocusFromPackages(packages, safeParseRepoIntelligence(result.repoIntelligence));
65
+ }
66
+ export function collectArtifactVerificationFocus(result) {
67
+ const artifactGrounded = result.assessment?.verificationScope?.artifactGrounded;
68
+ if (!artifactGrounded)
69
+ return [];
70
+ if (artifactGrounded.focus.length > 0)
71
+ return [...artifactGrounded.focus];
72
+ return collectVerificationFocusFromPackages(artifactGrounded.packages, safeParseRepoIntelligence(result.repoIntelligence));
73
+ }
74
+ export function collectVerificationFocusForPresentation(presentation, result) {
75
+ const channel = resolveVerificationChannel(presentation.verificationIntensity);
76
+ if (channel === "none") {
77
+ return { channel, focus: [] };
78
+ }
79
+ if (channel === "runtime") {
80
+ return { channel, focus: collectRuntimeVerificationFocus(result) };
81
+ }
82
+ return { channel, focus: collectArtifactVerificationFocus(result) };
83
+ }
84
+ /** @deprecated Use collectVerificationFocusForPresentation or channel-specific collectors. */
85
+ export function collectVerificationFocus(result) {
86
+ return collectRuntimeVerificationFocus(result);
87
+ }
57
88
  export function reachVisibilityLabel(visibility) {
58
89
  switch (visibility) {
59
90
  case "hidden":
@@ -118,7 +149,7 @@ export function projectInsightLines(assessment, result) {
118
149
  export function projectVerificationActions(assessment, presentation, result, max) {
119
150
  if (presentation.verificationIntensity === "none")
120
151
  return [];
121
- const focus = collectVerificationFocus(result);
152
+ const { focus } = collectVerificationFocusForPresentation(presentation, result);
122
153
  return focus.slice(0, max);
123
154
  }
124
155
  export function applicationAreaLabels(ri) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=assessmentProjection.verificationChannel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assessmentProjection.verificationChannel.test.d.ts","sourceRoot":"","sources":["../src/assessmentProjection.verificationChannel.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { artifactGroundedScopeFor, minimalReviewFocalPoint, verificationScopeFor, withAssessmentScope, } from "./fixtures/assessmentScopeFixtures.js";
3
+ import { assessmentFastifyRuntime, assessmentPrettier, assessmentTypescriptPatch, } from "./fixtures/assessmentFixtures.js";
4
+ import { collectArtifactVerificationFocus, collectRuntimeVerificationFocus, collectVerificationFocusForPresentation, resolveVerificationChannel, } from "./assessmentProjection.js";
5
+ import { toPublicPresentation } from "./assessmentPresentationUtils.js";
6
+ function scanResultWithAssessment(assessment) {
7
+ return {
8
+ totalScore: 30,
9
+ layerScores: {
10
+ security: 10,
11
+ maintainability: 10,
12
+ ecosystem: 10,
13
+ upgradeImpact: 0,
14
+ },
15
+ findings: [],
16
+ generatedAt: "2026-01-01T00:00:00.000Z",
17
+ assessment,
18
+ decision: {
19
+ recommendation: "safe",
20
+ confidence: "high",
21
+ reasoning: ["fixture"],
22
+ },
23
+ repoIntelligence: {},
24
+ changedPackages: assessment.reviewFocalPoint.anchors,
25
+ };
26
+ }
27
+ describe("resolveVerificationChannel", () => {
28
+ it("maps intensity to presentation channel", () => {
29
+ expect(resolveVerificationChannel("none")).toBe("none");
30
+ expect(resolveVerificationChannel("advisory")).toBe("artifact");
31
+ expect(resolveVerificationChannel("required")).toBe("runtime");
32
+ });
33
+ });
34
+ describe("collectVerificationFocusForPresentation", () => {
35
+ it("fastify required: runtime focus only", () => {
36
+ const result = scanResultWithAssessment(assessmentFastifyRuntime);
37
+ const presentation = toPublicPresentation(assessmentFastifyRuntime.presentation);
38
+ const projected = collectVerificationFocusForPresentation(presentation, result);
39
+ expect(projected.channel).toBe("runtime");
40
+ expect(projected.focus).toEqual(["routes", "middleware"]);
41
+ expect(collectRuntimeVerificationFocus(result)).toEqual(projected.focus);
42
+ expect(collectArtifactVerificationFocus(result)).toEqual([]);
43
+ });
44
+ it("typescript advisory: artifact focus only", () => {
45
+ const result = scanResultWithAssessment(assessmentTypescriptPatch);
46
+ const presentation = toPublicPresentation(assessmentTypescriptPatch.presentation);
47
+ const projected = collectVerificationFocusForPresentation(presentation, result);
48
+ expect(projected.channel).toBe("artifact");
49
+ expect(projected.focus).toEqual(["typecheck"]);
50
+ expect(collectArtifactVerificationFocus(result)).toEqual(["typecheck"]);
51
+ expect(collectRuntimeVerificationFocus(result)).toEqual([]);
52
+ });
53
+ it("prettier advisory: artifact focus only", () => {
54
+ const result = scanResultWithAssessment(assessmentPrettier);
55
+ const presentation = toPublicPresentation(assessmentPrettier.presentation);
56
+ const projected = collectVerificationFocusForPresentation(presentation, result);
57
+ expect(projected.channel).toBe("artifact");
58
+ expect(projected.focus).toEqual(["format"]);
59
+ });
60
+ it("none intensity: empty focus", () => {
61
+ const assessment = withAssessmentScope({
62
+ ...assessmentTypescriptPatch,
63
+ presentation: {
64
+ ...assessmentTypescriptPatch.presentation,
65
+ verificationIntensity: "none",
66
+ },
67
+ }, {
68
+ reviewFocalPoint: minimalReviewFocalPoint(["typescript"]),
69
+ verificationScope: verificationScopeFor(["fastify"], ["routes"], artifactGroundedScopeFor(["typescript"], ["typecheck"])),
70
+ });
71
+ const result = scanResultWithAssessment(assessment);
72
+ const projected = collectVerificationFocusForPresentation(toPublicPresentation(assessment.presentation), result);
73
+ expect(projected.channel).toBe("none");
74
+ expect(projected.focus).toEqual([]);
75
+ });
76
+ it("conflict fixture: advisory uses artifact only", () => {
77
+ const assessment = withAssessmentScope({
78
+ ...assessmentTypescriptPatch,
79
+ presentation: {
80
+ ...assessmentTypescriptPatch.presentation,
81
+ verificationIntensity: "advisory",
82
+ },
83
+ }, {
84
+ reviewFocalPoint: minimalReviewFocalPoint(["typescript"]),
85
+ verificationScope: verificationScopeFor(["fastify"], ["routes"], artifactGroundedScopeFor(["typescript"], ["typecheck"])),
86
+ });
87
+ const result = scanResultWithAssessment(assessment);
88
+ const projected = collectVerificationFocusForPresentation(toPublicPresentation(assessment.presentation), result);
89
+ expect(projected.channel).toBe("artifact");
90
+ expect(projected.focus).toEqual(["typecheck"]);
91
+ expect(projected.focus).not.toContain("routes");
92
+ });
93
+ it("conflict fixture: required uses runtime only", () => {
94
+ const assessment = withAssessmentScope({
95
+ ...assessmentFastifyRuntime,
96
+ presentation: {
97
+ ...assessmentFastifyRuntime.presentation,
98
+ verificationIntensity: "required",
99
+ },
100
+ }, {
101
+ reviewFocalPoint: minimalReviewFocalPoint(["fastify"]),
102
+ verificationScope: verificationScopeFor(["fastify"], ["routes", "middleware"], artifactGroundedScopeFor(["typescript"], ["typecheck"])),
103
+ });
104
+ const result = scanResultWithAssessment(assessment);
105
+ const projected = collectVerificationFocusForPresentation(toPublicPresentation(assessment.presentation), result);
106
+ expect(projected.channel).toBe("runtime");
107
+ expect(projected.focus).toEqual(["routes", "middleware"]);
108
+ expect(projected.focus).not.toContain("typecheck");
109
+ });
110
+ });
@@ -1,7 +1,7 @@
1
1
  import type { Assessment } from "@mergesignal/contracts";
2
2
  export declare const assessmentTypescriptPatch: Assessment;
3
- export declare const assessmentPrettier: Assessment;
4
3
  export declare const assessmentEslint: Assessment;
4
+ export declare const assessmentPrettier: Assessment;
5
5
  export declare const assessmentVitest: Assessment;
6
6
  export declare const assessmentFastifyRuntime: Assessment;
7
7
  export declare const assessmentBullmq: 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;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"}
1
+ {"version":3,"file":"assessmentFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/assessmentFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA2BzD,eAAO,MAAM,yBAAyB,EAAE,UAmBvC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAa9B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,UAchC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAuB9B,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"}
@@ -1,4 +1,4 @@
1
- import { emptyReachScope, emptyVerificationScope, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./assessmentScopeFixtures.js";
1
+ import { artifactGroundedScopeFor, emptyReachScope, emptyVerificationScope, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./assessmentScopeFixtures.js";
2
2
  const safeToolingPresentation = {
3
3
  narrativeIntensity: "minimal",
4
4
  reachVisibility: "hidden",
@@ -24,22 +24,22 @@ export const assessmentTypescriptPatch = withAssessmentScope({
24
24
  }, {
25
25
  reviewFocalPoint: minimalReviewFocalPoint(["typescript"]),
26
26
  reachScope: emptyReachScope(),
27
- verificationScope: emptyVerificationScope(),
27
+ verificationScope: verificationScopeFor([], [], artifactGroundedScopeFor(["typescript"], ["typecheck"], ["package.json"])),
28
28
  });
29
- export const assessmentPrettier = withAssessmentScope({
29
+ export const assessmentEslint = withAssessmentScope({
30
30
  ...assessmentTypescriptPatch,
31
- factors: ["tooling_maintenance"],
32
31
  }, {
33
- reviewFocalPoint: minimalReviewFocalPoint(["prettier"]),
32
+ reviewFocalPoint: minimalReviewFocalPoint(["eslint"]),
34
33
  reachScope: emptyReachScope(),
35
- verificationScope: emptyVerificationScope(),
34
+ verificationScope: verificationScopeFor([], [], artifactGroundedScopeFor(["eslint"], ["lint"], [".eslintrc.cjs"])),
36
35
  });
37
- export const assessmentEslint = withAssessmentScope({
36
+ export const assessmentPrettier = withAssessmentScope({
38
37
  ...assessmentTypescriptPatch,
38
+ factors: ["tooling_maintenance"],
39
39
  }, {
40
- reviewFocalPoint: minimalReviewFocalPoint(["eslint"]),
40
+ reviewFocalPoint: minimalReviewFocalPoint(["prettier"]),
41
41
  reachScope: emptyReachScope(),
42
- verificationScope: emptyVerificationScope(),
42
+ verificationScope: verificationScopeFor([], [], artifactGroundedScopeFor(["prettier"], ["format"], [".prettierrc"])),
43
43
  });
44
44
  export const assessmentVitest = withAssessmentScope({
45
45
  posture: "safe",
@@ -52,7 +52,7 @@ export const assessmentVitest = withAssessmentScope({
52
52
  }, {
53
53
  reviewFocalPoint: minimalReviewFocalPoint(["vitest"]),
54
54
  reachScope: emptyReachScope(),
55
- verificationScope: emptyVerificationScope(),
55
+ verificationScope: verificationScopeFor([], [], artifactGroundedScopeFor(["vitest"], ["test_suite"], ["vitest.config.ts"])),
56
56
  });
57
57
  export const assessmentFastifyRuntime = withAssessmentScope({
58
58
  posture: "needs_review",
@@ -1,4 +1,4 @@
1
- import type { Assessment, FocalElectionDimension, ReachScope, ReviewEpisodeShape, ReviewFocalPoint, VerificationScope } from "@mergesignal/contracts";
1
+ import type { ArtifactGroundedVerificationScope, Assessment, FocalElectionDimension, ReachScope, ReviewEpisodeShape, ReviewFocalPoint, VerificationScope } from "@mergesignal/contracts";
2
2
  export declare function minimalReviewFocalPoint(anchors: string[], opts?: {
3
3
  episodeShape?: ReviewEpisodeShape;
4
4
  supportingPackages?: string[];
@@ -6,8 +6,10 @@ export declare function minimalReviewFocalPoint(anchors: string[], opts?: {
6
6
  }): ReviewFocalPoint;
7
7
  export declare function emptyReachScope(): ReachScope;
8
8
  export declare function reachScopeFor(packages: string[], maxBucket?: ReachScope["maxBucket"]): ReachScope;
9
+ export declare function emptyArtifactGrounded(): ArtifactGroundedVerificationScope;
10
+ export declare function artifactGroundedScopeFor(packages: string[], focus?: string[], artifactPaths?: string[]): ArtifactGroundedVerificationScope;
9
11
  export declare function emptyVerificationScope(): VerificationScope;
10
- export declare function verificationScopeFor(packages: string[], focus?: string[]): VerificationScope;
12
+ export declare function verificationScopeFor(packages: string[], focus?: string[], artifactGrounded?: ArtifactGroundedVerificationScope): VerificationScope;
11
13
  /** Attach focal/scope fields required by assessmentSchema ABI 2. */
12
14
  export declare function withAssessmentScope(assessment: Omit<Assessment, "reviewFocalPoint" | "reachScope" | "verificationScope">, scope: {
13
15
  reviewFocalPoint: ReviewFocalPoint;
@@ -1 +1 @@
1
- {"version":3,"file":"assessmentScopeFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/assessmentScopeFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,sBAAsB,EACtB,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,CAAC,EAAE;IACL,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,GACA,gBAAgB,CAoBlB;AAED,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,GAAE,UAAU,CAAC,WAAW,CAAc,GAC9C,UAAU,CAEZ;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAE1D;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAAE,EAClB,KAAK,GAAE,MAAM,EAAO,GACnB,iBAAiB,CAEnB;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,IAAI,CACd,UAAU,EACV,kBAAkB,GAAG,YAAY,GAAG,mBAAmB,CACxD,EACD,KAAK,EAAE;IACL,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC,GACA,UAAU,CAOZ"}
1
+ {"version":3,"file":"assessmentScopeFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/assessmentScopeFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iCAAiC,EACjC,UAAU,EACV,sBAAsB,EACtB,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,CAAC,EAAE;IACL,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC,GACA,gBAAgB,CAoBlB;AAED,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAAE,EAClB,SAAS,GAAE,UAAU,CAAC,WAAW,CAAc,GAC9C,UAAU,CAEZ;AAED,wBAAgB,qBAAqB,IAAI,iCAAiC,CAEzE;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAAE,EAClB,KAAK,GAAE,MAAM,EAAO,EACpB,aAAa,GAAE,MAAM,EAAO,GAC3B,iCAAiC,CAEnC;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAM1D;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAAE,EAClB,KAAK,GAAE,MAAM,EAAO,EACpB,gBAAgB,GAAE,iCAA2D,GAC5E,iBAAiB,CAEnB;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,IAAI,CACd,UAAU,EACV,kBAAkB,GAAG,YAAY,GAAG,mBAAmB,CACxD,EACD,KAAK,EAAE;IACL,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC,GACA,UAAU,CAOZ"}
@@ -25,11 +25,21 @@ export function emptyReachScope() {
25
25
  export function reachScopeFor(packages, maxBucket = "moderate") {
26
26
  return { packages, maxBucket };
27
27
  }
28
+ export function emptyArtifactGrounded() {
29
+ return { packages: [], focus: [], artifactPaths: [] };
30
+ }
31
+ export function artifactGroundedScopeFor(packages, focus = [], artifactPaths = []) {
32
+ return { packages, focus, artifactPaths };
33
+ }
28
34
  export function emptyVerificationScope() {
29
- return { packages: [], focus: [] };
35
+ return {
36
+ packages: [],
37
+ focus: [],
38
+ artifactGrounded: emptyArtifactGrounded(),
39
+ };
30
40
  }
31
- export function verificationScopeFor(packages, focus = []) {
32
- return { packages, focus };
41
+ export function verificationScopeFor(packages, focus = [], artifactGrounded = emptyArtifactGrounded()) {
42
+ return { packages, focus, artifactGrounded };
33
43
  }
34
44
  /** Attach focal/scope fields required by assessmentSchema ABI 2. */
35
45
  export function withAssessmentScope(assessment, scope) {
@@ -1,4 +1,16 @@
1
- import type { ScanResult } from "../types.js";
1
+ import type { LayerScores, ScanResult } from "../types.js";
2
+ type Abi4SplitScores = {
3
+ prRiskScore: number;
4
+ repositoryHealthScore: number;
5
+ layerScores?: LayerScores;
6
+ };
7
+ /**
8
+ * Attach differentiated ABI-4 prRisk + repositoryHealth wire blocks.
9
+ * `totalScore` mirrors repository health for legacy readers; presentation must
10
+ * consume `prRisk.score` via `deriveRiskSignals()`.
11
+ */
12
+ export declare function withAbi4SplitScores<T extends ScanResult>(result: T, scores: Abi4SplitScores): T;
2
13
  /** Attach ABI-4 prRisk + repositoryHealth wire blocks for strict engine validation tests. */
3
14
  export declare function withAbi4EngineScores<T extends ScanResult>(result: T): T;
15
+ export {};
4
16
  //# sourceMappingURL=engineAbi4Fixtures.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engineAbi4Fixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/engineAbi4Fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEvE"}
1
+ {"version":3,"file":"engineAbi4Fixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/engineAbi4Fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG3D,KAAK,eAAe,GAAG;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,UAAU,EACtD,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,eAAe,GACtB,CAAC,CAeH;AAED,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAEvE"}
@@ -1,4 +1,25 @@
1
1
  import { normalizeEngineOutputAbi4 } from "../scanResultSchema.js";
2
+ /**
3
+ * Attach differentiated ABI-4 prRisk + repositoryHealth wire blocks.
4
+ * `totalScore` mirrors repository health for legacy readers; presentation must
5
+ * consume `prRisk.score` via `deriveRiskSignals()`.
6
+ */
7
+ export function withAbi4SplitScores(result, scores) {
8
+ const layerScores = scores.layerScores ?? result.layerScores;
9
+ return {
10
+ ...result,
11
+ totalScore: scores.repositoryHealthScore,
12
+ layerScores,
13
+ prRisk: {
14
+ score: scores.prRiskScore,
15
+ layerScores: result.prRisk?.layerScores ?? layerScores,
16
+ },
17
+ repositoryHealth: {
18
+ totalScore: scores.repositoryHealthScore,
19
+ layerScores,
20
+ },
21
+ };
22
+ }
2
23
  /** Attach ABI-4 prRisk + repositoryHealth wire blocks for strict engine validation tests. */
3
24
  export function withAbi4EngineScores(result) {
4
25
  return normalizeEngineOutputAbi4(result);
package/dist/index.d.ts CHANGED
@@ -29,6 +29,6 @@ export * from "./trustedScanGuards.js";
29
29
  export * from "./actionsStepSummary.js";
30
30
  export * from "./productMessaging.js";
31
31
  export * from "./presentation/index.js";
32
- export { emptyReachScope, emptyVerificationScope, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./fixtures/assessmentScopeFixtures.js";
32
+ export { emptyReachScope, emptyVerificationScope, emptyArtifactGrounded, artifactGroundedScopeFor, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./fixtures/assessmentScopeFixtures.js";
33
33
  export { assessmentStub, assessmentTypescriptPatch, } from "./fixtures/assessmentFixtures.js";
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EAAE,wCAAwC,EAAE,MAAM,6BAA6B,CAAC;AACvF,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,cAAc,EACd,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EAAE,wCAAwC,EAAE,MAAM,6BAA6B,CAAC;AACvF,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,cAAc,EACd,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC"}
package/dist/index.js CHANGED
@@ -29,5 +29,5 @@ export * from "./trustedScanGuards.js";
29
29
  export * from "./actionsStepSummary.js";
30
30
  export * from "./productMessaging.js";
31
31
  export * from "./presentation/index.js";
32
- export { emptyReachScope, emptyVerificationScope, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./fixtures/assessmentScopeFixtures.js";
32
+ export { emptyReachScope, emptyVerificationScope, emptyArtifactGrounded, artifactGroundedScopeFor, minimalReviewFocalPoint, reachScopeFor, verificationScopeFor, withAssessmentScope, } from "./fixtures/assessmentScopeFixtures.js";
33
33
  export { assessmentStub, assessmentTypescriptPatch, } from "./fixtures/assessmentFixtures.js";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prRiskWiringRegression.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prRiskWiringRegression.test.d.ts","sourceRoot":"","sources":["../src/prRiskWiringRegression.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
3
+ import { resolveRepositoryHealthScore } from "./prRiskWire.js";
4
+ import { buildScanPresentationBundle } from "./presentation/orchestration/buildScanPresentationBundle.js";
5
+ import { presentDashboardCard } from "./presentation/presenters/presentDashboardCard.js";
6
+ import { presentScanDetails } from "./presentation/presenters/presentScanDetails.js";
7
+ import { presentCliScanSummary } from "./presentation/presenters/presentCliScanSummary.js";
8
+ import { presentGitHubCheckRun } from "./presentation/presenters/presentGitHubCheckRun.js";
9
+ import { presentGitHubPrComment } from "./presentation/presenters/presentGitHubPrComment.js";
10
+ import { scanResultFastifyRuntime, scanResultTypescriptPatch, } from "./presentation/fixtures/scanResultFixtures.js";
11
+ import { scanResultPrettier } from "./presentation/fixtures/presentationPersonaFixtures.js";
12
+ const SCAN_ID = "22222222-2222-4222-8222-222222222222";
13
+ const ORIGIN = "https://app.example.com";
14
+ const validationPrShapes = [
15
+ {
16
+ id: "PR #27 fastify",
17
+ result: scanResultFastifyRuntime,
18
+ expectedPrRisk: 55,
19
+ expectedRepositoryHealth: 63,
20
+ },
21
+ {
22
+ id: "PR #28 typescript",
23
+ result: scanResultTypescriptPatch,
24
+ expectedPrRisk: 30,
25
+ expectedRepositoryHealth: 63,
26
+ },
27
+ {
28
+ id: "PR #29 prettier",
29
+ result: scanResultPrettier,
30
+ expectedPrRisk: 30,
31
+ expectedRepositoryHealth: 61,
32
+ },
33
+ ];
34
+ function surfacesFor(result) {
35
+ const bundle = buildScanPresentationBundle({
36
+ result,
37
+ pipelineStatus: "done",
38
+ });
39
+ return {
40
+ bundle,
41
+ card: presentDashboardCard(bundle),
42
+ details: presentScanDetails(bundle, { scanId: SCAN_ID }),
43
+ cli: presentCliScanSummary(bundle, { repoLabel: "mergesignal/app" }),
44
+ check: presentGitHubCheckRun(bundle, {
45
+ scanId: SCAN_ID,
46
+ webAppOrigin: ORIGIN,
47
+ }),
48
+ comment: presentGitHubPrComment(bundle),
49
+ };
50
+ }
51
+ describe("PR Risk wiring regression (Investigation 1)", () => {
52
+ it.each(validationPrShapes)("$id: riskIndex follows prRisk.score, not repository health", ({ result, expectedPrRisk, expectedRepositoryHealth }) => {
53
+ expect(result.prRisk?.score).toBe(expectedPrRisk);
54
+ expect(result.repositoryHealth?.totalScore).toBe(expectedRepositoryHealth);
55
+ expect(result.totalScore).toBe(expectedRepositoryHealth);
56
+ expect(resolveRepositoryHealthScore(result)).toBe(expectedRepositoryHealth);
57
+ const facts = deriveScanNarrative(result);
58
+ expect(facts.riskSignals?.riskIndex).toBe(expectedPrRisk);
59
+ expect(facts.riskIndex).toBe(expectedPrRisk);
60
+ const { card, details, cli, check, comment } = surfacesFor(result);
61
+ expect(card.sortKey.riskIndex).toBe(expectedPrRisk);
62
+ expect(details.hero.prRiskScore).toBe(expectedPrRisk);
63
+ expect(details.signalSummary?.prRiskScore).toBe(expectedPrRisk);
64
+ expect(cli.metrics?.prRiskScore).toBe(expectedPrRisk);
65
+ expect(cli.metrics?.riskIndex).toBe(expectedPrRisk);
66
+ const prRiskSection = check.sections.find((s) => s.title === "PR Risk");
67
+ expect(prRiskSection?.bullets[0]).toMatch(new RegExp(`^${expectedPrRisk} \\(`));
68
+ expect(comment.introLines.some((l) => l.startsWith("PR Risk:"))).toBe(true);
69
+ expect(comment.introLines.find((l) => l.startsWith("PR Risk:"))).toContain(String(expectedPrRisk));
70
+ });
71
+ it("same repository health with different PR evidence yields different riskIndex", () => {
72
+ const fastify = surfacesFor(scanResultFastifyRuntime);
73
+ const typescript = surfacesFor(scanResultTypescriptPatch);
74
+ expect(resolveRepositoryHealthScore(scanResultFastifyRuntime)).toBe(resolveRepositoryHealthScore(scanResultTypescriptPatch));
75
+ expect(fastify.card.sortKey.riskIndex).not.toBe(typescript.card.sortKey.riskIndex);
76
+ expect(fastify.card.sortKey.riskIndex).toBe(55);
77
+ expect(typescript.card.sortKey.riskIndex).toBe(30);
78
+ });
79
+ it("historical fallback: missing prRisk uses totalScore for riskIndex", () => {
80
+ const historical = {
81
+ totalScore: 48,
82
+ layerScores: {
83
+ security: 10,
84
+ maintainability: 12,
85
+ ecosystem: 14,
86
+ upgradeImpact: 12,
87
+ },
88
+ findings: [],
89
+ generatedAt: "2026-01-01T00:00:00.000Z",
90
+ assessment: scanResultTypescriptPatch.assessment,
91
+ decision: scanResultTypescriptPatch.decision,
92
+ };
93
+ const facts = deriveScanNarrative(historical);
94
+ expect(facts.riskSignals?.riskIndex).toBe(48);
95
+ expect(facts.riskIndex).toBe(48);
96
+ const bundle = buildScanPresentationBundle({
97
+ result: historical,
98
+ pipelineStatus: "done",
99
+ });
100
+ expect(presentDashboardCard(bundle).sortKey.riskIndex).toBe(48);
101
+ });
102
+ });
@@ -1,4 +1,5 @@
1
1
  import type { MergeConcernKind, NarrativeIntensity, ReachVisibility } from "@mergesignal/contracts";
2
+ import type { VerificationChannel } from "../../assessmentProjection.js";
2
3
  /** Assessment-aligned fields projected identically across surfaces. */
3
4
  export type AssessmentPresentationFields = {
4
5
  posture: "safe" | "needs_review" | "risky";
@@ -6,6 +7,7 @@ export type AssessmentPresentationFields = {
6
7
  factors: string[];
7
8
  reasoning: string[];
8
9
  verificationFocus: string[];
10
+ verificationChannel: VerificationChannel;
9
11
  reachVisibility: ReachVisibility;
10
12
  narrativeIntensity: NarrativeIntensity;
11
13
  };
@@ -1 +1 @@
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"}
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;AAChC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,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,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,eAAe,EAAE,eAAe,CAAC;IACjC,kBAAkB,EAAE,kBAAkB,CAAC;CACxC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"presentationPersonaFixtures.d.ts","sourceRoot":"","sources":["../../../src/presentation/fixtures/presentationPersonaFixtures.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAkBjD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AA+KjC,eAAO,MAAM,gBAAgB,EAAE,UAG7B,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,UAG/B,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,UAG7B,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,UAI/B,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,UAa7B,CAAC;AAEH,eAAO,MAAM,qBAAqB,EAAE,UAMnC,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,UAO9C,CAAC;AAEF,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,CAAC"}
1
+ {"version":3,"file":"presentationPersonaFixtures.d.ts","sourceRoot":"","sources":["../../../src/presentation/fixtures/presentationPersonaFixtures.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAmBjD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AA+KjC,eAAO,MAAM,gBAAgB,EAAE,UAG7B,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,UAShC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAG7B,CAAC;AAEH,eAAO,MAAM,kBAAkB,EAAE,UAI/B,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,UAa7B,CAAC;AAEH,eAAO,MAAM,qBAAqB,EAAE,UAMnC,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,UAO9C,CAAC;AAEF,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { assessmentBullmq, assessmentEslint, assessmentMixedTypescriptFastify, assessmentNextAuth, assessmentPrettier, assessmentUnknownSafe, assessmentVitest, } from "../../fixtures/assessmentFixtures.js";
2
2
  import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceTypescript, } from "../../fixtures/repoIntelligenceFixtures.js";
3
+ import { withAbi4SplitScores } from "../../fixtures/engineAbi4Fixtures.js";
3
4
  import { analysisPreparationWithValidRepoIntel } from "../../fixtures/repoIntelligenceTestHelpers.js";
4
5
  import { scanResultFastifyRuntime, scanResultLimitedContext, scanResultTypescriptPatch, } from "./scanResultFixtures.js";
5
6
  const basePrep = analysisPreparationWithValidRepoIntel();
@@ -165,9 +166,12 @@ export const scanResultEslint = scanBase(assessmentEslint, {
165
166
  changedPackages: ["eslint"],
166
167
  repoIntelligence: fixtureRepoIntelligenceEslint,
167
168
  });
168
- export const scanResultPrettier = scanBase(assessmentPrettier, {
169
+ export const scanResultPrettier = withAbi4SplitScores(scanBase(assessmentPrettier, {
169
170
  changedPackages: ["prettier"],
170
171
  repoIntelligence: fixtureRepoIntelligencePrettier,
172
+ }), {
173
+ prRiskScore: 30,
174
+ repositoryHealthScore: 61,
171
175
  });
172
176
  export const scanResultVitest = scanBase(assessmentVitest, {
173
177
  changedPackages: ["vitest"],
@@ -1 +1 @@
1
- {"version":3,"file":"scanResultFixtures.d.ts","sourceRoot":"","sources":["../../../src/presentation/fixtures/scanResultFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAYjD,eAAO,MAAM,wBAAwB,EAAE,UA0CtC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,UA2BvC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAqCtC,CAAC"}
1
+ {"version":3,"file":"scanResultFixtures.d.ts","sourceRoot":"","sources":["../../../src/presentation/fixtures/scanResultFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAoBjD,eAAO,MAAM,wBAAwB,EAAE,UA4CtC,CAAC;AASF,eAAO,MAAM,yBAAyB,EAAE,UA6BvC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAqCtC,CAAC"}
@@ -1,14 +1,16 @@
1
+ import { withAbi4SplitScores } from "../../fixtures/engineAbi4Fixtures.js";
1
2
  import { assessmentFastifyRuntime, assessmentLimitedContext, assessmentTypescriptPatch, } from "../../fixtures/assessmentFixtures.js";
2
3
  import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceTypescript, } from "../../fixtures/repoIntelligenceFixtures.js";
3
4
  import { analysisPreparationWithValidRepoIntel } from "../../fixtures/repoIntelligenceTestHelpers.js";
4
- export const scanResultFastifyRuntime = {
5
- totalScore: 55,
6
- layerScores: {
7
- security: 10,
8
- maintainability: 20,
9
- ecosystem: 30,
10
- upgradeImpact: 15,
11
- },
5
+ const fastifyLayerScores = {
6
+ security: 10,
7
+ maintainability: 20,
8
+ ecosystem: 30,
9
+ upgradeImpact: 15,
10
+ };
11
+ export const scanResultFastifyRuntime = withAbi4SplitScores({
12
+ totalScore: 63,
13
+ layerScores: fastifyLayerScores,
12
14
  findings: [
13
15
  {
14
16
  id: "finding-fastify-runtime",
@@ -43,15 +45,20 @@ export const scanResultFastifyRuntime = {
43
45
  remediation: "Run auth smoke tests",
44
46
  },
45
47
  ],
48
+ }, {
49
+ prRiskScore: 55,
50
+ repositoryHealthScore: 63,
51
+ layerScores: fastifyLayerScores,
52
+ });
53
+ const typescriptLayerScores = {
54
+ security: 5,
55
+ maintainability: 8,
56
+ ecosystem: 10,
57
+ upgradeImpact: 5,
46
58
  };
47
- export const scanResultTypescriptPatch = {
48
- totalScore: 18,
49
- layerScores: {
50
- security: 5,
51
- maintainability: 8,
52
- ecosystem: 10,
53
- upgradeImpact: 5,
54
- },
59
+ export const scanResultTypescriptPatch = withAbi4SplitScores({
60
+ totalScore: 63,
61
+ layerScores: typescriptLayerScores,
55
62
  findings: [],
56
63
  generatedAt: "2026-01-01T00:00:00.000Z",
57
64
  changedPackages: ["typescript"],
@@ -71,7 +78,11 @@ export const scanResultTypescriptPatch = {
71
78
  edges: 340,
72
79
  hotspots: [],
73
80
  },
74
- };
81
+ }, {
82
+ prRiskScore: 30,
83
+ repositoryHealthScore: 63,
84
+ layerScores: typescriptLayerScores,
85
+ });
75
86
  export const scanResultLimitedContext = {
76
87
  totalScore: 40,
77
88
  layerScores: {
@@ -67,7 +67,9 @@ describe("presentDashboardCard personas", () => {
67
67
  expect(card.verdict?.posture).toBe("safe");
68
68
  expect(card.headline).toMatch(/patch upgrade/i);
69
69
  expect(card.headline).not.toMatch(/needs review/i);
70
- expect(card.verification.length).toBe(0);
70
+ expect(card.verification.length).toBeGreaterThan(0);
71
+ expect(card.verificationChannel).toBe("artifact");
72
+ expect(card.verification.some((v) => v.toLowerCase().includes("typecheck"))).toBe(true);
71
73
  });
72
74
  });
73
75
  describe("presentScanDetails usage paths", () => {
@@ -16,6 +16,7 @@ const personas = [
16
16
  density: "minimal",
17
17
  headlineMatch: /patch upgrade/i,
18
18
  headlineMustNot: /needs review/i,
19
+ verificationIncludes: ["typecheck"],
19
20
  },
20
21
  {
21
22
  name: "eslint",
@@ -34,6 +35,7 @@ const personas = [
34
35
  density: "minimal",
35
36
  headlineMatch: /patch upgrade/i,
36
37
  headlineMustNot: /needs review/i,
38
+ verificationIncludes: ["format"],
37
39
  },
38
40
  {
39
41
  name: "vitest",
@@ -8,7 +8,7 @@ export function presentCliScanSummary(bundle, ctx = {}) {
8
8
  const layerLine = layerScores
9
9
  ? `security=${layerScores.security} maintainability=${layerScores.maintainability} ecosystem=${layerScores.ecosystem} upgradeImpact=${layerScores.upgradeImpact}`
10
10
  : "";
11
- const prRiskScore = facts.riskIndex ?? 0;
11
+ const prRiskScore = facts.riskSignals?.riskIndex ?? facts.riskIndex ?? 0;
12
12
  return {
13
13
  ...projectAssessmentFields(bundle),
14
14
  header: {
@@ -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,sCAAsC,IAAI,yBAAyB,CAelF;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,CA+C3B;AAED,wBAAgB,sCAAsC,IAAI,yBAAyB,CAelF;AAED,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAC9C,yBAAyB,CAqB3B"}
@@ -59,8 +59,9 @@ export function presentDashboardCard(bundle) {
59
59
  const { facts, profile } = bundle;
60
60
  const channels = buildNarrativeChannels(bundle);
61
61
  const posture = profile.status;
62
- const riskIndex = facts.riskIndex != null && Number.isFinite(facts.riskIndex)
63
- ? facts.riskIndex
62
+ const riskIndex = facts.riskSignals?.riskIndex != null &&
63
+ Number.isFinite(facts.riskSignals.riskIndex)
64
+ ? facts.riskSignals.riskIndex
64
65
  : -1;
65
66
  const insights = channels.insights.slice(0, INSIGHTS_CAP);
66
67
  const scopeAreas = channels.scopeAreas.length > 0
@@ -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,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
+ {"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;AAoDpE,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,4BAA4B,EAC1C,MAAM,EAAE,UAAU,GACjB,mBAAmB,CAuBrB"}
@@ -1,7 +1,7 @@
1
1
  import { narrativeIntensityToDensity } from "../../assessmentPresentationUtils.js";
2
2
  import { safeParseRepoIntelligence } from "../../repoIntelligenceSchema.js";
3
3
  import { scanSurfaceCopy } from "../../scanSurfaceCopy.js";
4
- import { collectVerificationFocus } from "../../assessmentProjection.js";
4
+ import { collectVerificationFocusForPresentation } from "../../assessmentProjection.js";
5
5
  function focalAnchorPackage(assessment) {
6
6
  const anchors = assessment.reviewFocalPoint.anchors;
7
7
  if (!anchors.length)
@@ -14,7 +14,7 @@ function focalAnchorPackage(assessment) {
14
14
  return token;
15
15
  }
16
16
  function buildInterpretation(assessment, presentation, result) {
17
- const verificationLabels = collectVerificationFocus(result);
17
+ const { focus: verificationLabels } = collectVerificationFocusForPresentation(presentation, result);
18
18
  const anchorPackage = focalAnchorPackage(assessment);
19
19
  const suppressRuntimeNarrative = presentation.reachVisibility === "hidden";
20
20
  let intent = "unknown_upgrade";
@@ -1 +1 @@
1
- {"version":3,"file":"projectAssessmentFields.d.ts","sourceRoot":"","sources":["../../src/presentation/projectAssessmentFields.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAExF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,sBAAsB,GAC7B,4BAA4B,CAW9B"}
1
+ {"version":3,"file":"projectAssessmentFields.d.ts","sourceRoot":"","sources":["../../src/presentation/projectAssessmentFields.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAExF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,sBAAsB,GAC7B,4BAA4B,CAgB9B"}
@@ -1,12 +1,14 @@
1
- import { collectVerificationFocus, projectReasoningLines, } from "../assessmentProjection.js";
1
+ import { collectVerificationFocusForPresentation, projectReasoningLines, } from "../assessmentProjection.js";
2
2
  export function projectAssessmentFields(bundle) {
3
3
  const { assessment, presentation, result } = bundle;
4
+ const { channel, focus } = collectVerificationFocusForPresentation(presentation, result);
4
5
  return {
5
6
  posture: assessment.posture,
6
7
  primaryConcern: assessment.primaryConcern,
7
8
  factors: [...assessment.factors],
8
9
  reasoning: projectReasoningLines(result),
9
- verificationFocus: collectVerificationFocus(result),
10
+ verificationFocus: focus,
11
+ verificationChannel: channel,
10
12
  reachVisibility: presentation.reachVisibility,
11
13
  narrativeIntensity: presentation.narrativeIntensity,
12
14
  };
@@ -85,6 +85,10 @@ describe("surfaceParity guardrail", () => {
85
85
  expect(s.details.verificationFocus).toEqual(s.fields.verificationFocus);
86
86
  expect(s.check.verificationFocus).toEqual(s.fields.verificationFocus);
87
87
  expect(s.comment.verificationFocus).toEqual(s.fields.verificationFocus);
88
+ expect(s.card.verificationChannel).toBe(s.fields.verificationChannel);
89
+ expect(s.details.verificationChannel).toBe(s.fields.verificationChannel);
90
+ expect(s.check.verificationChannel).toBe(s.fields.verificationChannel);
91
+ expect(s.comment.verificationChannel).toBe(s.fields.verificationChannel);
88
92
  });
89
93
  it(`${persona.name}: headline matches across card and details`, () => {
90
94
  const s = surfacesFor(persona.result);
@@ -133,6 +137,8 @@ describe("surfaceParity guardrail", () => {
133
137
  const ch26 = channelsById.get("validation-pr-26");
134
138
  expect(ch28.reachLabel).toBeUndefined();
135
139
  expect(ch29.reachLabel).toBeUndefined();
140
+ expect(ch28.verification.length).toBeGreaterThan(0);
141
+ expect(ch29.verification.length).toBeGreaterThan(0);
136
142
  expect(ch27.verification.length).toBeGreaterThan(0);
137
143
  expect(ch30.verification.length).toBeGreaterThan(0);
138
144
  expect(ch27.verification).not.toEqual(ch30.verification);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=verificationChannelParity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verificationChannelParity.test.d.ts","sourceRoot":"","sources":["../../src/presentation/verificationChannelParity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
3
+ import { presentDashboardCard } from "./presenters/presentDashboardCard.js";
4
+ import { presentScanDetails } from "./presenters/presentScanDetails.js";
5
+ import { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
6
+ import { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
7
+ import { projectAssessmentFields } from "./projectAssessmentFields.js";
8
+ import { scanResultFastifyRuntime, scanResultPrettier, scanResultTypescriptPatch, } from "./fixtures/presentationPersonaFixtures.js";
9
+ const SCAN_ID = "22222222-2222-4222-8222-222222222222";
10
+ const ORIGIN = "https://app.example.com";
11
+ describe("verification channel provenance parity", () => {
12
+ for (const persona of [
13
+ {
14
+ name: "fastify runtime",
15
+ result: scanResultFastifyRuntime,
16
+ channel: "runtime",
17
+ focusIncludes: "routes",
18
+ },
19
+ {
20
+ name: "typescript patch",
21
+ result: scanResultTypescriptPatch,
22
+ channel: "artifact",
23
+ focusIncludes: "typecheck",
24
+ },
25
+ {
26
+ name: "prettier",
27
+ result: scanResultPrettier,
28
+ channel: "artifact",
29
+ focusIncludes: "format",
30
+ },
31
+ ]) {
32
+ it(`${persona.name}: verificationChannel and focus match across surfaces`, () => {
33
+ const bundle = buildScanPresentationBundle({
34
+ result: persona.result,
35
+ pipelineStatus: "done",
36
+ });
37
+ const fields = projectAssessmentFields(bundle);
38
+ const card = presentDashboardCard(bundle);
39
+ const details = presentScanDetails(bundle, { scanId: SCAN_ID });
40
+ const check = presentGitHubCheckRun(bundle, {
41
+ scanId: SCAN_ID,
42
+ webAppOrigin: ORIGIN,
43
+ });
44
+ const comment = presentGitHubPrComment(bundle);
45
+ expect(fields.verificationChannel).toBe(persona.channel);
46
+ expect(card.verificationChannel).toBe(persona.channel);
47
+ expect(details.verificationChannel).toBe(persona.channel);
48
+ expect(check.verificationChannel).toBe(persona.channel);
49
+ expect(comment.verificationChannel).toBe(persona.channel);
50
+ expect(fields.verificationFocus).toEqual(card.verificationFocus);
51
+ expect(details.verificationFocus).toEqual(fields.verificationFocus);
52
+ expect(check.verificationFocus).toEqual(fields.verificationFocus);
53
+ expect(comment.verificationFocus).toEqual(fields.verificationFocus);
54
+ expect(fields.verificationFocus.some((f) => f.toLowerCase().includes(persona.focusIncludes.toLowerCase()))).toBe(true);
55
+ });
56
+ }
57
+ });
@@ -35,13 +35,13 @@ 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
+ lint: "lint";
39
+ format: "format";
38
40
  unknown: "unknown";
39
41
  runtime: "runtime";
40
42
  build: "build";
41
43
  test: "test";
42
44
  tooling: "tooling";
43
- lint: "lint";
44
- format: "format";
45
45
  ci: "ci";
46
46
  }>>;
47
47
  packageRole: z.ZodOptional<z.ZodEnum<{
@@ -70,10 +70,10 @@ export declare const repoIntelligenceWireSchema: z.ZodObject<{
70
70
  possible: "possible";
71
71
  }>>;
72
72
  expectedImpact: z.ZodOptional<z.ZodEnum<{
73
+ typecheck: "typecheck";
73
74
  unknown: "unknown";
74
75
  runtime: "runtime";
75
76
  build_time: "build_time";
76
- typecheck: "typecheck";
77
77
  test_time: "test_time";
78
78
  development_only: "development_only";
79
79
  }>>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=riskSignals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"riskSignals.test.d.ts","sourceRoot":"","sources":["../src/riskSignals.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveRiskSignals } from "./riskSignals.js";
3
+ import { resolveRepositoryHealthScore } from "./prRiskWire.js";
4
+ function baseResult(overrides = {}) {
5
+ return {
6
+ totalScore: 63,
7
+ layerScores: {
8
+ security: 10,
9
+ maintainability: 20,
10
+ ecosystem: 30,
11
+ upgradeImpact: 15,
12
+ },
13
+ findings: [],
14
+ generatedAt: "2026-01-01T00:00:00.000Z",
15
+ ...overrides,
16
+ };
17
+ }
18
+ describe("deriveRiskSignals", () => {
19
+ it("prefers prRisk.score over totalScore and repositoryHealth", () => {
20
+ const result = baseResult({
21
+ prRisk: { score: 55 },
22
+ repositoryHealth: { totalScore: 63 },
23
+ totalScore: 63,
24
+ });
25
+ expect(deriveRiskSignals(result)?.riskIndex).toBe(55);
26
+ expect(resolveRepositoryHealthScore(result)).toBe(63);
27
+ });
28
+ it("falls back to totalScore when prRisk is missing (historical scans)", () => {
29
+ const result = baseResult({ totalScore: 48 });
30
+ expect(deriveRiskSignals(result)?.riskIndex).toBe(48);
31
+ });
32
+ });
@@ -58,8 +58,8 @@ describe("scanPresentationBundle golden — proof model", () => {
58
58
  const { facts } = bundle;
59
59
  expect(facts.availability.corpusGateReason).toBe("ok");
60
60
  expect(facts.packageSemantics?.dependencyClass).toBe("tooling");
61
- expect(facts.riskSignals?.riskIndex).toBe(18);
62
- expect(facts.riskSignals?.band).toBe(scoreToBand(18));
61
+ expect(facts.riskSignals?.riskIndex).toBe(30);
62
+ expect(facts.riskSignals?.band).toBe(scoreToBand(30));
63
63
  });
64
64
  it("affected areas always carry linkage arrays (structure)", () => {
65
65
  for (const { result } of validationPersonas) {
@@ -156,10 +156,16 @@ export declare const engineOutputScanResultSchema: z.ZodObject<{
156
156
  very_low: "very_low";
157
157
  moderate: "moderate";
158
158
  }>;
159
+ entryPointFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
159
160
  }, z.core.$strip>;
160
161
  verificationScope: z.ZodObject<{
161
162
  packages: z.ZodArray<z.ZodString>;
162
163
  focus: z.ZodArray<z.ZodString>;
164
+ artifactGrounded: z.ZodOptional<z.ZodObject<{
165
+ packages: z.ZodArray<z.ZodString>;
166
+ focus: z.ZodArray<z.ZodString>;
167
+ artifactPaths: z.ZodArray<z.ZodString>;
168
+ }, z.core.$strip>>;
163
169
  }, z.core.$strip>;
164
170
  posture: z.ZodEnum<{
165
171
  safe: "safe";
@@ -1 +1 @@
1
- {"version":3,"file":"scanResultSchema.d.ts","sourceRoot":"","sources":["../src/scanResultSchema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,wGAAwG;AACxG,eAAO,MAAM,eAAe,EAAG,GAAY,CAAC;AAE5C,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAc/D;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8Bb,CAAC;AAUjB;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMvC,CAAC;AAEH,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,OAAO,GACZ,kCAAkC,GAAG,kCAAkC,CAezE;AAED,uFAAuF;AACvF,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,OAAO,GACZ,uBAAuB,CAMzB;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,GACZ,sBAAsB,GAAG,sBAAsB,CAajD;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAMhE"}
1
+ {"version":3,"file":"scanResultSchema.d.ts","sourceRoot":"","sources":["../src/scanResultSchema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,wGAAwG;AACxG,eAAO,MAAM,eAAe,EAAG,GAAY,CAAC;AAE5C,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAG,GAAY,CAAC;AAmBnD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAc/D;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8Bb,CAAC;AAUjB;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAMvC,CAAC;AAEH,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,OAAO,GACZ,kCAAkC,GAAG,kCAAkC,CAezE;AAED,uFAAuF;AACvF,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,OAAO,GACZ,uBAAuB,CAMzB;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,KAAK,CAAC;IACV,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,UAAU,CAAC;CACpB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,GACZ,sBAAsB,GAAG,sBAAsB,CAajD;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAMhE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mergesignal/shared",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "MergeSignal shared scan contract, presentation policy, and trusted validation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,7 +38,7 @@
38
38
  "test": "vitest run"
39
39
  },
40
40
  "dependencies": {
41
- "@mergesignal/contracts": "0.1.0",
41
+ "@mergesignal/contracts": "catalog:",
42
42
  "zod": "^4.4.3"
43
43
  },
44
44
  "devDependencies": {