@mergesignal/shared 0.3.0 → 0.4.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 (92) hide show
  1. package/dist/deriveScanNarrative.js +1 -1
  2. package/dist/engineWireRegression.test.js +2 -2
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -2
  6. package/dist/narrativeParity.test.js +19 -18
  7. package/dist/presentGitHubPrComment.d.ts +3 -4
  8. package/dist/presentGitHubPrComment.d.ts.map +1 -1
  9. package/dist/presentGitHubPrComment.js +15 -61
  10. package/dist/presentation/compose/narrativeCompose.d.ts +16 -0
  11. package/dist/presentation/compose/narrativeCompose.d.ts.map +1 -0
  12. package/dist/presentation/compose/narrativeCompose.js +167 -0
  13. package/dist/presentation/dto/githubAndCliPresentation.d.ts +53 -0
  14. package/dist/presentation/dto/githubAndCliPresentation.d.ts.map +1 -0
  15. package/dist/presentation/dto/githubAndCliPresentation.js +1 -0
  16. package/dist/presentation/dto/scanCardPresentation.d.ts +25 -0
  17. package/dist/presentation/dto/scanCardPresentation.d.ts.map +1 -0
  18. package/dist/presentation/dto/scanCardPresentation.js +1 -0
  19. package/dist/presentation/dto/scanDetailsPresentation.d.ts +111 -0
  20. package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -0
  21. package/dist/presentation/dto/scanDetailsPresentation.js +1 -0
  22. package/dist/presentation/dto/types.d.ts +24 -0
  23. package/dist/presentation/dto/types.d.ts.map +1 -0
  24. package/dist/presentation/dto/types.js +22 -0
  25. package/dist/presentation/fixtures/scanResultFixtures.d.ts +5 -0
  26. package/dist/presentation/fixtures/scanResultFixtures.d.ts.map +1 -0
  27. package/dist/presentation/fixtures/scanResultFixtures.js +83 -0
  28. package/dist/presentation/index.d.ts +25 -0
  29. package/dist/presentation/index.d.ts.map +1 -0
  30. package/dist/presentation/index.js +13 -0
  31. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts +11 -0
  32. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -0
  33. package/dist/presentation/orchestration/buildScanCardPresentation.js +44 -0
  34. package/dist/presentation/orchestration/buildScanDetailsPresentation.d.ts +14 -0
  35. package/dist/presentation/orchestration/buildScanDetailsPresentation.d.ts.map +1 -0
  36. package/dist/presentation/orchestration/buildScanDetailsPresentation.js +19 -0
  37. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts +15 -0
  38. package/dist/presentation/orchestration/buildScanPresentationBundle.d.ts.map +1 -0
  39. package/dist/presentation/orchestration/buildScanPresentationBundle.js +23 -0
  40. package/dist/presentation/orchestration/scanPresentationBundle.d.ts +15 -0
  41. package/dist/presentation/orchestration/scanPresentationBundle.d.ts.map +1 -0
  42. package/dist/presentation/orchestration/scanPresentationBundle.js +1 -0
  43. package/dist/presentation/presentation.test.d.ts +2 -0
  44. package/dist/presentation/presentation.test.d.ts.map +1 -0
  45. package/dist/presentation/presentation.test.js +95 -0
  46. package/dist/presentation/presenters/presentCliScanSummary.d.ts +6 -0
  47. package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -0
  48. package/dist/presentation/presenters/presentCliScanSummary.js +30 -0
  49. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts +8 -0
  50. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -0
  51. package/dist/presentation/presenters/presentGitHubCheckRun.js +32 -0
  52. package/dist/presentation/presenters/presentGitHubPrComment.d.ts +4 -0
  53. package/dist/presentation/presenters/presentGitHubPrComment.d.ts.map +1 -0
  54. package/dist/presentation/presenters/presentGitHubPrComment.js +40 -0
  55. package/dist/presentation/presenters/presentPipelineScanCard.d.ts +3 -0
  56. package/dist/presentation/presenters/presentPipelineScanCard.d.ts.map +1 -0
  57. package/dist/presentation/presenters/presentPipelineScanCard.js +23 -0
  58. package/dist/presentation/presenters/presentScanCard.d.ts +7 -0
  59. package/dist/presentation/presenters/presentScanCard.d.ts.map +1 -0
  60. package/dist/presentation/presenters/presentScanCard.js +43 -0
  61. package/dist/presentation/presenters/presentScanDetails.d.ts +9 -0
  62. package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -0
  63. package/dist/presentation/presenters/presentScanDetails.js +177 -0
  64. package/dist/presentation/profile/derivePresentationProfile.d.ts +12 -0
  65. package/dist/presentation/profile/derivePresentationProfile.d.ts.map +1 -0
  66. package/dist/presentation/profile/derivePresentationProfile.js +86 -0
  67. package/dist/presentation/profile/presentationProfile.d.ts +9 -0
  68. package/dist/presentation/profile/presentationProfile.d.ts.map +1 -0
  69. package/dist/presentation/profile/presentationProfile.js +1 -0
  70. package/dist/presentation/render/renderCliScanSummaryText.d.ts +3 -0
  71. package/dist/presentation/render/renderCliScanSummaryText.d.ts.map +1 -0
  72. package/dist/presentation/render/renderCliScanSummaryText.js +30 -0
  73. package/dist/presentation/render/renderGitHubCheckRunMarkdown.d.ts +3 -0
  74. package/dist/presentation/render/renderGitHubCheckRunMarkdown.d.ts.map +1 -0
  75. package/dist/presentation/render/renderGitHubCheckRunMarkdown.js +23 -0
  76. package/dist/presentation/render/renderGitHubPrCommentMarkdown.d.ts +3 -0
  77. package/dist/presentation/render/renderGitHubPrCommentMarkdown.d.ts.map +1 -0
  78. package/dist/presentation/render/renderGitHubPrCommentMarkdown.js +24 -0
  79. package/dist/scanCardSummary.d.ts +4 -35
  80. package/dist/scanCardSummary.d.ts.map +1 -1
  81. package/dist/scanCardSummary.js +12 -63
  82. package/dist/scanCardSummary.test.js +56 -75
  83. package/dist/scanDetailViewModel.d.ts +1 -0
  84. package/dist/scanDetailViewModel.d.ts.map +1 -1
  85. package/dist/scanDetailViewModel.js +1 -1
  86. package/dist/scanSurfaceCopy.d.ts +25 -0
  87. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  88. package/dist/scanSurfaceCopy.js +25 -0
  89. package/package.json +1 -1
  90. package/dist/presentScanCardSummary.d.ts +0 -16
  91. package/dist/presentScanCardSummary.d.ts.map +0 -1
  92. package/dist/presentScanCardSummary.js +0 -212
@@ -0,0 +1,24 @@
1
+ export type PipelineStatus = "queued" | "running" | "done" | "failed";
2
+ export type PresentationStatus = "safe" | "needs_review" | "risky";
3
+ export type PresentationDensity = "minimal" | "standard" | "rich";
4
+ export type PresentationConfidence = "high" | "medium" | "low";
5
+ export type PresentationPriority = "pr_intelligence" | "limited";
6
+ export type PresentationEvidenceRow = {
7
+ label: string;
8
+ value: string;
9
+ };
10
+ /** User-facing evidence summary - mirrors profile.priority */
11
+ export type PresentationEvidenceContext = {
12
+ priority: PresentationPriority;
13
+ degradedMessage?: string;
14
+ };
15
+ export type FindingCountSummary = {
16
+ critical: number;
17
+ high: number;
18
+ medium: number;
19
+ low: number;
20
+ };
21
+ export declare function aggregateFindingCounts(findings: Array<{
22
+ severity: string;
23
+ }> | null | undefined): FindingCountSummary;
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,cAAc,GAAG,OAAO,CAAC;AAEnE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAElE,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAEjE,MAAM,MAAM,uBAAuB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,8DAA8D;AAC9D,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACvD,mBAAmB,CAgBrB"}
@@ -0,0 +1,22 @@
1
+ export function aggregateFindingCounts(findings) {
2
+ const counts = {
3
+ critical: 0,
4
+ high: 0,
5
+ medium: 0,
6
+ low: 0,
7
+ };
8
+ if (!Array.isArray(findings))
9
+ return counts;
10
+ for (const f of findings) {
11
+ const s = f.severity;
12
+ if (s === "critical")
13
+ counts.critical += 1;
14
+ else if (s === "high")
15
+ counts.high += 1;
16
+ else if (s === "medium")
17
+ counts.medium += 1;
18
+ else if (s === "low")
19
+ counts.low += 1;
20
+ }
21
+ return counts;
22
+ }
@@ -0,0 +1,5 @@
1
+ import type { ScanResult } from "../../types.js";
2
+ export declare const scanResultFastifyRuntime: ScanResult;
3
+ export declare const scanResultTypescriptPatch: ScanResult;
4
+ export declare const scanResultLimitedContext: ScanResult;
5
+ //# sourceMappingURL=scanResultFixtures.d.ts.map
@@ -0,0 +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;AAOjD,eAAO,MAAM,wBAAwB,EAAE,UA6BtC,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,UAwBvC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,UAyBtC,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceTypescript, } from "../../fixtures/repoIntelligenceFixtures.js";
2
+ import { analysisPreparationWithValidRepoIntel } from "../../fixtures/repoIntelligenceTestHelpers.js";
3
+ export const scanResultFastifyRuntime = {
4
+ totalScore: 55,
5
+ layerScores: {
6
+ security: 10,
7
+ maintainability: 20,
8
+ ecosystem: 30,
9
+ upgradeImpact: 15,
10
+ },
11
+ findings: [],
12
+ generatedAt: "2026-01-01T00:00:00.000Z",
13
+ changedPackages: ["fastify"],
14
+ analysisPreparation: analysisPreparationWithValidRepoIntel(),
15
+ repoIntelligence: fixtureRepoIntelligenceFastify,
16
+ decision: {
17
+ recommendation: "needs_review",
18
+ confidence: "medium",
19
+ reasoning: [],
20
+ },
21
+ insights: [
22
+ {
23
+ type: "usage_risk",
24
+ priority: "high",
25
+ confidence: "confirmed",
26
+ scope: "changed",
27
+ message: "Review fastify middleware ordering",
28
+ context: "apps/api/src/middleware/auth.ts",
29
+ remediation: "Run auth smoke tests",
30
+ },
31
+ ],
32
+ };
33
+ export const scanResultTypescriptPatch = {
34
+ totalScore: 18,
35
+ layerScores: {
36
+ security: 5,
37
+ maintainability: 8,
38
+ ecosystem: 10,
39
+ upgradeImpact: 5,
40
+ },
41
+ findings: [],
42
+ generatedAt: "2026-01-01T00:00:00.000Z",
43
+ changedPackages: ["typescript"],
44
+ analysisPreparation: analysisPreparationWithValidRepoIntel(),
45
+ repoIntelligence: fixtureRepoIntelligenceTypescript,
46
+ decision: {
47
+ recommendation: "safe",
48
+ confidence: "high",
49
+ reasoning: [],
50
+ },
51
+ graphInsights: {
52
+ maxDepth: 4,
53
+ nodes: 120,
54
+ edges: 340,
55
+ hotspots: [],
56
+ },
57
+ };
58
+ export const scanResultLimitedContext = {
59
+ totalScore: 40,
60
+ layerScores: {
61
+ security: 10,
62
+ maintainability: 10,
63
+ ecosystem: 10,
64
+ upgradeImpact: 10,
65
+ },
66
+ findings: [],
67
+ generatedAt: "2026-01-01T00:00:00.000Z",
68
+ changedPackages: ["lodash"],
69
+ repoIntelligence: { invalid: true },
70
+ graphInsights: {
71
+ maxDepth: 5,
72
+ nodes: 200,
73
+ edges: 500,
74
+ hotspots: [
75
+ { kind: "hotspot", packageName: "lodash", direct: true, depth: 2 },
76
+ ],
77
+ },
78
+ decision: {
79
+ recommendation: "needs_review",
80
+ confidence: "low",
81
+ reasoning: [],
82
+ },
83
+ };
@@ -0,0 +1,25 @@
1
+ export type { ScanPresentationBundle } from "./orchestration/scanPresentationBundle.js";
2
+ export { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
3
+ export type { BuildScanPresentationBundleInput } from "./orchestration/buildScanPresentationBundle.js";
4
+ export { buildScanCardPresentation } from "./orchestration/buildScanCardPresentation.js";
5
+ export type { BuildScanCardPresentationInput } from "./orchestration/buildScanCardPresentation.js";
6
+ export { buildScanDetailsPresentation } from "./orchestration/buildScanDetailsPresentation.js";
7
+ export type { BuildScanDetailsPresentationInput } from "./orchestration/buildScanDetailsPresentation.js";
8
+ export type { PresentationProfile } from "./profile/presentationProfile.js";
9
+ export type { PipelineStatus, PresentationStatus, PresentationDensity, PresentationConfidence, PresentationPriority, PresentationEvidenceContext, PresentationEvidenceRow, } from "./dto/types.js";
10
+ export type { ScanCardPresentation } from "./dto/scanCardPresentation.js";
11
+ export type { ScanDetailsPresentation } from "./dto/scanDetailsPresentation.js";
12
+ export type { GitHubCheckRunPresentation, GitHubPrCommentPresentation, CliScanPresentation, } from "./dto/githubAndCliPresentation.js";
13
+ export { presentScanCard } from "./presenters/presentScanCard.js";
14
+ export type { PresentScanCardContext } from "./presenters/presentScanCard.js";
15
+ export { presentPipelineScanCard } from "./presenters/presentPipelineScanCard.js";
16
+ export { presentScanDetails } from "./presenters/presentScanDetails.js";
17
+ export type { PresentScanDetailsContext } from "./presenters/presentScanDetails.js";
18
+ export { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
19
+ export { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
20
+ export { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
21
+ export { renderGitHubCheckRunMarkdown } from "./render/renderGitHubCheckRunMarkdown.js";
22
+ export { renderGitHubPrCommentMarkdown } from "./render/renderGitHubPrCommentMarkdown.js";
23
+ export { renderCliScanSummaryText } from "./render/renderCliScanSummaryText.js";
24
+ export { composeHeadline, composeSubheadline, composeKeyPoints, composeVerificationActions, composeAffectedAreaLabels, composeEvidenceRows, composeSupportingContext, evidenceContextFromProfile, } from "./compose/narrativeCompose.js";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/presentation/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,gDAAgD,CAAC;AAC7F,YAAY,EAAE,gCAAgC,EAAE,MAAM,gDAAgD,CAAC;AACvG,OAAO,EAAE,yBAAyB,EAAE,MAAM,8CAA8C,CAAC;AACzF,YAAY,EAAE,8BAA8B,EAAE,MAAM,8CAA8C,CAAC;AACnG,OAAO,EAAE,4BAA4B,EAAE,MAAM,iDAAiD,CAAC;AAC/F,YAAY,EAAE,iCAAiC,EAAE,MAAM,iDAAiD,CAAC;AAEzG,YAAY,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,YAAY,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAChF,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,OAAO,EAAE,6BAA6B,EAAE,MAAM,2CAA2C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAEhF,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,13 @@
1
+ export { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
2
+ export { buildScanCardPresentation } from "./orchestration/buildScanCardPresentation.js";
3
+ export { buildScanDetailsPresentation } from "./orchestration/buildScanDetailsPresentation.js";
4
+ export { presentScanCard } from "./presenters/presentScanCard.js";
5
+ export { presentPipelineScanCard } from "./presenters/presentPipelineScanCard.js";
6
+ export { presentScanDetails } from "./presenters/presentScanDetails.js";
7
+ export { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
8
+ export { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
9
+ export { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
10
+ export { renderGitHubCheckRunMarkdown } from "./render/renderGitHubCheckRunMarkdown.js";
11
+ export { renderGitHubPrCommentMarkdown } from "./render/renderGitHubPrCommentMarkdown.js";
12
+ export { renderCliScanSummaryText } from "./render/renderCliScanSummaryText.js";
13
+ export { composeHeadline, composeSubheadline, composeKeyPoints, composeVerificationActions, composeAffectedAreaLabels, composeEvidenceRows, composeSupportingContext, evidenceContextFromProfile, } from "./compose/narrativeCompose.js";
@@ -0,0 +1,11 @@
1
+ import type { PipelineStatus } from "../dto/types.js";
2
+ import type { ScanResult } from "../../types.js";
3
+ import type { ScanCardPresentation } from "../dto/scanCardPresentation.js";
4
+ export type BuildScanCardPresentationInput = {
5
+ pipelineStatus: PipelineStatus;
6
+ result: ScanResult | null;
7
+ decision?: string | null;
8
+ totalScore?: number | null;
9
+ };
10
+ export declare function buildScanCardPresentation(input: BuildScanCardPresentationInput): ScanCardPresentation;
11
+ //# sourceMappingURL=buildScanCardPresentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildScanCardPresentation.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanCardPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AA2BF,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,8BAA8B,GACpC,oBAAoB,CA2BtB"}
@@ -0,0 +1,44 @@
1
+ import { mergePostureFromDecision } from "../../riskVocabulary.js";
2
+ import { buildScanPresentationBundle } from "./buildScanPresentationBundle.js";
3
+ import { presentScanCard } from "../presenters/presentScanCard.js";
4
+ import { presentPipelineScanCard } from "../presenters/presentPipelineScanCard.js";
5
+ function minimalResultFromDenormalized(decision, totalScore) {
6
+ const posture = mergePostureFromDecision(decision ?? undefined);
7
+ if (!posture && (totalScore == null || !Number.isFinite(totalScore))) {
8
+ return null;
9
+ }
10
+ return {
11
+ totalScore: totalScore ?? 0,
12
+ layerScores: {
13
+ security: 0,
14
+ maintainability: 0,
15
+ ecosystem: 0,
16
+ upgradeImpact: 0,
17
+ },
18
+ findings: [],
19
+ generatedAt: new Date().toISOString(),
20
+ decision: posture
21
+ ? { recommendation: posture, confidence: "medium", reasoning: [] }
22
+ : undefined,
23
+ };
24
+ }
25
+ export function buildScanCardPresentation(input) {
26
+ if (input.pipelineStatus !== "done") {
27
+ return presentPipelineScanCard(input.pipelineStatus === "failed" ? "failed" : input.pipelineStatus);
28
+ }
29
+ const result = input.result ??
30
+ minimalResultFromDenormalized(input.decision, input.totalScore);
31
+ if (!result) {
32
+ return presentPipelineScanCard("failed");
33
+ }
34
+ const bundle = buildScanPresentationBundle({
35
+ result,
36
+ pipelineStatus: input.pipelineStatus,
37
+ decision: input.decision,
38
+ totalScore: input.totalScore,
39
+ });
40
+ if (!bundle) {
41
+ return presentPipelineScanCard("failed");
42
+ }
43
+ return presentScanCard(bundle, { includeFindingCounts: true });
44
+ }
@@ -0,0 +1,14 @@
1
+ import type { PipelineStatus } from "../dto/types.js";
2
+ import type { ScanResult } from "../../types.js";
3
+ import type { ScanDetailsPresentation } from "../dto/scanDetailsPresentation.js";
4
+ export type BuildScanDetailsPresentationInput = {
5
+ scanId: string;
6
+ pipelineStatus: PipelineStatus;
7
+ result: ScanResult;
8
+ methodologyVersion?: string | null;
9
+ prNumber?: number | null;
10
+ decision?: string | null;
11
+ totalScore?: number | null;
12
+ };
13
+ export declare function buildScanDetailsPresentation(input: BuildScanDetailsPresentationInput): ScanDetailsPresentation | null;
14
+ //# sourceMappingURL=buildScanDetailsPresentation.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,19 @@
1
+ import { buildScanPresentationBundle } from "./buildScanPresentationBundle.js";
2
+ import { presentScanDetails } from "../presenters/presentScanDetails.js";
3
+ export function buildScanDetailsPresentation(input) {
4
+ if (input.pipelineStatus !== "done" || !input.result)
5
+ return null;
6
+ const bundle = buildScanPresentationBundle({
7
+ result: input.result,
8
+ pipelineStatus: input.pipelineStatus,
9
+ decision: input.decision,
10
+ totalScore: input.totalScore,
11
+ });
12
+ if (!bundle)
13
+ return null;
14
+ return presentScanDetails(bundle, {
15
+ scanId: input.scanId,
16
+ methodologyVersion: input.methodologyVersion,
17
+ prNumber: input.prNumber,
18
+ });
19
+ }
@@ -0,0 +1,15 @@
1
+ import type { ScanResult } from "../../types.js";
2
+ import type { PipelineStatus } from "../dto/types.js";
3
+ import type { ScanPresentationBundle } from "./scanPresentationBundle.js";
4
+ export type BuildScanPresentationBundleInput = {
5
+ result: ScanResult;
6
+ pipelineStatus: PipelineStatus;
7
+ decision?: string | null;
8
+ totalScore?: number | null;
9
+ };
10
+ /**
11
+ * Mandatory orchestration entrypoint for completed-scan presentation.
12
+ * Only place that calls deriveScanNarrative and derivePresentationProfile.
13
+ */
14
+ export declare function buildScanPresentationBundle(input: BuildScanPresentationBundleInput): ScanPresentationBundle | null;
15
+ //# sourceMappingURL=buildScanPresentationBundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildScanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/buildScanPresentationBundle.ts"],"names":[],"mappings":"AACA,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,CAiB/B"}
@@ -0,0 +1,23 @@
1
+ import { deriveScanNarrative } from "../../deriveScanNarrative.js";
2
+ import { derivePresentationProfile } from "../profile/derivePresentationProfile.js";
3
+ /**
4
+ * Mandatory orchestration entrypoint for completed-scan presentation.
5
+ * Only place that calls deriveScanNarrative and derivePresentationProfile.
6
+ */
7
+ export function buildScanPresentationBundle(input) {
8
+ if (input.pipelineStatus !== "done" || !input.result)
9
+ return null;
10
+ const facts = deriveScanNarrative(input.result);
11
+ const profile = derivePresentationProfile(facts, {
12
+ pipelineStatus: input.pipelineStatus,
13
+ decision: input.decision,
14
+ totalScore: input.totalScore ?? input.result.totalScore,
15
+ });
16
+ if (!profile)
17
+ return null;
18
+ return {
19
+ facts,
20
+ profile,
21
+ result: input.result,
22
+ };
23
+ }
@@ -0,0 +1,15 @@
1
+ import type { ScanNarrativeFacts } from "../../scanNarrativeFacts.js";
2
+ import type { ScanResult } from "../../types.js";
3
+ import type { PresentationProfile } from "../profile/presentationProfile.js";
4
+ export type ScanPresentationBundle = {
5
+ facts: ScanNarrativeFacts;
6
+ profile: PresentationProfile;
7
+ /**
8
+ * Projection-only reference to the raw scan payload.
9
+ * Presenters may read fields for formatting but must NOT re-derive interpretation.
10
+ */
11
+ result: ScanResult;
12
+ /** Extension point for future shared derived artifacts */
13
+ shared?: Record<string, unknown>;
14
+ };
15
+ //# sourceMappingURL=scanPresentationBundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanPresentationBundle.d.ts","sourceRoot":"","sources":["../../../src/presentation/orchestration/scanPresentationBundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,mBAAmB,CAAC;IAC7B;;;OAGG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=presentation.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.test.d.ts","sourceRoot":"","sources":["../../src/presentation/presentation.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
3
+ import { presentScanCard } from "./presenters/presentScanCard.js";
4
+ import { presentScanDetails } from "./presenters/presentScanDetails.js";
5
+ import { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
6
+ import { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
7
+ import { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
8
+ import { scanResultFastifyRuntime, scanResultLimitedContext, scanResultTypescriptPatch, } from "./fixtures/scanResultFixtures.js";
9
+ const SCAN_ID = "22222222-2222-4222-8222-222222222222";
10
+ const ORIGIN = "https://app.example.com";
11
+ describe("buildScanPresentationBundle", () => {
12
+ it("returns null for incomplete pipeline", () => {
13
+ expect(buildScanPresentationBundle({
14
+ result: scanResultFastifyRuntime,
15
+ pipelineStatus: "running",
16
+ })).toBeNull();
17
+ });
18
+ it("fastify persona: rich needs_review pr_intelligence", () => {
19
+ const bundle = buildScanPresentationBundle({
20
+ result: scanResultFastifyRuntime,
21
+ pipelineStatus: "done",
22
+ });
23
+ expect(bundle.profile.density).toBe("rich");
24
+ expect(bundle.profile.status).toBe("needs_review");
25
+ expect(bundle.profile.priority).toBe("pr_intelligence");
26
+ });
27
+ it("typescript persona: minimal safe pr_intelligence", () => {
28
+ const bundle = buildScanPresentationBundle({
29
+ result: scanResultTypescriptPatch,
30
+ pipelineStatus: "done",
31
+ });
32
+ expect(bundle.profile.density).toBe("minimal");
33
+ expect(bundle.profile.status).toBe("safe");
34
+ expect(bundle.profile.priority).toBe("pr_intelligence");
35
+ });
36
+ it("limited context persona: minimal limited priority", () => {
37
+ const bundle = buildScanPresentationBundle({
38
+ result: scanResultLimitedContext,
39
+ pipelineStatus: "done",
40
+ });
41
+ expect(bundle.profile.density).toBe("minimal");
42
+ expect(bundle.profile.priority).toBe("limited");
43
+ expect(bundle.profile.degradedMessage).toBeTruthy();
44
+ });
45
+ });
46
+ describe("presentScanCard personas", () => {
47
+ it("fastify card is rich with affected areas and verification", () => {
48
+ const bundle = buildScanPresentationBundle({
49
+ result: scanResultFastifyRuntime,
50
+ pipelineStatus: "done",
51
+ });
52
+ const card = presentScanCard(bundle);
53
+ expect(card.density).toBe("rich");
54
+ expect(card.affectedAreas.length).toBeGreaterThan(0);
55
+ expect(card.verificationActions.length).toBeGreaterThan(0);
56
+ expect(card.headline.toLowerCase()).toContain("fastify");
57
+ expect(card.supportingContext).toBeUndefined();
58
+ });
59
+ it("typescript card is minimal safe without graph-first story", () => {
60
+ const bundle = buildScanPresentationBundle({
61
+ result: scanResultTypescriptPatch,
62
+ pipelineStatus: "done",
63
+ });
64
+ const card = presentScanCard(bundle);
65
+ expect(card.density).toBe("minimal");
66
+ expect(card.status).toBe("safe");
67
+ expect(card.changedPackages).toContain("typescript");
68
+ expect(card.verificationActions.length).toBeGreaterThan(0);
69
+ });
70
+ });
71
+ describe("presentation parity across surfaces", () => {
72
+ it("same bundle drives card, details, GitHub, CLI with identical profile", () => {
73
+ const bundle = buildScanPresentationBundle({
74
+ result: scanResultFastifyRuntime,
75
+ pipelineStatus: "done",
76
+ });
77
+ const card = presentScanCard(bundle);
78
+ const details = presentScanDetails(bundle, { scanId: SCAN_ID });
79
+ const check = presentGitHubCheckRun(bundle, {
80
+ scanId: SCAN_ID,
81
+ webAppOrigin: ORIGIN,
82
+ });
83
+ const comment = presentGitHubPrComment(bundle);
84
+ const cli = presentCliScanSummary(bundle, { repoLabel: "acme/api" });
85
+ for (const dto of [card, details, check, comment, cli]) {
86
+ expect(dto.status).toBe(bundle.profile.status);
87
+ expect(dto.density).toBe(bundle.profile.density);
88
+ expect(dto.confidence).toBe(bundle.profile.confidence);
89
+ expect(dto.evidenceContext?.priority).toBe(bundle.profile.priority);
90
+ }
91
+ expect(card.primaryPackage).toBe("fastify");
92
+ expect(details.narrative.primaryPackage).toBe("fastify");
93
+ expect(check.title.toLowerCase()).toContain("fastify");
94
+ });
95
+ });
@@ -0,0 +1,6 @@
1
+ import type { CliScanPresentation } from "../dto/githubAndCliPresentation.js";
2
+ import type { ScanPresentationBundle } from "../orchestration/scanPresentationBundle.js";
3
+ export declare function presentCliScanSummary(bundle: ScanPresentationBundle, ctx?: {
4
+ repoLabel?: string;
5
+ }): CliScanPresentation;
6
+ //# sourceMappingURL=presentCliScanSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentCliScanSummary.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentCliScanSummary.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAEzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,mBAAmB,CA6BrB"}
@@ -0,0 +1,30 @@
1
+ import { composeHeadline, composeKeyPoints, composeSubheadline, composeSupportingContext, composeVerificationActions, evidenceContextFromProfile, } from "../compose/narrativeCompose.js";
2
+ export function presentCliScanSummary(bundle, ctx = {}) {
3
+ const { facts, profile, result } = bundle;
4
+ const layers = result.layerScores;
5
+ const layerLine = layers
6
+ ? `security=${layers.security} maintainability=${layers.maintainability} ecosystem=${layers.ecosystem} upgradeImpact=${layers.upgradeImpact}`
7
+ : "";
8
+ return {
9
+ header: {
10
+ repoLabel: ctx.repoLabel ?? "scan",
11
+ methodology: result.methodologyVersion ?? undefined,
12
+ confidence: profile.confidence,
13
+ },
14
+ status: profile.status,
15
+ density: profile.density,
16
+ confidence: profile.confidence,
17
+ evidenceContext: evidenceContextFromProfile(bundle),
18
+ headline: composeHeadline(bundle),
19
+ subheadline: composeSubheadline(bundle),
20
+ keyPoints: composeKeyPoints(bundle, 6),
21
+ verificationActions: composeVerificationActions(bundle, 5),
22
+ metrics: {
23
+ riskIndex: facts.riskIndex ?? result.totalScore ?? 0,
24
+ layerLine,
25
+ findingCount: result.findings?.length ?? 0,
26
+ recommendationCount: result.recommendations?.length ?? 0,
27
+ },
28
+ supportingContext: composeSupportingContext(bundle),
29
+ };
30
+ }
@@ -0,0 +1,8 @@
1
+ import type { GitHubCheckRunPresentation } from "../dto/githubAndCliPresentation.js";
2
+ import type { ScanPresentationBundle } from "../orchestration/scanPresentationBundle.js";
3
+ export declare function presentGitHubCheckRun(bundle: ScanPresentationBundle, ctx: {
4
+ scanId: string;
5
+ webAppOrigin: string;
6
+ baseline?: boolean;
7
+ }): GitHubCheckRunPresentation;
8
+ //# sourceMappingURL=presentGitHubCheckRun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentGitHubCheckRun.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubCheckRun.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAEzF,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,CAkC5B"}
@@ -0,0 +1,32 @@
1
+ import { composeHeadline, composeKeyPoints, composeSubheadline, composeVerificationActions, evidenceContextFromProfile, } from "../compose/narrativeCompose.js";
2
+ export function presentGitHubCheckRun(bundle, ctx) {
3
+ const { profile } = bundle;
4
+ const headline = composeHeadline(bundle);
5
+ const keyPoints = composeKeyPoints(bundle, 3);
6
+ const actions = composeVerificationActions(bundle, 3);
7
+ const detailsUrl = `${ctx.webAppOrigin.replace(/\/$/, "")}/scan/${ctx.scanId}`;
8
+ const conclusion = profile.status === "safe"
9
+ ? "success"
10
+ : profile.status === "risky"
11
+ ? "failure"
12
+ : "neutral";
13
+ const sections = [];
14
+ if (keyPoints.length > 0) {
15
+ sections.push({ id: "why", title: "Why", bullets: keyPoints });
16
+ }
17
+ if (actions.length > 0) {
18
+ sections.push({ id: "actions", title: "What to verify", bullets: actions });
19
+ }
20
+ sections.push({ id: "footer", bullets: [`Full scan: ${detailsUrl}`] });
21
+ return {
22
+ status: profile.status,
23
+ density: profile.density,
24
+ confidence: profile.confidence,
25
+ evidenceContext: evidenceContextFromProfile(bundle),
26
+ title: headline,
27
+ conclusion,
28
+ summaryLead: composeSubheadline(bundle) ?? headline,
29
+ sections,
30
+ detailsUrl,
31
+ };
32
+ }
@@ -0,0 +1,4 @@
1
+ import type { GitHubPrCommentPresentation } from "../dto/githubAndCliPresentation.js";
2
+ import type { ScanPresentationBundle } from "../orchestration/scanPresentationBundle.js";
3
+ export declare function presentGitHubPrComment(bundle: ScanPresentationBundle): GitHubPrCommentPresentation;
4
+ //# sourceMappingURL=presentGitHubPrComment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentGitHubPrComment.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentGitHubPrComment.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAEzF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,2BAA2B,CAwC7B"}
@@ -0,0 +1,40 @@
1
+ import { MERGE_POSTURE_LABEL } from "../../riskVocabulary.js";
2
+ import { composeVerificationActions, evidenceContextFromProfile, } from "../compose/narrativeCompose.js";
3
+ import { formatChangedPackagesShort, formatUsageSummaryLine, selectReviewerGuidance, } from "../../narrativePresentation.js";
4
+ export function presentGitHubPrComment(bundle) {
5
+ const { facts, profile } = bundle;
6
+ const introLines = [];
7
+ const changed = formatChangedPackagesShort(facts, 3);
8
+ if (changed)
9
+ introLines.push(`Changed: ${changed}`);
10
+ const usage = formatUsageSummaryLine(facts, 1);
11
+ if (usage)
12
+ introLines.push(usage);
13
+ const verify = composeVerificationActions(bundle, 1)[0];
14
+ if (verify)
15
+ introLines.push(`Verify: ${verify}`);
16
+ const guidance = selectReviewerGuidance(facts, { scope: "changed", max: 3 });
17
+ const selected = guidance.length === 0
18
+ ? selectReviewerGuidance(facts, { max: 3 })
19
+ : guidance;
20
+ const guidanceBlocks = selected.map((g) => {
21
+ const where = g.context?.trim() ||
22
+ facts.packageUsage
23
+ .flatMap((u) => u.paths.slice(0, 1))
24
+ .filter(Boolean)[0] ||
25
+ "See scan detail for affected paths.";
26
+ const action = g.remediation?.trim() ||
27
+ composeVerificationActions(bundle, 1)[0] ||
28
+ "Review before merge.";
29
+ return { message: g.message, where, action };
30
+ });
31
+ return {
32
+ status: profile.status,
33
+ density: profile.density,
34
+ confidence: profile.confidence,
35
+ evidenceContext: evidenceContextFromProfile(bundle),
36
+ title: MERGE_POSTURE_LABEL[profile.status],
37
+ introLines,
38
+ guidanceBlocks,
39
+ };
40
+ }
@@ -0,0 +1,3 @@
1
+ import type { ScanCardPresentation } from "../dto/scanCardPresentation.js";
2
+ export declare function presentPipelineScanCard(pipelineStatus: "queued" | "running" | "failed"): ScanCardPresentation;
3
+ //# sourceMappingURL=presentPipelineScanCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentPipelineScanCard.d.ts","sourceRoot":"","sources":["../../../src/presentation/presenters/presentPipelineScanCard.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAC9C,oBAAoB,CAqBtB"}