@mergesignal/shared 0.4.1 → 0.4.3

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 (96) hide show
  1. package/dist/deriveScanNarrative.d.ts.map +1 -1
  2. package/dist/deriveScanNarrative.js +41 -8
  3. package/dist/deriveScanNarrative.test.js +4 -0
  4. package/dist/engineWireRegression.test.js +2 -2
  5. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
  6. package/dist/fixtures/repoIntelligenceFixtures.js +8 -1
  7. package/dist/narrativeParity.test.js +3 -4
  8. package/dist/presentation/compose/narrativeChannels.d.ts +17 -0
  9. package/dist/presentation/compose/narrativeChannels.d.ts.map +1 -0
  10. package/dist/presentation/compose/narrativeChannels.js +75 -0
  11. package/dist/presentation/compose/narrativeChannels.test.d.ts +2 -0
  12. package/dist/presentation/compose/narrativeChannels.test.d.ts.map +1 -0
  13. package/dist/presentation/compose/narrativeChannels.test.js +42 -0
  14. package/dist/presentation/compose/narrativeCompose.d.ts +1 -0
  15. package/dist/presentation/compose/narrativeCompose.d.ts.map +1 -1
  16. package/dist/presentation/compose/narrativeCompose.js +17 -102
  17. package/dist/presentation/dto/dashboardCardPresentation.d.ts +29 -0
  18. package/dist/presentation/dto/dashboardCardPresentation.d.ts.map +1 -0
  19. package/dist/presentation/dto/scanDetailsPresentation.d.ts +2 -0
  20. package/dist/presentation/dto/scanDetailsPresentation.d.ts.map +1 -1
  21. package/dist/presentation/dto/types.d.ts +0 -9
  22. package/dist/presentation/dto/types.d.ts.map +1 -1
  23. package/dist/presentation/dto/types.js +1 -22
  24. package/dist/presentation/fixtures/dashboardCardGolden.test.d.ts +2 -0
  25. package/dist/presentation/fixtures/dashboardCardGolden.test.d.ts.map +1 -0
  26. package/dist/presentation/fixtures/dashboardCardGolden.test.js +31 -0
  27. package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts +11 -0
  28. package/dist/presentation/fixtures/presentationPersonaFixtures.d.ts.map +1 -0
  29. package/dist/presentation/fixtures/presentationPersonaFixtures.js +197 -0
  30. package/dist/presentation/index.d.ts +8 -5
  31. package/dist/presentation/index.d.ts.map +1 -1
  32. package/dist/presentation/index.js +3 -3
  33. package/dist/presentation/intent/applyPostureVocabularyGuard.d.ts +5 -0
  34. package/dist/presentation/intent/applyPostureVocabularyGuard.d.ts.map +1 -0
  35. package/dist/presentation/intent/applyPostureVocabularyGuard.js +44 -0
  36. package/dist/presentation/intent/derivePresentationInterpretation.d.ts +4 -0
  37. package/dist/presentation/intent/derivePresentationInterpretation.d.ts.map +1 -0
  38. package/dist/presentation/intent/derivePresentationInterpretation.js +202 -0
  39. package/dist/presentation/intent/formatPresentationCopy.d.ts +8 -0
  40. package/dist/presentation/intent/formatPresentationCopy.d.ts.map +1 -0
  41. package/dist/presentation/intent/formatPresentationCopy.js +140 -0
  42. package/dist/presentation/intent/packageDisplayName.d.ts +4 -0
  43. package/dist/presentation/intent/packageDisplayName.d.ts.map +1 -0
  44. package/dist/presentation/intent/packageDisplayName.js +17 -0
  45. package/dist/presentation/intent/postureVocabularyGuard.test.d.ts +2 -0
  46. package/dist/presentation/intent/postureVocabularyGuard.test.d.ts.map +1 -0
  47. package/dist/presentation/intent/postureVocabularyGuard.test.js +23 -0
  48. package/dist/presentation/intent/presentationIntent.d.ts +14 -0
  49. package/dist/presentation/intent/presentationIntent.d.ts.map +1 -0
  50. package/dist/presentation/intent/presentationIntent.js +1 -0
  51. package/dist/presentation/intent/presentationIntent.test.d.ts +2 -0
  52. package/dist/presentation/intent/presentationIntent.test.d.ts.map +1 -0
  53. package/dist/presentation/intent/presentationIntent.test.js +29 -0
  54. package/dist/presentation/intent/verificationFocusLabels.d.ts +2 -0
  55. package/dist/presentation/intent/verificationFocusLabels.d.ts.map +1 -0
  56. package/dist/presentation/intent/verificationFocusLabels.js +45 -0
  57. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts +2 -2
  58. package/dist/presentation/orchestration/buildScanCardPresentation.d.ts.map +1 -1
  59. package/dist/presentation/orchestration/buildScanCardPresentation.js +5 -6
  60. package/dist/presentation/presentation.test.js +18 -17
  61. package/dist/presentation/presentationPersonas.test.d.ts +2 -0
  62. package/dist/presentation/presentationPersonas.test.d.ts.map +1 -0
  63. package/dist/presentation/presentationPersonas.test.js +167 -0
  64. package/dist/presentation/presenters/presentCliScanSummary.d.ts.map +1 -1
  65. package/dist/presentation/presenters/presentCliScanSummary.js +2 -2
  66. package/dist/presentation/presenters/presentDashboardCard.d.ts +5 -0
  67. package/dist/presentation/presenters/presentDashboardCard.d.ts.map +1 -0
  68. package/dist/presentation/presenters/presentDashboardCard.js +117 -0
  69. package/dist/presentation/presenters/presentGitHubCheckRun.d.ts.map +1 -1
  70. package/dist/presentation/presenters/presentGitHubCheckRun.js +2 -2
  71. package/dist/presentation/presenters/presentScanDetails.d.ts.map +1 -1
  72. package/dist/presentation/presenters/presentScanDetails.js +13 -4
  73. package/dist/presentation/profile/derivePresentationProfile.d.ts.map +1 -1
  74. package/dist/presentation/profile/derivePresentationProfile.js +18 -27
  75. package/dist/presentation/profile/presentationProfile.d.ts +2 -0
  76. package/dist/presentation/profile/presentationProfile.d.ts.map +1 -1
  77. package/dist/repoIntelligenceSchema.d.ts +1 -1
  78. package/dist/scanCardSummary.d.ts +3 -4
  79. package/dist/scanCardSummary.d.ts.map +1 -1
  80. package/dist/scanCardSummary.test.js +16 -16
  81. package/dist/scanNarrativeFacts.d.ts +31 -7
  82. package/dist/scanNarrativeFacts.d.ts.map +1 -1
  83. package/dist/scanNarrativeFacts.js +1 -0
  84. package/dist/scanSurfaceCopy.d.ts +8 -0
  85. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  86. package/dist/scanSurfaceCopy.js +8 -0
  87. package/package.json +1 -1
  88. package/dist/presentation/dto/scanCardPresentation.d.ts +0 -25
  89. package/dist/presentation/dto/scanCardPresentation.d.ts.map +0 -1
  90. package/dist/presentation/presenters/presentPipelineScanCard.d.ts +0 -3
  91. package/dist/presentation/presenters/presentPipelineScanCard.d.ts.map +0 -1
  92. package/dist/presentation/presenters/presentPipelineScanCard.js +0 -23
  93. package/dist/presentation/presenters/presentScanCard.d.ts +0 -7
  94. package/dist/presentation/presenters/presentScanCard.d.ts.map +0 -1
  95. package/dist/presentation/presenters/presentScanCard.js +0 -43
  96. /package/dist/presentation/dto/{scanCardPresentation.js → dashboardCardPresentation.js} +0 -0
@@ -0,0 +1,140 @@
1
+ import { scanSurfaceCopy } from "../../scanSurfaceCopy.js";
2
+ import { applyPostureVocabularyGuardHeadline, applyPostureVocabularyGuardLines, } from "./applyPostureVocabularyGuard.js";
3
+ import { formatPackageDisplayName } from "./packageDisplayName.js";
4
+ function anchorSemantics(facts, interpretation) {
5
+ const name = interpretation.anchorPackage;
6
+ if (!name)
7
+ return null;
8
+ return (facts.changedPackageSemantics.find((r) => r.packageName === name) ?? null);
9
+ }
10
+ export function formatPresentationHeadline(interpretation, facts, status) {
11
+ const anchor = interpretation.anchorPackage ?? facts.changedPackages.primary;
12
+ const row = anchorSemantics(facts, interpretation);
13
+ const pkgDisplay = formatPackageDisplayName(anchor ?? "dependency", row?.packageRole);
14
+ const copy = scanSurfaceCopy.presentation;
15
+ let headline;
16
+ switch (interpretation.intent) {
17
+ case "limited_context":
18
+ headline = copy.limitedContextHeadline;
19
+ break;
20
+ case "no_changed_packages":
21
+ headline = copy.noChangedPackagesHeadline;
22
+ break;
23
+ case "tooling_patch":
24
+ headline = copy.toolingPatchHeadline.replace("{package}", pkgDisplay);
25
+ break;
26
+ case "tooling_upgrade":
27
+ headline = copy.toolingUpgradeHeadline.replace("{package}", pkgDisplay);
28
+ break;
29
+ case "multi_runtime_upgrade":
30
+ headline = copy.multiRuntimeHeadline;
31
+ break;
32
+ case "auth_runtime_upgrade":
33
+ case "queue_runtime_upgrade":
34
+ case "runtime_upgrade": {
35
+ const surface = interpretation.runtimeSurfaceLabel ?? "application code";
36
+ headline = copy.runtimeUpgradeHeadline
37
+ .replace("{package}", pkgDisplay)
38
+ .replace("{surface}", surface);
39
+ break;
40
+ }
41
+ case "unknown_upgrade":
42
+ if (status === "safe") {
43
+ headline = copy.safeNeutralHeadline.replace("{package}", pkgDisplay);
44
+ }
45
+ else {
46
+ headline = copy.defaultUpgradeHeadline.replace("{package}", pkgDisplay);
47
+ }
48
+ break;
49
+ default:
50
+ headline = copy.safeNeutralHeadline.replace("{package}", pkgDisplay);
51
+ }
52
+ return applyPostureVocabularyGuardHeadline(headline, status, interpretation.anchorPackage);
53
+ }
54
+ function formatExpectedImpactKeyPoint(interpretation) {
55
+ if (interpretation.intent !== "tooling_patch" &&
56
+ interpretation.intent !== "tooling_upgrade") {
57
+ return null;
58
+ }
59
+ const key = interpretation.expectedImpactKey;
60
+ let workflows = "build and typecheck workflows";
61
+ if (key === "test_time")
62
+ workflows = "test workflows";
63
+ else if (key === "development_only")
64
+ workflows = "development workflows";
65
+ else if (key === "build_time")
66
+ workflows = "build workflows";
67
+ return scanSurfaceCopy.presentation.expectedImpactLimitedKeyPoint.replace("{workflows}", workflows);
68
+ }
69
+ function formatLimitedEvidenceKeyPoint() {
70
+ return scanSurfaceCopy.presentation.limitedEvidenceKeyPoint;
71
+ }
72
+ /** Atomic insight lines — no embedded areas or blast radius (those live in separate channels). */
73
+ export function formatPresentationInsightLines(interpretation, facts, status) {
74
+ const points = [];
75
+ if (interpretation.intent === "limited_context") {
76
+ points.push(scanSurfaceCopy.presentation.limitedContextKeyPoint);
77
+ if (facts.changedPackages.primary) {
78
+ points.push(scanSurfaceCopy.presentation.changedPackageKeyPoint.replace("{package}", facts.changedPackages.primary));
79
+ }
80
+ return applyPostureVocabularyGuardLines(points, status);
81
+ }
82
+ if (interpretation.showLimitedEvidence) {
83
+ points.push(formatLimitedEvidenceKeyPoint());
84
+ }
85
+ if (interpretation.suppressRuntimeNarrative) {
86
+ points.push(scanSurfaceCopy.presentation.noRuntimePathsKeyPoint);
87
+ const impact = formatExpectedImpactKeyPoint(interpretation);
88
+ if (impact)
89
+ points.push(impact);
90
+ }
91
+ else if (interpretation.allowRuntimeNarrative) {
92
+ const usage = facts.packageUsage.find((u) => u.packageName === interpretation.anchorPackage);
93
+ const paths = [...(usage?.paths ?? []), ...(usage?.criticalPaths ?? [])];
94
+ if (paths.length > 0) {
95
+ points.push(scanSurfaceCopy.presentation.usedInRuntimePathsKeyPoint.replace("{count}", String(paths.length)));
96
+ }
97
+ else {
98
+ points.push(scanSurfaceCopy.presentation.runtimeUsageKeyPoint);
99
+ }
100
+ }
101
+ return applyPostureVocabularyGuardLines(points, status);
102
+ }
103
+ const AUTH_VERIFICATION = [
104
+ "routes",
105
+ "middleware/hooks",
106
+ "auth flow",
107
+ "serialization",
108
+ "error handling",
109
+ ];
110
+ const QUEUE_VERIFICATION = [
111
+ "workers",
112
+ "queue",
113
+ "serialization",
114
+ "error handling",
115
+ ];
116
+ const RUNTIME_DEFAULT_VERIFICATION = [
117
+ "routes",
118
+ "middleware/hooks",
119
+ "error handling",
120
+ ];
121
+ export function formatPresentationVerification(interpretation, status, max) {
122
+ let actions = [];
123
+ if (interpretation.verificationLabels.length > 0) {
124
+ actions = [...interpretation.verificationLabels];
125
+ }
126
+ else if (interpretation.intent === "auth_runtime_upgrade") {
127
+ actions = [...AUTH_VERIFICATION];
128
+ }
129
+ else if (interpretation.intent === "queue_runtime_upgrade") {
130
+ actions = [...QUEUE_VERIFICATION];
131
+ }
132
+ else if (interpretation.allowRuntimeNarrative) {
133
+ actions = [...RUNTIME_DEFAULT_VERIFICATION];
134
+ }
135
+ else if (interpretation.intent === "tooling_patch" ||
136
+ interpretation.intent === "tooling_upgrade") {
137
+ actions = ["CI", "typecheck", "build"];
138
+ }
139
+ return applyPostureVocabularyGuardLines(actions, status).slice(0, max);
140
+ }
@@ -0,0 +1,4 @@
1
+ import type { PackageRoleKind } from "../../scanNarrativeFacts.js";
2
+ /** Display name from package role when engine classified it; else title-case package name. */
3
+ export declare function formatPackageDisplayName(packageName: string, packageRole?: PackageRoleKind | null): string;
4
+ //# sourceMappingURL=packageDisplayName.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packageDisplayName.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/packageDisplayName.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AASnE,8FAA8F;AAC9F,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,eAAe,GAAG,IAAI,GACnC,MAAM,CAOR"}
@@ -0,0 +1,17 @@
1
+ const ROLE_DISPLAY = {
2
+ typechecker: "TypeScript",
3
+ linter: "ESLint",
4
+ formatter: "Prettier",
5
+ test_runner: "Test runner",
6
+ };
7
+ /** Display name from package role when engine classified it; else title-case package name. */
8
+ export function formatPackageDisplayName(packageName, packageRole) {
9
+ if (packageRole && ROLE_DISPLAY[packageRole]) {
10
+ return ROLE_DISPLAY[packageRole];
11
+ }
12
+ if (!packageName)
13
+ return packageName;
14
+ if (packageName.length <= 3)
15
+ return packageName.toUpperCase();
16
+ return packageName.charAt(0).toUpperCase() + packageName.slice(1);
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=postureVocabularyGuard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postureVocabularyGuard.test.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/postureVocabularyGuard.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { applyPostureVocabularyGuardHeadline, applyPostureVocabularyGuardLines, guardHeadlineForPosture, } from "./applyPostureVocabularyGuard.js";
3
+ describe("applyPostureVocabularyGuard", () => {
4
+ it("substitutes safe headline when review language present", () => {
5
+ const out = guardHeadlineForPosture("typescript dependency upgrade needs review", "safe", "typescript");
6
+ expect(out.toLowerCase()).not.toContain("needs review");
7
+ expect(out.toLowerCase()).toContain("typescript");
8
+ });
9
+ it("filters safe-forbidden key points", () => {
10
+ const lines = applyPostureVocabularyGuardLines(["No runtime usage paths detected", "High risk upgrade path"], "safe");
11
+ expect(lines).toContain("No runtime usage paths detected");
12
+ expect(lines.some((l) => /high risk/i.test(l))).toBe(false);
13
+ });
14
+ it("filters needs_review forbidden verification", () => {
15
+ const lines = applyPostureVocabularyGuardLines(["Confirm CI passes", "No action required"], "needs_review");
16
+ expect(lines).toContain("Confirm CI passes");
17
+ expect(lines.some((l) => /no action required/i.test(l))).toBe(false);
18
+ });
19
+ it("allows review headline for needs_review posture", () => {
20
+ const out = applyPostureVocabularyGuardHeadline("lodash dependency upgrade needs review", "needs_review", "lodash");
21
+ expect(out).toContain("needs review");
22
+ });
23
+ });
@@ -0,0 +1,14 @@
1
+ import type { PackageExpectedImpact } from "../../scanNarrativeFacts.js";
2
+ export type PresentationIntent = "limited_context" | "no_changed_packages" | "tooling_patch" | "tooling_upgrade" | "runtime_upgrade" | "auth_runtime_upgrade" | "queue_runtime_upgrade" | "multi_runtime_upgrade" | "unknown_upgrade";
3
+ export type PresentationInterpretation = {
4
+ intent: PresentationIntent;
5
+ anchorPackage: string | null;
6
+ suppressRuntimeNarrative: boolean;
7
+ allowRuntimeNarrative: boolean;
8
+ showLimitedEvidence: boolean;
9
+ expectedImpactKey: PackageExpectedImpact | null;
10
+ verificationLabels: string[];
11
+ runtimeSurfaceLabel: string | null;
12
+ runtimePackages: string[];
13
+ };
14
+ //# sourceMappingURL=presentationIntent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentationIntent.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/presentationIntent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEzE,MAAM,MAAM,kBAAkB,GAC1B,iBAAiB,GACjB,qBAAqB,GACrB,eAAe,GACf,iBAAiB,GACjB,iBAAiB,GACjB,sBAAsB,GACtB,uBAAuB,GACvB,uBAAuB,GACvB,iBAAiB,CAAC;AAEtB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wBAAwB,EAAE,OAAO,CAAC;IAClC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=presentationIntent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentationIntent.test.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/presentationIntent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { deriveScanNarrative } from "../../deriveScanNarrative.js";
3
+ import { scanResultMixedTypescriptFastify, scanResultTypescriptPatch, scanResultFastifyRuntime, } from "../fixtures/presentationPersonaFixtures.js";
4
+ import { derivePresentationInterpretation } from "./derivePresentationInterpretation.js";
5
+ describe("derivePresentationInterpretation", () => {
6
+ it("typescript patch: tooling_patch intent from engine semantics", () => {
7
+ const facts = deriveScanNarrative(scanResultTypescriptPatch);
8
+ const interp = derivePresentationInterpretation(facts);
9
+ // Engine: suppressRuntimeNarrative=true, expectedImpact=typecheck, dependencyClass=tooling
10
+ expect(interp.intent).toBe("tooling_patch");
11
+ expect(interp.anchorPackage).toBe("typescript");
12
+ expect(interp.suppressRuntimeNarrative).toBe(true);
13
+ });
14
+ it("fastify: runtime_upgrade from runtimeImpact=confirmed", () => {
15
+ const facts = deriveScanNarrative(scanResultFastifyRuntime);
16
+ const interp = derivePresentationInterpretation(facts);
17
+ expect(interp.intent).toBe("runtime_upgrade");
18
+ expect(interp.anchorPackage).toBe("fastify");
19
+ expect(interp.allowRuntimeNarrative).toBe(true);
20
+ });
21
+ it("mixed typescript+fastify: runtime dominates tooling (anchor fastify)", () => {
22
+ const facts = deriveScanNarrative(scanResultMixedTypescriptFastify);
23
+ const interp = derivePresentationInterpretation(facts);
24
+ expect(interp.intent).toBe("runtime_upgrade");
25
+ expect(interp.anchorPackage).toBe("fastify");
26
+ expect(interp.runtimePackages).toContain("fastify");
27
+ expect(interp.runtimePackages).not.toContain("typescript");
28
+ });
29
+ });
@@ -0,0 +1,2 @@
1
+ export declare function mergeVerificationFocusLabels(focusLists: string[][], max?: number): string[];
2
+ //# sourceMappingURL=verificationFocusLabels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verificationFocusLabels.d.ts","sourceRoot":"","sources":["../../../src/presentation/intent/verificationFocusLabels.ts"],"names":[],"mappings":"AA2BA,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAAE,EAAE,EACtB,GAAG,SAAI,GACN,MAAM,EAAE,CAeV"}
@@ -0,0 +1,45 @@
1
+ const FOCUS_LABELS = {
2
+ ci: "CI",
3
+ typecheck: "typecheck",
4
+ build: "build",
5
+ routes: "routes",
6
+ middleware: "middleware/hooks",
7
+ hooks: "middleware/hooks",
8
+ auth_flow: "auth flow",
9
+ auth: "auth flow",
10
+ serialization: "serialization",
11
+ error_handling: "error handling",
12
+ handlers: "handlers",
13
+ plugins: "plugins",
14
+ session: "session",
15
+ workers: "workers",
16
+ queue: "queue",
17
+ test_suite: "test suite",
18
+ lint: "lint",
19
+ format: "format",
20
+ };
21
+ function labelVerificationFocus(focus) {
22
+ const key = focus.trim().toLowerCase();
23
+ if (!key)
24
+ return "";
25
+ return FOCUS_LABELS[key] ?? focus.replace(/_/g, " ");
26
+ }
27
+ export function mergeVerificationFocusLabels(focusLists, max = 6) {
28
+ const seen = new Set();
29
+ const out = [];
30
+ for (const list of focusLists) {
31
+ for (const f of list) {
32
+ const label = labelVerificationFocus(f);
33
+ if (!label)
34
+ continue;
35
+ const norm = label.toLowerCase();
36
+ if (seen.has(norm))
37
+ continue;
38
+ seen.add(norm);
39
+ out.push(label);
40
+ if (out.length >= max)
41
+ return out;
42
+ }
43
+ }
44
+ return out;
45
+ }
@@ -1,11 +1,11 @@
1
1
  import type { PipelineStatus } from "../dto/types.js";
2
2
  import type { ScanResult } from "../../types.js";
3
- import type { ScanCardPresentation } from "../dto/scanCardPresentation.js";
3
+ import type { DashboardCardPresentation } from "../dto/dashboardCardPresentation.js";
4
4
  export type BuildScanCardPresentationInput = {
5
5
  pipelineStatus: PipelineStatus;
6
6
  result: ScanResult | null;
7
7
  decision?: string | null;
8
8
  totalScore?: number | null;
9
9
  };
10
- export declare function buildScanCardPresentation(input: BuildScanCardPresentationInput): ScanCardPresentation;
10
+ export declare function buildScanCardPresentation(input: BuildScanCardPresentationInput): DashboardCardPresentation;
11
11
  //# sourceMappingURL=buildScanCardPresentation.d.ts.map
@@ -1 +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"}
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;AAOjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qCAAqC,CAAC;AAErF,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,yBAAyB,CA2B3B"}
@@ -1,7 +1,6 @@
1
1
  import { mergePostureFromDecision } from "../../riskVocabulary.js";
2
2
  import { buildScanPresentationBundle } from "./buildScanPresentationBundle.js";
3
- import { presentScanCard } from "../presenters/presentScanCard.js";
4
- import { presentPipelineScanCard } from "../presenters/presentPipelineScanCard.js";
3
+ import { presentDashboardCard, presentPipelineDashboardCard, } from "../presenters/presentDashboardCard.js";
5
4
  function minimalResultFromDenormalized(decision, totalScore) {
6
5
  const posture = mergePostureFromDecision(decision ?? undefined);
7
6
  if (!posture && (totalScore == null || !Number.isFinite(totalScore))) {
@@ -24,12 +23,12 @@ function minimalResultFromDenormalized(decision, totalScore) {
24
23
  }
25
24
  export function buildScanCardPresentation(input) {
26
25
  if (input.pipelineStatus !== "done") {
27
- return presentPipelineScanCard(input.pipelineStatus === "failed" ? "failed" : input.pipelineStatus);
26
+ return presentPipelineDashboardCard(input.pipelineStatus === "failed" ? "failed" : input.pipelineStatus);
28
27
  }
29
28
  const result = input.result ??
30
29
  minimalResultFromDenormalized(input.decision, input.totalScore);
31
30
  if (!result) {
32
- return presentPipelineScanCard("failed");
31
+ return presentPipelineDashboardCard("failed");
33
32
  }
34
33
  const bundle = buildScanPresentationBundle({
35
34
  result,
@@ -38,7 +37,7 @@ export function buildScanCardPresentation(input) {
38
37
  totalScore: input.totalScore,
39
38
  });
40
39
  if (!bundle) {
41
- return presentPipelineScanCard("failed");
40
+ return presentPipelineDashboardCard("failed");
42
41
  }
43
- return presentScanCard(bundle, { includeFindingCounts: true });
42
+ return presentDashboardCard(bundle);
44
43
  }
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { buildScanPresentationBundle } from "./orchestration/buildScanPresentationBundle.js";
3
- import { presentScanCard } from "./presenters/presentScanCard.js";
3
+ import { presentDashboardCard } from "./presenters/presentDashboardCard.js";
4
4
  import { presentScanDetails } from "./presenters/presentScanDetails.js";
5
5
  import { presentGitHubCheckRun } from "./presenters/presentGitHubCheckRun.js";
6
6
  import { presentGitHubPrComment } from "./presenters/presentGitHubPrComment.js";
@@ -43,29 +43,30 @@ describe("buildScanPresentationBundle", () => {
43
43
  expect(bundle.profile.degradedMessage).toBeTruthy();
44
44
  });
45
45
  });
46
- describe("presentScanCard personas", () => {
47
- it("fastify card is rich with affected areas and verification", () => {
46
+ describe("presentDashboardCard personas", () => {
47
+ it("fastify card has scope areas and verification", () => {
48
48
  const bundle = buildScanPresentationBundle({
49
49
  result: scanResultFastifyRuntime,
50
50
  pipelineStatus: "done",
51
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);
52
+ const card = presentDashboardCard(bundle);
53
+ expect(card.layout).toBe("expanded");
54
+ expect(card.scopeAreas.length).toBeGreaterThan(0);
55
+ expect(card.verification.length).toBeGreaterThan(0);
56
56
  expect(card.headline.toLowerCase()).toContain("fastify");
57
- expect(card.supportingContext).toBeUndefined();
57
+ expect(card.verdict?.posture).toBe("needs_review");
58
58
  });
59
- it("typescript card is minimal safe without graph-first story", () => {
59
+ it("typescript card is quiet safe", () => {
60
60
  const bundle = buildScanPresentationBundle({
61
61
  result: scanResultTypescriptPatch,
62
62
  pipelineStatus: "done",
63
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);
64
+ const card = presentDashboardCard(bundle);
65
+ expect(["quiet", "standard"]).toContain(card.layout);
66
+ expect(card.verdict?.posture).toBe("safe");
67
+ expect(card.headline).toMatch(/patch upgrade/i);
68
+ expect(card.headline).not.toMatch(/needs review/i);
69
+ expect(card.verification.length).toBeGreaterThan(0);
69
70
  });
70
71
  });
71
72
  describe("presentation parity across surfaces", () => {
@@ -74,7 +75,7 @@ describe("presentation parity across surfaces", () => {
74
75
  result: scanResultFastifyRuntime,
75
76
  pipelineStatus: "done",
76
77
  });
77
- const card = presentScanCard(bundle);
78
+ const card = presentDashboardCard(bundle);
78
79
  const details = presentScanDetails(bundle, { scanId: SCAN_ID });
79
80
  const check = presentGitHubCheckRun(bundle, {
80
81
  scanId: SCAN_ID,
@@ -82,13 +83,13 @@ describe("presentation parity across surfaces", () => {
82
83
  });
83
84
  const comment = presentGitHubPrComment(bundle);
84
85
  const cli = presentCliScanSummary(bundle, { repoLabel: "acme/api" });
85
- for (const dto of [card, details, check, comment, cli]) {
86
+ for (const dto of [details, check, comment, cli]) {
86
87
  expect(dto.status).toBe(bundle.profile.status);
87
88
  expect(dto.density).toBe(bundle.profile.density);
88
89
  expect(dto.confidence).toBe(bundle.profile.confidence);
89
90
  expect(dto.evidenceContext?.priority).toBe(bundle.profile.priority);
90
91
  }
91
- expect(card.primaryPackage).toBe("fastify");
92
+ expect(card.verdict?.posture).toBe(bundle.profile.status);
92
93
  expect(details.narrative.primaryPackage).toBe("fastify");
93
94
  expect(check.title.toLowerCase()).toContain("fastify");
94
95
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=presentationPersonas.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentationPersonas.test.d.ts","sourceRoot":"","sources":["../../src/presentation/presentationPersonas.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,167 @@
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 { presentCliScanSummary } from "./presenters/presentCliScanSummary.js";
6
+ import { scanResultBullmq, scanResultEslint, scanResultFastifyRuntime, scanResultLimitedContext, scanResultMixedTypescriptFastify, scanResultNextAuth, scanResultPrettier, scanResultTypescriptPatch, scanResultUnknownSafe, scanResultVitest, } from "./fixtures/presentationPersonaFixtures.js";
7
+ const SCAN_ID = "22222222-2222-4222-8222-222222222222";
8
+ const SAFE_FORBIDDEN = /needs review|high risk|critical concern/i;
9
+ const REVIEW_FORBIDDEN = /no action required|no verification required/i;
10
+ const personas = [
11
+ {
12
+ name: "typescript patch",
13
+ result: scanResultTypescriptPatch,
14
+ intent: "tooling_patch",
15
+ status: "safe",
16
+ density: "minimal",
17
+ headlineMatch: /patch upgrade/i,
18
+ headlineMustNot: /needs review/i,
19
+ verificationIncludes: ["CI", "typecheck"],
20
+ verificationMustNot: /auth flow/i,
21
+ },
22
+ {
23
+ name: "eslint",
24
+ result: scanResultEslint,
25
+ intent: "tooling_upgrade",
26
+ status: "safe",
27
+ density: "minimal",
28
+ headlineMatch: /tooling upgrade/i,
29
+ headlineMustNot: /needs review/i,
30
+ verificationIncludes: ["CI", "lint"],
31
+ },
32
+ {
33
+ name: "prettier",
34
+ result: scanResultPrettier,
35
+ intent: "tooling_upgrade",
36
+ status: "safe",
37
+ density: "minimal",
38
+ headlineMatch: /tooling upgrade/i,
39
+ headlineMustNot: /needs review/i,
40
+ verificationIncludes: ["format"],
41
+ },
42
+ {
43
+ name: "vitest",
44
+ result: scanResultVitest,
45
+ intent: "tooling_upgrade",
46
+ status: "safe",
47
+ density: "minimal",
48
+ headlineMatch: /tooling upgrade/i,
49
+ headlineMustNot: /needs review/i,
50
+ verificationIncludes: ["test suite"],
51
+ },
52
+ {
53
+ name: "fastify runtime",
54
+ result: scanResultFastifyRuntime,
55
+ intent: "runtime_upgrade",
56
+ status: "needs_review",
57
+ density: "rich",
58
+ headlineMatch: /runtime upgrade affects/i,
59
+ verificationIncludes: ["routes", "middleware"],
60
+ },
61
+ {
62
+ name: "nextauth runtime",
63
+ result: scanResultNextAuth,
64
+ intent: "auth_runtime_upgrade",
65
+ status: "needs_review",
66
+ density: "rich",
67
+ headlineMatch: /runtime upgrade affects/i,
68
+ verificationIncludes: ["auth flow"],
69
+ },
70
+ {
71
+ name: "bullmq runtime",
72
+ result: scanResultBullmq,
73
+ intent: "queue_runtime_upgrade",
74
+ status: "needs_review",
75
+ density: "rich",
76
+ headlineMatch: /runtime upgrade affects/i,
77
+ verificationIncludes: ["workers", "queue"],
78
+ },
79
+ {
80
+ name: "limited context",
81
+ result: scanResultLimitedContext,
82
+ intent: "limited_context",
83
+ status: "needs_review",
84
+ density: "minimal",
85
+ headlineMatch: /limited scan context/i,
86
+ },
87
+ {
88
+ name: "unknown safe",
89
+ result: scanResultUnknownSafe,
90
+ intent: "unknown_upgrade",
91
+ status: "safe",
92
+ density: "standard",
93
+ headlineMatch: /dependency upgrade/i,
94
+ headlineMustNot: /needs review/i,
95
+ },
96
+ ];
97
+ function cardFor(result) {
98
+ const bundle = buildScanPresentationBundle({
99
+ result,
100
+ pipelineStatus: "done",
101
+ });
102
+ return presentDashboardCard(bundle);
103
+ }
104
+ describe("presentation personas", () => {
105
+ for (const persona of personas) {
106
+ it(`${persona.name}: intent, posture alignment, headline`, () => {
107
+ const card = cardFor(persona.result);
108
+ expect(card.verdict?.posture).toBe(persona.status);
109
+ if (persona.density === "rich") {
110
+ expect(card.layout).toBe("expanded");
111
+ }
112
+ expect(card.headline).toMatch(persona.headlineMatch);
113
+ if (persona.headlineMustNot) {
114
+ expect(card.headline).not.toMatch(persona.headlineMustNot);
115
+ }
116
+ if (persona.status === "safe") {
117
+ const combined = [
118
+ card.headline,
119
+ ...card.insights,
120
+ ...card.verification,
121
+ ].join(" ");
122
+ expect(combined).not.toMatch(SAFE_FORBIDDEN);
123
+ }
124
+ if (persona.status === "needs_review") {
125
+ const combined = card.verification.join(" ");
126
+ expect(combined).not.toMatch(REVIEW_FORBIDDEN);
127
+ }
128
+ if (persona.verificationIncludes) {
129
+ for (const v of persona.verificationIncludes) {
130
+ expect(card.verification.some((a) => a.toLowerCase().includes(v.toLowerCase()))).toBe(true);
131
+ }
132
+ }
133
+ if (persona.verificationMustNot) {
134
+ expect(card.verification.join(" ")).not.toMatch(persona.verificationMustNot);
135
+ }
136
+ });
137
+ }
138
+ it("mixed typescript+fastify: runtime intent, fastify in headline", () => {
139
+ const card = cardFor(scanResultMixedTypescriptFastify);
140
+ expect(card.verdict?.posture).toBe("needs_review");
141
+ expect(card.headline.toLowerCase()).toContain("fastify");
142
+ expect(card.headline).not.toMatch(/patch upgrade/i);
143
+ });
144
+ it("cross-surface parity: fastify headline matches card, details, CLI", () => {
145
+ const bundle = buildScanPresentationBundle({
146
+ result: scanResultFastifyRuntime,
147
+ pipelineStatus: "done",
148
+ });
149
+ const card = presentDashboardCard(bundle);
150
+ const details = presentScanDetails(bundle, { scanId: SCAN_ID });
151
+ const cli = presentCliScanSummary(bundle, { repoLabel: "acme/api" });
152
+ expect(details.hero.headline).toBe(card.headline);
153
+ expect(cli.headline).toBe(card.headline);
154
+ });
155
+ it("cross-surface parity: typescript headline matches card, details, CLI", () => {
156
+ const bundle = buildScanPresentationBundle({
157
+ result: scanResultTypescriptPatch,
158
+ pipelineStatus: "done",
159
+ });
160
+ const card = presentDashboardCard(bundle);
161
+ const details = presentScanDetails(bundle, { scanId: SCAN_ID });
162
+ const cli = presentCliScanSummary(bundle, { repoLabel: "acme/api" });
163
+ expect(details.hero.headline).toBe(card.headline);
164
+ expect(cli.headline).toBe(card.headline);
165
+ expect(card.headline).not.toMatch(/needs review/i);
166
+ });
167
+ });
@@ -1 +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"}
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;AAEzF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,GAAG,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,mBAAmB,CA6BrB"}
@@ -1,4 +1,4 @@
1
- import { composeHeadline, composeKeyPoints, composeSubheadline, composeSupportingContext, composeVerificationActions, evidenceContextFromProfile, } from "../compose/narrativeCompose.js";
1
+ import { buildNarrativeChannels, composeHeadline, composeSubheadline, composeSupportingContext, composeVerificationActions, evidenceContextFromProfile, projectCompactKeyPoints, } from "../compose/narrativeCompose.js";
2
2
  export function presentCliScanSummary(bundle, ctx = {}) {
3
3
  const { facts, profile, result } = bundle;
4
4
  const layers = result.layerScores;
@@ -17,7 +17,7 @@ export function presentCliScanSummary(bundle, ctx = {}) {
17
17
  evidenceContext: evidenceContextFromProfile(bundle),
18
18
  headline: composeHeadline(bundle),
19
19
  subheadline: composeSubheadline(bundle),
20
- keyPoints: composeKeyPoints(bundle, 6),
20
+ keyPoints: projectCompactKeyPoints(buildNarrativeChannels(bundle), 6),
21
21
  verificationActions: composeVerificationActions(bundle, 5),
22
22
  metrics: {
23
23
  riskIndex: facts.riskIndex ?? result.totalScore ?? 0,