@mergesignal/shared 0.8.1 → 0.9.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 (72) hide show
  1. package/dist/deriveScanNarrative.test.js +2 -2
  2. package/dist/fixtures/engineAbi4Fixtures.d.ts +4 -0
  3. package/dist/fixtures/engineAbi4Fixtures.d.ts.map +1 -0
  4. package/dist/fixtures/engineAbi4Fixtures.js +5 -0
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/prRiskBand.d.ts +23 -0
  9. package/dist/prRiskBand.d.ts.map +1 -0
  10. package/dist/prRiskBand.js +66 -0
  11. package/dist/prRiskBand.test.d.ts +2 -0
  12. package/dist/prRiskBand.test.d.ts.map +1 -0
  13. package/dist/prRiskBand.test.js +57 -0
  14. package/dist/prRiskWire.d.ts +57 -0
  15. package/dist/prRiskWire.d.ts.map +1 -0
  16. package/dist/prRiskWire.js +98 -0
  17. package/dist/prRiskWire.test.d.ts +2 -0
  18. package/dist/prRiskWire.test.d.ts.map +1 -0
  19. package/dist/prRiskWire.test.js +106 -0
  20. package/dist/presentGitHubPrComment.d.ts.map +1 -1
  21. package/dist/presentGitHubPrComment.js +0 -1
  22. package/dist/presentation/dto/githubAndCliPresentation.d.ts +3 -0
  23. package/dist/presentation/dto/githubAndCliPresentation.d.ts.map +1 -1
  24. package/dist/presentation/dto/scanDetailsPresentation.d.ts +5 -2
  25. package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -1
  26. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts +2 -1
  27. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -1
  28. package/dist/presentation/orchestration/buildScanCardPresentation.js +7 -6
  29. package/dist/presentation/orchestration/buildScanDetailsPresentation.d.ts +1 -1
  30. package/dist/presentation/orchestration/buildScanDetailsPresentation.d.ts.map +1 -1
  31. package/dist/presentation/orchestration/buildScanDetailsPresentation.js +0 -1
  32. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts +0 -1
  33. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -1
  34. package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -1
  35. package/dist/presentation/presenters/presentCliScanSummary.js +9 -4
  36. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -1
  37. package/dist/presentation/presenters/presentGitHubCheckRun.js +10 -0
  38. package/dist/presentation/presenters/presentGitHubPrComment.d.ts.map +1 -1
  39. package/dist/presentation/presenters/presentGitHubPrComment.js +6 -0
  40. package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -1
  41. package/dist/presentation/presenters/presentScanDetails.js +46 -3
  42. package/dist/presentation/render/renderCliScanSummaryText.d.ts.map +1 -1
  43. package/dist/presentation/render/renderCliScanSummaryText.js +4 -1
  44. package/dist/productMessaging.d.ts +2 -2
  45. package/dist/productMessaging.d.ts.map +1 -1
  46. package/dist/productMessaging.js +3 -3
  47. package/dist/productMessaging.test.js +6 -6
  48. package/dist/resolvePipelineStatus.d.ts +4 -3
  49. package/dist/resolvePipelineStatus.d.ts.map +1 -1
  50. package/dist/resolvePipelineStatus.js +3 -3
  51. package/dist/riskSignals.d.ts +6 -6
  52. package/dist/riskSignals.d.ts.map +1 -1
  53. package/dist/riskSignals.js +9 -10
  54. package/dist/riskVocabulary.js +7 -7
  55. package/dist/scanPresentationBundle.golden.test.js +3 -3
  56. package/dist/scanResultSchema.d.ts +42 -1
  57. package/dist/scanResultSchema.d.ts.map +1 -1
  58. package/dist/scanResultSchema.js +35 -2
  59. package/dist/scanResultSchema.test.js +15 -2
  60. package/dist/scanSurfaceCopy.d.ts +4 -2
  61. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  62. package/dist/scanSurfaceCopy.js +4 -2
  63. package/dist/trustedScanGuards.test.js +3 -2
  64. package/dist/types.d.ts +17 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/formatCardExposureDisplay.d.ts +0 -22
  68. package/dist/formatCardExposureDisplay.d.ts.map +0 -1
  69. package/dist/formatCardExposureDisplay.js +0 -63
  70. package/dist/formatCardExposureDisplay.test.d.ts +0 -2
  71. package/dist/formatCardExposureDisplay.test.d.ts.map +0 -1
  72. package/dist/formatCardExposureDisplay.test.js +0 -44
@@ -1 +1 @@
1
- {"version":3,"file":"buildScanDetailsPresentation.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanDetailsPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAEjF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,iCAAiC,GACvC,uBAAuB,GAAG,IAAI,CAiBhC"}
1
+ {"version":3,"file":"buildScanDetailsPresentation.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanDetailsPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAEjF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,iCAAiC,GACvC,uBAAuB,GAAG,IAAI,CAgBhC"}
@@ -7,7 +7,6 @@ export function buildScanDetailsPresentation(input) {
7
7
  result: input.result,
8
8
  pipelineStatus: input.pipelineStatus,
9
9
  decision: input.decision,
10
- totalScore: input.totalScore,
11
10
  });
12
11
  if (!bundle)
13
12
  return null;
@@ -5,7 +5,6 @@ export type BuildScanPresentationBundleInput = {
5
5
  result: ScanResult;
6
6
  pipelineStatus: PipelineStatus;
7
7
  decision?: string | null;
8
- totalScore?: number | null;
9
8
  };
10
9
  /**
11
10
  * Mandatory orchestration entrypoint for completed-scan presentation.
@@ -1 +1 @@
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
+ {"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;CAC1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,gCAAgC,GACtC,sBAAsB,GAAG,IAAI,CA4B/B"}
@@ -1 +1 @@
1
- {"version":3,"file":"presentCliScanSummary.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentCliScanSummary.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,mBAAmB,CA8BrB"}
1
+ {"version":3,"file":"presentCliScanSummary.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentCliScanSummary.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,mBAAmB,CAiCrB"}
@@ -1,11 +1,14 @@
1
+ import { scoreToBandLabel } from "../../prRiskBand.js";
2
+ import { resolvePrRiskLayerScores } from "../../prRiskWire.js";
1
3
  import { buildNarrativeChannels, composeHeadline, composeSubheadline, composeSupportingContext, composeVerificationActions, evidenceContextFromProfile, projectCompactKeyPoints, } from "../compose/narrativeCompose.js";
2
4
  import { projectAssessmentFields } from "../projectAssessmentFields.js";
3
5
  export function presentCliScanSummary(bundle, ctx = {}) {
4
6
  const { facts, profile, result } = bundle;
5
- const layers = result.layerScores;
6
- const layerLine = layers
7
- ? `security=${layers.security} maintainability=${layers.maintainability} ecosystem=${layers.ecosystem} upgradeImpact=${layers.upgradeImpact}`
7
+ const layerScores = resolvePrRiskLayerScores(result);
8
+ const layerLine = layerScores
9
+ ? `security=${layerScores.security} maintainability=${layerScores.maintainability} ecosystem=${layerScores.ecosystem} upgradeImpact=${layerScores.upgradeImpact}`
8
10
  : "";
11
+ const prRiskScore = facts.riskIndex ?? 0;
9
12
  return {
10
13
  ...projectAssessmentFields(bundle),
11
14
  header: {
@@ -22,7 +25,9 @@ export function presentCliScanSummary(bundle, ctx = {}) {
22
25
  keyPoints: projectCompactKeyPoints(buildNarrativeChannels(bundle), 6),
23
26
  verificationActions: composeVerificationActions(bundle, 5),
24
27
  metrics: {
25
- riskIndex: facts.riskIndex ?? result.totalScore ?? 0,
28
+ prRiskScore,
29
+ prRiskBandLabel: scoreToBandLabel(prRiskScore) ?? undefined,
30
+ riskIndex: prRiskScore,
26
31
  layerLine,
27
32
  findingCount: result.findings?.length ?? 0,
28
33
  recommendationCount: result.recommendations?.length ?? 0,
@@ -1 +1 @@
1
- {"version":3,"file":"presentGitHubCheckRun.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubCheckRun.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAChE,0BAA0B,CAmC5B"}
1
+ {"version":3,"file":"presentGitHubCheckRun.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubCheckRun.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAChE,0BAA0B,CA4C5B"}
@@ -1,3 +1,4 @@
1
+ import { scoreToBandLabel } from "../../prRiskBand.js";
1
2
  import { buildNarrativeChannels, composeHeadline, composeSubheadline, composeVerificationActions, evidenceContextFromProfile, projectCompactKeyPoints, } from "../compose/narrativeCompose.js";
2
3
  import { projectAssessmentFields } from "../projectAssessmentFields.js";
3
4
  export function presentGitHubCheckRun(bundle, ctx) {
@@ -12,6 +13,15 @@ export function presentGitHubCheckRun(bundle, ctx) {
12
13
  ? "failure"
13
14
  : "neutral";
14
15
  const sections = [];
16
+ const prRiskScore = bundle.facts.riskSignals?.riskIndex;
17
+ const prRiskBandLabelText = scoreToBandLabel(prRiskScore);
18
+ if (prRiskScore != null && prRiskBandLabelText) {
19
+ sections.push({
20
+ id: "why",
21
+ title: "PR Risk",
22
+ bullets: [`${prRiskScore} (${prRiskBandLabelText})`],
23
+ });
24
+ }
15
25
  if (keyPoints.length > 0) {
16
26
  sections.push({ id: "why", title: "Why", bullets: keyPoints });
17
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presentGitHubPrComment.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubPrComment.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,2BAA2B,CA8B7B"}
1
+ {"version":3,"file":"presentGitHubPrComment.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubPrComment.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,2BAA2B,CAmC7B"}
@@ -1,4 +1,5 @@
1
1
  import { MERGE_POSTURE_LABEL } from "../../riskVocabulary.js";
2
+ import { scoreToBandLabel } from "../../prRiskBand.js";
2
3
  import { buildNarrativeChannels, evidenceContextFromProfile, } from "../compose/narrativeCompose.js";
3
4
  import { formatChangedPackagesShort } from "../../narrativePresentation.js";
4
5
  import { projectAssessmentFields } from "../projectAssessmentFields.js";
@@ -10,6 +11,11 @@ export function presentGitHubPrComment(bundle) {
10
11
  const changed = formatChangedPackagesShort(facts, 3);
11
12
  if (changed)
12
13
  introLines.push(`Changed: ${changed}`);
14
+ const prRiskScore = bundle.facts.riskSignals?.riskIndex;
15
+ const prRiskBandLabelText = scoreToBandLabel(prRiskScore);
16
+ if (prRiskScore != null && prRiskBandLabelText) {
17
+ introLines.push(`PR Risk: ${prRiskScore} (${prRiskBandLabelText})`);
18
+ }
13
19
  for (const line of assessmentFields.reasoning.slice(0, 2)) {
14
20
  introLines.push(line);
15
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presentScanDetails.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentScanDetails.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAmDF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,yBAAyB,GAC7B,uBAAuB,CAwEzB"}
1
+ {"version":3,"file":"presentScanDetails.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentScanDetails.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAGzF,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AA6FF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,yBAAyB,GAC7B,uBAAuB,CAiFzB"}
@@ -1,10 +1,46 @@
1
1
  import { MERGE_CONCERN_LABELS } from "../../assessmentLabels.js";
2
2
  import { normalizedPackageUsagePaths } from "../../narrativePresentation.js";
3
+ import { prRiskBandLabel, prRiskBandToGaugeBand, scoreToBandLabel, } from "../../prRiskBand.js";
3
4
  import { MERGE_POSTURE_LABEL } from "../../riskVocabulary.js";
4
5
  import { buildNarrativeChannels, composeSubheadline, composeSupportingContext, evidenceContextFromProfile, projectCompactKeyPoints, } from "../compose/narrativeCompose.js";
5
6
  import { normalizeGeneratedText, normalizeGeneratedTextNullable, } from "../../normalizeGeneratedText.js";
6
7
  import { scanSurfaceCopy } from "../../scanSurfaceCopy.js";
7
8
  import { projectAssessmentFields } from "../projectAssessmentFields.js";
9
+ const LAYER_LABEL = {
10
+ security: "Security",
11
+ maintainability: "Maintainability",
12
+ ecosystem: "Ecosystem",
13
+ upgradeImpact: "Upgrade impact",
14
+ };
15
+ function gaugeBandToSignalBand(band) {
16
+ if (band === "high")
17
+ return "high";
18
+ if (band === "moderate")
19
+ return "medium";
20
+ return "low";
21
+ }
22
+ function mapSignalSummary(bundle) {
23
+ const riskSignals = bundle.facts.riskSignals;
24
+ const prRiskScore = riskSignals?.riskIndex;
25
+ if (!riskSignals || prRiskScore == null || !Number.isFinite(prRiskScore)) {
26
+ return undefined;
27
+ }
28
+ const band = riskSignals.band;
29
+ if (!band)
30
+ return undefined;
31
+ const gaugeBand = prRiskBandToGaugeBand(band);
32
+ const signalBand = gaugeBandToSignalBand(gaugeBand);
33
+ return {
34
+ prRiskScore,
35
+ band: signalBand,
36
+ layers: riskSignals.layers.map((layer) => ({
37
+ layer: layer.layer,
38
+ score: layer.score,
39
+ band: gaugeBandToSignalBand(prRiskBandToGaugeBand(layer.band)),
40
+ label: LAYER_LABEL[layer.layer],
41
+ })),
42
+ };
43
+ }
8
44
  function mapUsage(bundle) {
9
45
  const { facts } = bundle;
10
46
  if (facts.packageUsage.length === 0 && facts.frameworks.length === 0) {
@@ -43,7 +79,7 @@ function mapEvidence(bundle) {
43
79
  };
44
80
  }
45
81
  export function presentScanDetails(bundle, ctx) {
46
- const { assessment, facts, profile, result } = bundle;
82
+ const { assessment, facts, profile } = bundle;
47
83
  const assessmentFields = projectAssessmentFields(bundle);
48
84
  const channels = buildNarrativeChannels(bundle);
49
85
  const keyPoints = projectCompactKeyPoints(channels, 6);
@@ -74,6 +110,10 @@ export function presentScanDetails(bundle, ctx) {
74
110
  : "low",
75
111
  rationale: action.detail,
76
112
  }));
113
+ const prRiskScore = facts.riskSignals?.riskIndex ?? null;
114
+ const prRiskBandLabelText = facts.riskSignals?.band != null
115
+ ? prRiskBandLabel(facts.riskSignals.band)
116
+ : scoreToBandLabel(prRiskScore);
77
117
  return {
78
118
  ...assessmentFields,
79
119
  evidenceContext: evidenceContextFromProfile(bundle),
@@ -87,8 +127,11 @@ export function presentScanDetails(bundle, ctx) {
87
127
  verdictLine: normalizeGeneratedText(verdictLine),
88
128
  scopeChip: channels.reachLabel,
89
129
  postureLabel,
90
- riskIndex: result.totalScore,
130
+ prRiskScore,
131
+ prRiskBandLabel: prRiskBandLabelText ?? undefined,
132
+ riskIndex: prRiskScore,
91
133
  },
134
+ signalSummary: mapSignalSummary(bundle),
92
135
  narrative: {
93
136
  keyPoints: keyPoints.map((k) => normalizeGeneratedText(k)),
94
137
  changedPackages: facts.changedPackages.all,
@@ -102,7 +145,7 @@ export function presentScanDetails(bundle, ctx) {
102
145
  supportingContext,
103
146
  metadata: {
104
147
  scanId: ctx.scanId,
105
- generatedAt: result.generatedAt,
148
+ generatedAt: bundle.result.generatedAt,
106
149
  methodologyVersion: ctx.methodologyVersion,
107
150
  changedPackagesSummary: facts.changedPackages.primary ?? facts.changedPackages.all.join(", "),
108
151
  },
@@ -1 +1 @@
1
- {"version":3,"file":"renderCliScanSummaryText.d.ts","sourceRoot":"","sources":["../../../src/presentation/render/renderCliScanSummaryText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE9E,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CA+BvE"}
1
+ {"version":3,"file":"renderCliScanSummaryText.d.ts","sourceRoot":"","sources":["../../../src/presentation/render/renderCliScanSummaryText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAE9E,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAkCvE"}
@@ -9,7 +9,10 @@ export function renderCliScanSummaryText(p) {
9
9
  lines.push(p.subheadline);
10
10
  lines.push("", `Status: ${p.status} | Density: ${p.density} | Confidence: ${p.confidence}`);
11
11
  if (p.metrics) {
12
- lines.push(`Risk index: ${p.metrics.riskIndex} (${scanSurfaceCopy.actions.riskIndexDirectionShort})`, p.metrics.layerLine, `Findings: ${p.metrics.findingCount} | Recommendations: ${p.metrics.recommendationCount}`);
12
+ const bandSuffix = p.metrics.prRiskBandLabel
13
+ ? ` (${p.metrics.prRiskBandLabel})`
14
+ : "";
15
+ lines.push(`${scanSurfaceCopy.actions.prRiskScoreLabel}: ${p.metrics.prRiskScore}${bandSuffix}`, p.metrics.layerLine, `Findings: ${p.metrics.findingCount} | Recommendations: ${p.metrics.recommendationCount}`);
13
16
  }
14
17
  if (p.keyPoints.length > 0) {
15
18
  lines.push("", "Key points:");
@@ -69,8 +69,8 @@ export declare const productMessaging: {
69
69
  readonly intro: "The repository dashboard is a runtime-aware review surface for open upgrade pull requests: triage by merge recommendation, then read operational context on each card.";
70
70
  readonly posture: `Posture indicates the merge recommendation (${string}, ${string}, ${string}).`;
71
71
  readonly exposure: "Reach indicates how prominently affected application paths are surfaced for this upgrade.";
72
- readonly exposureCategoriesLead: "Risk index bands (supporting context) read as one of:";
73
- readonly exposureCategories: readonly string[];
72
+ readonly prRiskBandsLead: "PR Risk bands (supporting context) read as one of:";
73
+ readonly prRiskBands: readonly string[];
74
74
  readonly cardBody: "Below the verdict, scanned cards lead with the changed package, affected areas, verification checks, and blast radius when code intelligence is available - focused on this PR, not the whole repository.";
75
75
  };
76
76
  };
@@ -1 +1 @@
1
- {"version":3,"file":"productMessaging.d.ts","sourceRoot":"","sources":["../src/productMessaging.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmF3B,6FAA6F;;;;;;;;;CAarF,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,gBAAgB;;;;CAOnB,CAAC;AAEX,iEAAiE;AACjE,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAOnD"}
1
+ {"version":3,"file":"productMessaging.d.ts","sourceRoot":"","sources":["../src/productMessaging.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmF3B,6FAA6F;;;;;;;;;CAYrF,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,gBAAgB;;;;CAOnB,CAAC;AAEX,iEAAiE;AACjE,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAOnD"}
@@ -1,4 +1,4 @@
1
- import { CARD_EXPOSURE_CATEGORY_LABELS } from "./formatCardExposureDisplay.js";
1
+ import { PR_RISK_BAND_LABELS } from "./prRiskBand.js";
2
2
  import { MERGE_POSTURE_LABEL } from "./riskVocabulary.js";
3
3
  /**
4
4
  * Canonical marketing / category copy. Operational scan strings stay in scanSurfaceCopy.
@@ -84,8 +84,8 @@ export const productMessaging = {
84
84
  intro: "The repository dashboard is a runtime-aware review surface for open upgrade pull requests: triage by merge recommendation, then read operational context on each card.",
85
85
  posture: `Posture indicates the merge recommendation (${MERGE_POSTURE_LABEL.safe}, ${MERGE_POSTURE_LABEL.needs_review}, ${MERGE_POSTURE_LABEL.risky}).`,
86
86
  exposure: "Reach indicates how prominently affected application paths are surfaced for this upgrade.",
87
- exposureCategoriesLead: "Risk index bands (supporting context) read as one of:",
88
- exposureCategories: CARD_EXPOSURE_CATEGORY_LABELS,
87
+ prRiskBandsLead: "PR Risk bands (supporting context) read as one of:",
88
+ prRiskBands: PR_RISK_BAND_LABELS,
89
89
  cardBody: "Below the verdict, scanned cards lead with the changed package, affected areas, verification checks, and blast radius when code intelligence is available - focused on this PR, not the whole repository.",
90
90
  },
91
91
  };
@@ -60,21 +60,21 @@ describe("productMessaging", () => {
60
60
  expect(productMessaging.hero.h1).not.toContain("Dependabot");
61
61
  expect(productMessaging.hero.lead).not.toContain("Dependabot");
62
62
  });
63
- it("describes dashboard posture and exposure without scoring jargon", () => {
63
+ it("describes dashboard posture and PR Risk bands without scoring jargon", () => {
64
64
  const d = productMessaging.dashboardPrCard;
65
65
  const text = [
66
66
  d.intro,
67
67
  d.posture,
68
68
  d.exposure,
69
- d.exposureCategoriesLead,
70
- ...d.exposureCategories,
69
+ d.prRiskBandsLead,
70
+ ...d.prRiskBands,
71
71
  d.cardBody,
72
72
  ].join(" ");
73
73
  expect(text).toContain("merge recommendation");
74
74
  expect(text).toContain("affected application paths");
75
75
  expect(text).not.toMatch(/total score|0-100|formula|weight/i);
76
- expect(d.exposureCategories).toHaveLength(5);
77
- expect(d.exposureCategories).toContain("Elevated exposure");
76
+ expect(d.prRiskBands).toHaveLength(5);
77
+ expect(d.prRiskBands).toContain("Medium");
78
78
  });
79
79
  });
80
80
  function collectMarketingStrings() {
@@ -92,6 +92,6 @@ function collectMarketingStrings() {
92
92
  out.push(body);
93
93
  }
94
94
  const d = productMessaging.dashboardPrCard;
95
- out.push(d.intro, d.posture, d.exposure, d.exposureCategoriesLead, d.cardBody, ...d.exposureCategories);
95
+ out.push(d.intro, d.posture, d.exposure, d.prRiskBandsLead, d.cardBody, ...d.prRiskBands);
96
96
  return out;
97
97
  }
@@ -2,13 +2,14 @@ export type ScanPipelineStatus = "queued" | "running" | "done" | "failed";
2
2
  export type PipelineCompletionEvidence = {
3
3
  scannedAt?: string | null;
4
4
  decision?: string | null;
5
- totalScore?: number | null;
5
+ /** Denormalized or resolved PR risk score — completion signal only. */
6
+ prRiskScore?: number | null;
6
7
  hasResult?: boolean;
7
8
  };
8
- /** Scan completion signals — decision/totalScore/result are only set after analysis. */
9
+ /** Scan completion signals — decision/prRiskScore/result are set after analysis. */
9
10
  export declare function hasScanCompletionEvidence(evidence: PipelineCompletionEvidence): boolean;
10
11
  /**
11
- * Resolve wire status using completion evidence (decision, totalScore, result).
12
+ * Resolve wire status using completion evidence (decision, prRiskScore, result).
12
13
  * Handles races where denormalized columns are written before status flips to done.
13
14
  */
14
15
  export declare function resolvePipelineStatus(status: string | undefined, evidence: PipelineCompletionEvidence): ScanPipelineStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"resolvePipelineStatus.d.ts","sourceRoot":"","sources":["../src/resolvePipelineStatus.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE1E,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAsBF,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,0BAA0B,GACnC,kBAAkB,CAKpB"}
1
+ {"version":3,"file":"resolvePipelineStatus.d.ts","sourceRoot":"","sources":["../src/resolvePipelineStatus.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE1E,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAsBF,oFAAoF;AACpF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,0BAA0B,GACnC,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE,0BAA0B,GACnC,kBAAkB,CAKpB"}
@@ -10,13 +10,13 @@ function normalizePipelineStatus(status) {
10
10
  function isMergePostureDecision(decision) {
11
11
  return (decision === "safe" || decision === "needs_review" || decision === "risky");
12
12
  }
13
- /** Scan completion signals — decision/totalScore/result are only set after analysis. */
13
+ /** Scan completion signals — decision/prRiskScore/result are set after analysis. */
14
14
  export function hasScanCompletionEvidence(evidence) {
15
15
  if (isMergePostureDecision(evidence.decision))
16
16
  return true;
17
17
  if (evidence.hasResult === true)
18
18
  return true;
19
- if (evidence.totalScore != null && Number.isFinite(evidence.totalScore)) {
19
+ if (evidence.prRiskScore != null && Number.isFinite(evidence.prRiskScore)) {
20
20
  return true;
21
21
  }
22
22
  if (evidence.scannedAt?.trim())
@@ -24,7 +24,7 @@ export function hasScanCompletionEvidence(evidence) {
24
24
  return false;
25
25
  }
26
26
  /**
27
- * Resolve wire status using completion evidence (decision, totalScore, result).
27
+ * Resolve wire status using completion evidence (decision, prRiskScore, result).
28
28
  * Handles races where denormalized columns are written before status flips to done.
29
29
  */
30
30
  export function resolvePipelineStatus(status, evidence) {
@@ -1,19 +1,19 @@
1
- import { type CardExposureCategory } from "./formatCardExposureDisplay.js";
1
+ import { type PrRiskBand } from "./prRiskBand.js";
2
2
  import type { ScanResult, ScoreLayer } from "./types.js";
3
3
  export type RiskSignalLayer = {
4
4
  layer: ScoreLayer;
5
5
  score: number;
6
- exposure: CardExposureCategory;
6
+ band: PrRiskBand;
7
7
  };
8
- /** Canonical exposure classification for totalScore and layerScores. */
8
+ /** Canonical PR Risk classification for prRisk.score and layer scores. */
9
9
  export type RiskSignals = {
10
10
  riskIndex: number | null;
11
- exposure: CardExposureCategory | null;
11
+ band: PrRiskBand | null;
12
12
  layers: RiskSignalLayer[];
13
13
  };
14
14
  /**
15
- * Single owner for numeric risk index + exposure bucket classification.
16
- * Surfaces must consume `facts.riskSignals` in future phases — do not re-derive bands.
15
+ * Single owner for PR risk index + band classification.
16
+ * Surfaces must consume `facts.riskSignals` — do not re-derive bands.
17
17
  */
18
18
  export declare function deriveRiskSignals(result: ScanResult): RiskSignals | null;
19
19
  //# sourceMappingURL=riskSignals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"riskSignals.d.ts","sourceRoot":"","sources":["../src/riskSignals.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAe,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,oBAAoB,CAAC;CAChC,CAAC;AAEF,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AASF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,CAwBxE"}
1
+ {"version":3,"file":"riskSignals.d.ts","sourceRoot":"","sources":["../src/riskSignals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,KAAK,EAAe,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AASF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,CAqBxE"}
@@ -1,4 +1,5 @@
1
- import { deriveCardExposureCategory, } from "./formatCardExposureDisplay.js";
1
+ import { scoreToBand } from "./prRiskBand.js";
2
+ import { resolvePrRiskLayerScores, resolvePrRiskScore } from "./prRiskWire.js";
2
3
  const LAYER_ORDER = [
3
4
  "security",
4
5
  "maintainability",
@@ -6,27 +7,25 @@ const LAYER_ORDER = [
6
7
  "upgradeImpact",
7
8
  ];
8
9
  /**
9
- * Single owner for numeric risk index + exposure bucket classification.
10
- * Surfaces must consume `facts.riskSignals` in future phases — do not re-derive bands.
10
+ * Single owner for PR risk index + band classification.
11
+ * Surfaces must consume `facts.riskSignals` — do not re-derive bands.
11
12
  */
12
13
  export function deriveRiskSignals(result) {
13
- const riskIndex = typeof result.totalScore === "number" && Number.isFinite(result.totalScore)
14
- ? result.totalScore
15
- : null;
16
- const layerScores = result.layerScores;
14
+ const riskIndex = resolvePrRiskScore(result);
15
+ const layerScores = resolvePrRiskLayerScores(result);
17
16
  if (riskIndex == null && layerScores == null) {
18
17
  return null;
19
18
  }
20
- const exposure = deriveCardExposureCategory(riskIndex);
19
+ const band = scoreToBand(riskIndex);
21
20
  const layers = layerScores
22
21
  ? LAYER_ORDER.map((layer) => {
23
22
  const score = layerScores[layer];
24
23
  return {
25
24
  layer,
26
25
  score,
27
- exposure: deriveCardExposureCategory(score) ?? "minimal",
26
+ band: scoreToBand(score) ?? "very_low",
28
27
  };
29
28
  })
30
29
  : [];
31
- return { riskIndex, exposure, layers };
30
+ return { riskIndex, band, layers };
32
31
  }
@@ -1,4 +1,4 @@
1
- import { exposureAriaFragment } from "./formatCardExposureDisplay.js";
1
+ import { prRiskBandAriaFragment } from "./prRiskBand.js";
2
2
  /** Canonical display strings for merge posture values. */
3
3
  export const MERGE_POSTURE_LABEL = {
4
4
  safe: "Safe",
@@ -44,17 +44,17 @@ export function mergePostureLabel(decision, fallback = "-") {
44
44
  */
45
45
  export function ariaLabelForPosture(decision, score) {
46
46
  const label = mergePostureLabel(decision);
47
- const exposure = exposureAriaFragment(score);
48
- if (exposure)
49
- return `${label}, ${exposure}`;
47
+ const bandFragment = prRiskBandAriaFragment(score);
48
+ if (bandFragment)
49
+ return `${label}, ${bandFragment}`;
50
50
  return label;
51
51
  }
52
52
  /** Accessible label for unified PR card findings block. */
53
53
  export function ariaLabelForCardSummary(postureLabel, riskIndex, observations, supportingLine) {
54
54
  const parts = [postureLabel];
55
- const exposure = exposureAriaFragment(riskIndex);
56
- if (exposure)
57
- parts.push(exposure);
55
+ const bandFragment = prRiskBandAriaFragment(riskIndex);
56
+ if (bandFragment)
57
+ parts.push(bandFragment);
58
58
  for (const obs of observations ?? []) {
59
59
  if (obs?.trim())
60
60
  parts.push(obs.trim());
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { deriveCardExposureCategory } from "./formatCardExposureDisplay.js";
2
+ import { scoreToBand } from "./prRiskBand.js";
3
3
  import { buildScanPresentationBundle } from "./presentation/orchestration/buildScanPresentationBundle.js";
4
4
  import { scanResultBullmq, scanResultEslint, scanResultFastifyRuntime, scanResultLimitedContext, scanResultMixedTypescriptFastify, scanResultPrettier, scanResultTypescriptPatch, scanResultVitest, } from "./presentation/fixtures/presentationPersonaFixtures.js";
5
5
  const validationPersonas = [
@@ -42,7 +42,7 @@ describe("scanPresentationBundle golden — proof model", () => {
42
42
  expect(apiArea?.hotspotPackages).toContain("fastify");
43
43
  expect(apiArea?.verificationFocus.length).toBeGreaterThan(0);
44
44
  expect(facts.riskSignals?.riskIndex).toBe(55);
45
- expect(facts.riskSignals?.exposure).toBe(deriveCardExposureCategory(55));
45
+ expect(facts.riskSignals?.band).toBe(scoreToBand(55));
46
46
  expect(facts.riskIndex).toBe(facts.riskSignals?.riskIndex);
47
47
  });
48
48
  it("limited-context — diagnostics without synthetic confidence", () => {
@@ -59,7 +59,7 @@ describe("scanPresentationBundle golden — proof model", () => {
59
59
  expect(facts.availability.corpusGateReason).toBe("ok");
60
60
  expect(facts.packageSemantics?.dependencyClass).toBe("tooling");
61
61
  expect(facts.riskSignals?.riskIndex).toBe(18);
62
- expect(facts.riskSignals?.exposure).toBe(deriveCardExposureCategory(18));
62
+ expect(facts.riskSignals?.band).toBe(scoreToBand(18));
63
63
  });
64
64
  it("affected areas always carry linkage arrays (structure)", () => {
65
65
  for (const { result } of validationPersonas) {
@@ -3,7 +3,12 @@ import type { EngineEmittedScanResult, ScanResult } from "./types.js";
3
3
  /** Bump when persisted `result` JSON validation rules change materially (relaxed / legacy-tolerant). */
4
4
  export declare const SCAN_RESULT_ABI: "2";
5
5
  /** Bump when strict fresh-engine-output validation rules change materially. */
6
- export declare const ENGINE_OUTPUT_SCAN_ABI: "3";
6
+ export declare const ENGINE_OUTPUT_SCAN_ABI: "4";
7
+ /**
8
+ * Synthesize ABI-4 prRisk/repositoryHealth from legacy totalScore when an engine
9
+ * has not yet adopted the dual-score wire (transitional bridge).
10
+ */
11
+ export declare function normalizeEngineOutputAbi4(raw: unknown): unknown;
7
12
  /**
8
13
  * Minimum structural invariants for **persisted** `scans.result` JSON and legacy reads.
9
14
  * `methodologyVersion` stays optional so historical rows without it remain valid.
@@ -17,6 +22,24 @@ export declare const scanResultSchema: z.ZodObject<{
17
22
  ecosystem: z.ZodNumber;
18
23
  upgradeImpact: z.ZodNumber;
19
24
  }, z.core.$strip>;
25
+ prRisk: z.ZodOptional<z.ZodObject<{
26
+ score: z.ZodNumber;
27
+ layerScores: z.ZodOptional<z.ZodObject<{
28
+ security: z.ZodNumber;
29
+ maintainability: z.ZodNumber;
30
+ ecosystem: z.ZodNumber;
31
+ upgradeImpact: z.ZodNumber;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>>;
34
+ repositoryHealth: z.ZodOptional<z.ZodObject<{
35
+ totalScore: z.ZodNumber;
36
+ layerScores: z.ZodOptional<z.ZodObject<{
37
+ security: z.ZodNumber;
38
+ maintainability: z.ZodNumber;
39
+ ecosystem: z.ZodNumber;
40
+ upgradeImpact: z.ZodNumber;
41
+ }, z.core.$strip>>;
42
+ }, z.core.$strip>>;
20
43
  findings: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodUnknown>, z.ZodNull]>>, z.ZodTransform<unknown[], unknown[] | null | undefined>>;
21
44
  methodologyVersion: z.ZodOptional<z.ZodString>;
22
45
  confidence: z.ZodOptional<z.ZodEnum<{
@@ -210,6 +233,24 @@ export declare const engineOutputScanResultSchema: z.ZodObject<{
210
233
  }>;
211
234
  }, z.core.$strip>;
212
235
  }, z.core.$strip>;
236
+ prRisk: z.ZodObject<{
237
+ score: z.ZodNumber;
238
+ layerScores: z.ZodOptional<z.ZodObject<{
239
+ security: z.ZodNumber;
240
+ maintainability: z.ZodNumber;
241
+ ecosystem: z.ZodNumber;
242
+ upgradeImpact: z.ZodNumber;
243
+ }, z.core.$strip>>;
244
+ }, z.core.$strip>;
245
+ repositoryHealth: z.ZodObject<{
246
+ totalScore: z.ZodNumber;
247
+ layerScores: z.ZodOptional<z.ZodObject<{
248
+ security: z.ZodNumber;
249
+ maintainability: z.ZodNumber;
250
+ ecosystem: z.ZodNumber;
251
+ upgradeImpact: z.ZodNumber;
252
+ }, z.core.$strip>>;
253
+ }, z.core.$strip>;
213
254
  }, z.core.$loose>;
214
255
  export type EngineOutputScanResultParseFailure = {
215
256
  ok: false;
@@ -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;AASnD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4Bb,CAAC;AAUjB;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAIvC,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,CAazE;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"}
@@ -3,13 +3,42 @@ import { z } from "zod";
3
3
  /** Bump when persisted `result` JSON validation rules change materially (relaxed / legacy-tolerant). */
4
4
  export const SCAN_RESULT_ABI = "2";
5
5
  /** Bump when strict fresh-engine-output validation rules change materially. */
6
- export const ENGINE_OUTPUT_SCAN_ABI = "3";
6
+ export const ENGINE_OUTPUT_SCAN_ABI = "4";
7
7
  const layerScoresSchema = z.object({
8
8
  security: z.number(),
9
9
  maintainability: z.number(),
10
10
  ecosystem: z.number(),
11
11
  upgradeImpact: z.number(),
12
12
  });
13
+ const prRiskSchema = z.object({
14
+ score: z.number().min(0).max(100),
15
+ layerScores: layerScoresSchema.optional(),
16
+ });
17
+ const repositoryHealthSchema = z.object({
18
+ totalScore: z.number().min(0).max(100),
19
+ layerScores: layerScoresSchema.optional(),
20
+ });
21
+ /**
22
+ * Synthesize ABI-4 prRisk/repositoryHealth from legacy totalScore when an engine
23
+ * has not yet adopted the dual-score wire (transitional bridge).
24
+ */
25
+ export function normalizeEngineOutputAbi4(raw) {
26
+ if (!raw || typeof raw !== "object")
27
+ return raw;
28
+ const obj = raw;
29
+ const totalScore = obj.totalScore;
30
+ const layerScores = obj.layerScores;
31
+ if (typeof totalScore !== "number" || !Number.isFinite(totalScore)) {
32
+ return raw;
33
+ }
34
+ if (!layerScores || typeof layerScores !== "object")
35
+ return raw;
36
+ return {
37
+ ...obj,
38
+ prRisk: obj.prRisk ?? { score: totalScore, layerScores },
39
+ repositoryHealth: obj.repositoryHealth ?? { totalScore, layerScores },
40
+ };
41
+ }
13
42
  /**
14
43
  * Minimum structural invariants for **persisted** `scans.result` JSON and legacy reads.
15
44
  * `methodologyVersion` stays optional so historical rows without it remain valid.
@@ -19,6 +48,8 @@ export const scanResultSchema = z
19
48
  .object({
20
49
  totalScore: z.number().min(0).max(100),
21
50
  layerScores: layerScoresSchema,
51
+ prRisk: prRiskSchema.optional(),
52
+ repositoryHealth: repositoryHealthSchema.optional(),
22
53
  findings: z
23
54
  .union([z.array(z.unknown()), z.null()])
24
55
  .optional()
@@ -59,9 +90,11 @@ export const engineOutputScanResultSchema = scanResultSchema.extend({
59
90
  methodologyVersion: z.string().trim().min(1),
60
91
  generatedAt: engineOutputGeneratedAtSchema,
61
92
  assessment: assessmentSchema,
93
+ prRisk: prRiskSchema,
94
+ repositoryHealth: repositoryHealthSchema,
62
95
  });
63
96
  export function safeParseEngineOutputScanResult(data) {
64
- const parsed = engineOutputScanResultSchema.safeParse(data);
97
+ const parsed = engineOutputScanResultSchema.safeParse(normalizeEngineOutputAbi4(data));
65
98
  if (!parsed.success) {
66
99
  const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
67
100
  return {