@mergesignal/shared 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./formatInsight.js";
3
3
  export * from "./riskVocabulary.js";
4
4
  export * from "./selectTopAffectedAreas.js";
5
5
  export * from "./deriveScanSummaryText.js";
6
+ export * from "./scanCardSummary.js";
6
7
  export * from "./scanResultSchema.js";
7
8
  export * from "./scanSurfaceCopy.js";
8
9
  export * from "./trustedScanGuards.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export * from "./formatInsight.js";
3
3
  export * from "./riskVocabulary.js";
4
4
  export * from "./selectTopAffectedAreas.js";
5
5
  export * from "./deriveScanSummaryText.js";
6
+ export * from "./scanCardSummary.js";
6
7
  export * from "./scanResultSchema.js";
7
8
  export * from "./scanSurfaceCopy.js";
8
9
  export * from "./trustedScanGuards.js";
@@ -1 +1 @@
1
- {"version":3,"file":"prCheckRunPresentation.d.ts","sourceRoot":"","sources":["../src/prCheckRunPresentation.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAIV,UAAU,EAEX,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAqB7C,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,iDAAiD;AACjD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/D,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAUnD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,sBAA2B,GAChC,MAAM,CAMR;AA4BD,sEAAsE;AACtE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAGrE;AAkGD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,IAAI,CAAC,wBAAwB,EAAE,UAAU,CAAC,GAC9C,cAAc,CA0BhB;AAqBD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,wBAAwB,GAC5B,eAAe,EAAE,CAmDnB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,CA0E1E;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,wBAAwB,GAC5B,MAAM,CAIR;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAExE"}
1
+ {"version":3,"file":"prCheckRunPresentation.d.ts","sourceRoot":"","sources":["../src/prCheckRunPresentation.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAIV,UAAU,EAEX,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAC3C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAqB7C,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,iDAAiD;AACjD,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/D,GACD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAUnD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,sBAA2B,GAChC,MAAM,CAMR;AA4BD,sEAAsE;AACtE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAGrE;AAkGD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,IAAI,CAAC,wBAAwB,EAAE,UAAU,CAAC,GAC9C,cAAc,CA0BhB;AAqBD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,wBAAwB,GAC5B,eAAe,EAAE,CAmDnB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,CA4E1E;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,wBAAwB,GAC5B,MAAM,CAIR;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAExE"}
@@ -40,7 +40,7 @@ export function formatScanDashboardUrl(webAppOrigin, scanId) {
40
40
  export function buildPrCheckRunTitle(opts = {}) {
41
41
  const base = scanSurfaceCopy.checkRun.titleBase;
42
42
  if (opts.baselineOnly) {
43
- return `${base} - ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
43
+ return `${base} ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
44
44
  }
45
45
  return base;
46
46
  }
@@ -288,7 +288,7 @@ export function renderCheckRunMarkdown(sections) {
288
288
  break;
289
289
  }
290
290
  case "footer": {
291
- parts.push(`[${section.label}](${section.url})`);
291
+ parts.push(`<a href="${section.url}" target="_blank" rel="noopener noreferrer">${section.label}</a>`);
292
292
  break;
293
293
  }
294
294
  default: {
@@ -32,12 +32,13 @@ function minimalResult(overrides = {}) {
32
32
  };
33
33
  }
34
34
  describe("buildPrCheckRunTitle", () => {
35
- it("uses hyphenated product title", () => {
36
- expect(buildPrCheckRunTitle()).toBe("MergeSignal scan - PR dependency change");
35
+ it("uses short product title without app prefix", () => {
36
+ expect(buildPrCheckRunTitle()).toBe("PR dependency change");
37
+ expect(buildPrCheckRunTitle()).not.toContain("MergeSignal scan");
37
38
  expect(buildPrCheckRunTitle()).not.toContain("—");
38
39
  });
39
40
  it("appends baseline suffix when requested", () => {
40
- expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("MergeSignal scan - PR dependency change - baseline scan only");
41
+ expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change baseline scan only");
41
42
  });
42
43
  });
43
44
  describe("formatScanDashboardUrl", () => {
@@ -99,7 +100,7 @@ describe("buildPrCheckRunSummaryMarkdown", () => {
99
100
  webAppOrigin: ORIGIN,
100
101
  baseline: false,
101
102
  });
102
- expect(md).toContain(`[View full scan](${ORIGIN}/scan/${SCAN_ID})`);
103
+ expect(md).toContain(`<a href="${ORIGIN}/scan/${SCAN_ID}" target="_blank" rel="noopener noreferrer">View full scan</a>`);
103
104
  });
104
105
  it("baseline body omits repo graph score lines", () => {
105
106
  const md = buildPrCheckRunSummaryMarkdown({
@@ -16,6 +16,8 @@ export declare function mergePostureLabel(decision: string | null | undefined, f
16
16
  * e.g. "Risky, risk score 72" or "Safe"
17
17
  */
18
18
  export declare function ariaLabelForPosture(decision: string | null | undefined, score: number | null | undefined): string;
19
+ /** Accessible label for unified PR card risk block. */
20
+ export declare function ariaLabelForCardSummary(headline: string, riskIndex: number | null | undefined, summaryLine: string | null | undefined): string;
19
21
  export type SignalSeverity = "low" | "medium" | "high";
20
22
  /**
21
23
  * Format a signal severity for prose use — always returns a qualified string
@@ -1 +1 @@
1
- {"version":3,"file":"riskVocabulary.d.ts","sourceRoot":"","sources":["../src/riskVocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAkB3D,MAAM,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAEpD,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAI5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAIjE,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,YAAY,GAAG,IAAI,CASrB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,SAAM,GACb,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAIR;AAMD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAQvD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG3E"}
1
+ {"version":3,"file":"riskVocabulary.d.ts","sourceRoot":"","sources":["../src/riskVocabulary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAkB3D,MAAM,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAEpD,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAI5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAIjE,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,YAAY,GAAG,IAAI,CASrB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,SAAM,GACb,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAIR;AAED,uDAAuD;AACvD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,MAAM,CAKR;AAMD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAQvD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG3E"}
@@ -37,6 +37,15 @@ export function ariaLabelForPosture(decision, score) {
37
37
  return `${label}, risk score ${Math.round(score)}`;
38
38
  return label;
39
39
  }
40
+ /** Accessible label for unified PR card risk block. */
41
+ export function ariaLabelForCardSummary(headline, riskIndex, summaryLine) {
42
+ const parts = [headline];
43
+ if (riskIndex != null)
44
+ parts.push(`risk index ${Math.round(riskIndex)}`);
45
+ if (summaryLine?.trim())
46
+ parts.push(summaryLine.trim());
47
+ return parts.join(". ");
48
+ }
40
49
  const SIGNAL_SEVERITY_LABEL = {
41
50
  low: "Low",
42
51
  medium: "Medium",
@@ -0,0 +1,63 @@
1
+ import { type MergePosture } from "./riskVocabulary.js";
2
+ import type { Finding, ScanResult } from "./types.js";
3
+ export type ScanPipelineStatus = "queued" | "running" | "done" | "failed";
4
+ export type ScanCardPresentationState = "not_scanned" | "scanning" | "analysis_failed" | "ready" | "stale";
5
+ export type RiskIndexBand = "low" | "medium" | "high";
6
+ export type FindingCountSummary = {
7
+ critical: number;
8
+ high: number;
9
+ medium: number;
10
+ low: number;
11
+ };
12
+ export type ScanCardSummary = {
13
+ mergePosture: MergePosture | null;
14
+ riskIndex: number | null;
15
+ riskIndexBand: RiskIndexBand | null;
16
+ headline: string;
17
+ summaryLine: string | null;
18
+ findingCounts: FindingCountSummary | null;
19
+ topAffectedAreas: string[];
20
+ };
21
+ export declare const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results\u2026";
22
+ export type PipelineCompletionEvidence = {
23
+ scannedAt?: string | null;
24
+ decision?: string | null;
25
+ totalScore?: number | null;
26
+ hasResult?: boolean;
27
+ };
28
+ /** Scan completion signals — decision/totalScore/result are only set after analysis. */
29
+ export declare function hasScanCompletionEvidence(evidence: PipelineCompletionEvidence): boolean;
30
+ /**
31
+ * Resolve wire status using completion evidence (decision, totalScore, result).
32
+ * Handles races where denormalized columns are written before status flips to done.
33
+ */
34
+ export declare function resolvePipelineStatus(status: string | undefined, evidence: PipelineCompletionEvidence): ScanPipelineStatus;
35
+ /** Pipeline lifecycle copy that must never appear on completed scan cards. */
36
+ export declare function isPipelinePlaceholderCopy(text: string | null | undefined): boolean;
37
+ /** True when summary reflects pipeline lifecycle, not merge posture. */
38
+ export declare function isPipelineCardSummary(summary: ScanCardSummary): boolean;
39
+ /** Single source for risk index color bands (aligns with decision score bands). */
40
+ export declare function deriveRiskIndexBand(score: number | null | undefined): RiskIndexBand | null;
41
+ export declare function aggregateFindingCounts(findings: Finding[] | null | undefined): FindingCountSummary;
42
+ /**
43
+ * Server-side card summary for PR dashboard list views.
44
+ * Pipeline lifecycle copy is included when `pipelineStatus !== "done"`.
45
+ */
46
+ export declare function deriveScanCardSummary(result: ScanResult | null | undefined, pipelineStatus: ScanPipelineStatus): ScanCardSummary;
47
+ /** Subline appended when scan results are stale (head SHA mismatch). */
48
+ export declare function staleScanSubline(): string;
49
+ /** Rebuild card summary from denormalized scan columns when result JSON is unavailable. */
50
+ export declare function deriveScanCardSummaryFromDenormalized(decision: string | null, totalScore: number | null, summaryText: string | null, pipelineStatus: ScanPipelineStatus): ScanCardSummary;
51
+ export type PrScanCardSummaryInput = {
52
+ pipelineStatus: string;
53
+ decision: string | null;
54
+ totalScore: number | null;
55
+ summaryText: string | null;
56
+ result: ScanResult | null;
57
+ scannedAt?: string | null;
58
+ };
59
+ /**
60
+ * Canonical card summary for PR list/dashboard — single derivation path for API + web.
61
+ */
62
+ export declare function resolvePrScanCardSummary(input: PrScanCardSummaryInput): ScanCardSummary;
63
+ //# sourceMappingURL=scanCardSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanCardSummary.d.ts","sourceRoot":"","sources":["../src/scanCardSummary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ import { deriveScanSummaryText } from "./deriveScanSummaryText.js";
2
+ import { mergePostureFromDecision, MERGE_POSTURE_LABEL, } from "./riskVocabulary.js";
3
+ import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
4
+ import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
5
+ const SAFE_SUMMARY = "No merge blockers detected";
6
+ const STALE_SUBLINE = "Based on earlier commit";
7
+ export const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results…";
8
+ function normalizePipelineStatus(status) {
9
+ const st = String(status ?? "")
10
+ .trim()
11
+ .toLowerCase();
12
+ if (st === "queued" || st === "running" || st === "done" || st === "failed") {
13
+ return st;
14
+ }
15
+ return "failed";
16
+ }
17
+ function isMergePostureDecision(decision) {
18
+ return (decision === "safe" || decision === "needs_review" || decision === "risky");
19
+ }
20
+ /** Scan completion signals — decision/totalScore/result are only set after analysis. */
21
+ export function hasScanCompletionEvidence(evidence) {
22
+ if (isMergePostureDecision(evidence.decision))
23
+ return true;
24
+ if (evidence.hasResult === true)
25
+ return true;
26
+ if (evidence.totalScore != null && Number.isFinite(evidence.totalScore)) {
27
+ return true;
28
+ }
29
+ if (evidence.scannedAt?.trim())
30
+ return true;
31
+ return false;
32
+ }
33
+ /**
34
+ * Resolve wire status using completion evidence (decision, totalScore, result).
35
+ * Handles races where denormalized columns are written before status flips to done.
36
+ */
37
+ export function resolvePipelineStatus(status, evidence) {
38
+ const normalized = normalizePipelineStatus(status);
39
+ if (normalized === "done" || normalized === "failed")
40
+ return normalized;
41
+ if (hasScanCompletionEvidence(evidence))
42
+ return "done";
43
+ return normalized;
44
+ }
45
+ /** Pipeline lifecycle copy that must never appear on completed scan cards. */
46
+ export function isPipelinePlaceholderCopy(text) {
47
+ if (!text?.trim())
48
+ return false;
49
+ const t = text.trim();
50
+ return (t === SCAN_CARD_SCANNING_SUMMARY ||
51
+ t === scanSurfaceCopy.pipeline.scanRunning ||
52
+ t === scanSurfaceCopy.pipeline.scanIncomplete ||
53
+ t === scanSurfaceCopy.pipeline.scanUnavailable);
54
+ }
55
+ /** True when summary reflects pipeline lifecycle, not merge posture. */
56
+ export function isPipelineCardSummary(summary) {
57
+ if (isPipelinePlaceholderCopy(summary.summaryLine))
58
+ return true;
59
+ return (summary.mergePosture === null &&
60
+ summary.riskIndex === null &&
61
+ (summary.headline === scanSurfaceCopy.pipeline.scanRunning ||
62
+ summary.headline === scanSurfaceCopy.pipeline.analysisIncomplete ||
63
+ summary.headline === scanSurfaceCopy.pipeline.scanUnavailable));
64
+ }
65
+ /** Single source for risk index color bands (aligns with decision score bands). */
66
+ export function deriveRiskIndexBand(score) {
67
+ if (score == null || !Number.isFinite(score))
68
+ return null;
69
+ if (score > 60)
70
+ return "high";
71
+ if (score > 30)
72
+ return "medium";
73
+ return "low";
74
+ }
75
+ export function aggregateFindingCounts(findings) {
76
+ const counts = {
77
+ critical: 0,
78
+ high: 0,
79
+ medium: 0,
80
+ low: 0,
81
+ };
82
+ if (!Array.isArray(findings))
83
+ return counts;
84
+ for (const f of findings) {
85
+ const s = f.severity;
86
+ if (s === "critical")
87
+ counts.critical += 1;
88
+ else if (s === "high")
89
+ counts.high += 1;
90
+ else if (s === "medium")
91
+ counts.medium += 1;
92
+ else if (s === "low")
93
+ counts.low += 1;
94
+ }
95
+ return counts;
96
+ }
97
+ function hasActionableFindings(counts) {
98
+ return counts.critical + counts.high + counts.medium + counts.low > 0;
99
+ }
100
+ function deriveSafeSummaryLine(posture, counts, fallback) {
101
+ if (posture === "safe" && !hasActionableFindings(counts)) {
102
+ return SAFE_SUMMARY;
103
+ }
104
+ return fallback;
105
+ }
106
+ /**
107
+ * Server-side card summary for PR dashboard list views.
108
+ * Pipeline lifecycle copy is included when `pipelineStatus !== "done"`.
109
+ */
110
+ export function deriveScanCardSummary(result, pipelineStatus) {
111
+ if (pipelineStatus === "queued" || pipelineStatus === "running") {
112
+ return {
113
+ mergePosture: null,
114
+ riskIndex: null,
115
+ riskIndexBand: null,
116
+ headline: scanSurfaceCopy.pipeline.scanRunning,
117
+ summaryLine: SCAN_CARD_SCANNING_SUMMARY,
118
+ findingCounts: null,
119
+ topAffectedAreas: [],
120
+ };
121
+ }
122
+ if (pipelineStatus === "failed") {
123
+ return {
124
+ mergePosture: null,
125
+ riskIndex: null,
126
+ riskIndexBand: null,
127
+ headline: scanSurfaceCopy.pipeline.analysisIncomplete,
128
+ summaryLine: null,
129
+ findingCounts: null,
130
+ topAffectedAreas: [],
131
+ };
132
+ }
133
+ if (!result) {
134
+ return {
135
+ mergePosture: null,
136
+ riskIndex: null,
137
+ riskIndexBand: null,
138
+ headline: scanSurfaceCopy.pipeline.scanUnavailable,
139
+ summaryLine: null,
140
+ findingCounts: null,
141
+ topAffectedAreas: [],
142
+ };
143
+ }
144
+ const posture = mergePostureFromDecision(result.decision?.recommendation) ?? null;
145
+ const riskIndex = typeof result.totalScore === "number" ? result.totalScore : null;
146
+ const riskIndexBand = deriveRiskIndexBand(riskIndex);
147
+ const findingCounts = aggregateFindingCounts(result.findings);
148
+ const summaryFromText = deriveScanSummaryText(result);
149
+ const headline = posture
150
+ ? MERGE_POSTURE_LABEL[posture]
151
+ : scanSurfaceCopy.checkRun.mergePostureUnavailable;
152
+ const summaryLine = posture
153
+ ? deriveSafeSummaryLine(posture, findingCounts, summaryFromText)
154
+ : summaryFromText;
155
+ return {
156
+ mergePosture: posture,
157
+ riskIndex,
158
+ riskIndexBand,
159
+ headline,
160
+ summaryLine,
161
+ findingCounts,
162
+ topAffectedAreas: selectTopAffectedAreas(result, { max: 2 }),
163
+ };
164
+ }
165
+ /** Subline appended when scan results are stale (head SHA mismatch). */
166
+ export function staleScanSubline() {
167
+ return STALE_SUBLINE;
168
+ }
169
+ /** Rebuild card summary from denormalized scan columns when result JSON is unavailable. */
170
+ export function deriveScanCardSummaryFromDenormalized(decision, totalScore, summaryText, pipelineStatus) {
171
+ if (pipelineStatus !== "done") {
172
+ return deriveScanCardSummary(null, pipelineStatus);
173
+ }
174
+ const posture = mergePostureFromDecision(decision);
175
+ const minimalResult = {
176
+ totalScore: totalScore ?? 0,
177
+ layerScores: {
178
+ security: 0,
179
+ maintainability: 0,
180
+ ecosystem: 0,
181
+ upgradeImpact: 0,
182
+ },
183
+ findings: [],
184
+ generatedAt: new Date().toISOString(),
185
+ decision: posture
186
+ ? { recommendation: posture, confidence: "medium", reasoning: [] }
187
+ : undefined,
188
+ };
189
+ const summary = deriveScanCardSummary(minimalResult, "done");
190
+ const sanitized = isPipelinePlaceholderCopy(summaryText)
191
+ ? null
192
+ : (summaryText?.trim() ?? null);
193
+ if (sanitized && !summary.summaryLine) {
194
+ return { ...summary, summaryLine: sanitized };
195
+ }
196
+ if (sanitized &&
197
+ posture &&
198
+ summary.summaryLine === SAFE_SUMMARY &&
199
+ sanitized !== SAFE_SUMMARY) {
200
+ return { ...summary, summaryLine: sanitized };
201
+ }
202
+ return summary;
203
+ }
204
+ /**
205
+ * Canonical card summary for PR list/dashboard — single derivation path for API + web.
206
+ */
207
+ export function resolvePrScanCardSummary(input) {
208
+ const effectivePipeline = resolvePipelineStatus(input.pipelineStatus, {
209
+ decision: input.decision,
210
+ totalScore: input.totalScore,
211
+ hasResult: input.result != null,
212
+ scannedAt: input.scannedAt,
213
+ });
214
+ if (effectivePipeline === "done") {
215
+ const scanResult = input.result ??
216
+ (hasScanCompletionEvidence({
217
+ decision: input.decision,
218
+ totalScore: input.totalScore,
219
+ hasResult: false,
220
+ })
221
+ ? {
222
+ totalScore: input.totalScore ?? 0,
223
+ layerScores: {
224
+ security: 0,
225
+ maintainability: 0,
226
+ ecosystem: 0,
227
+ upgradeImpact: 0,
228
+ },
229
+ findings: [],
230
+ generatedAt: new Date().toISOString(),
231
+ decision: mergePostureFromDecision(input.decision)
232
+ ? {
233
+ recommendation: mergePostureFromDecision(input.decision),
234
+ confidence: "medium",
235
+ reasoning: [],
236
+ }
237
+ : undefined,
238
+ }
239
+ : null);
240
+ if (scanResult) {
241
+ const fromResult = deriveScanCardSummary(scanResult, "done");
242
+ if (!isPipelineCardSummary(fromResult))
243
+ return fromResult;
244
+ }
245
+ return deriveScanCardSummaryFromDenormalized(input.decision, input.totalScore, input.summaryText, "done");
246
+ }
247
+ return deriveScanCardSummary(input.result, effectivePipeline);
248
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scanCardSummary.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanCardSummary.test.d.ts","sourceRoot":"","sources":["../src/scanCardSummary.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,199 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { aggregateFindingCounts, deriveRiskIndexBand, deriveScanCardSummary, deriveScanCardSummaryFromDenormalized, isPipelineCardSummary, isPipelinePlaceholderCopy, resolvePipelineStatus, resolvePrScanCardSummary, staleScanSubline, } from "./scanCardSummary.js";
3
+ const baseResult = {
4
+ totalScore: 10,
5
+ layerScores: {
6
+ security: 1,
7
+ maintainability: 2,
8
+ ecosystem: 3,
9
+ upgradeImpact: 4,
10
+ },
11
+ findings: [],
12
+ generatedAt: "2026-01-01T00:00:00.000Z",
13
+ };
14
+ describe("deriveRiskIndexBand", () => {
15
+ it("maps score thresholds", () => {
16
+ expect(deriveRiskIndexBand(0)).toBe("low");
17
+ expect(deriveRiskIndexBand(30)).toBe("low");
18
+ expect(deriveRiskIndexBand(31)).toBe("medium");
19
+ expect(deriveRiskIndexBand(60)).toBe("medium");
20
+ expect(deriveRiskIndexBand(61)).toBe("high");
21
+ expect(deriveRiskIndexBand(null)).toBeNull();
22
+ });
23
+ });
24
+ describe("aggregateFindingCounts", () => {
25
+ it("counts by severity", () => {
26
+ expect(aggregateFindingCounts([
27
+ {
28
+ id: "1",
29
+ title: "a",
30
+ description: "d",
31
+ severity: "critical",
32
+ packageName: "p",
33
+ },
34
+ {
35
+ id: "2",
36
+ title: "b",
37
+ description: "d",
38
+ severity: "high",
39
+ packageName: "p",
40
+ },
41
+ {
42
+ id: "3",
43
+ title: "c",
44
+ description: "d",
45
+ severity: "high",
46
+ packageName: "p",
47
+ },
48
+ ])).toEqual({ critical: 1, high: 2, medium: 0, low: 0 });
49
+ });
50
+ });
51
+ describe("deriveScanCardSummary", () => {
52
+ it("returns scanning copy for queued/running", () => {
53
+ const queued = deriveScanCardSummary(null, "queued");
54
+ expect(queued.headline).toBe("Scan in progress");
55
+ expect(queued.summaryLine).toBe("Waiting for results…");
56
+ expect(queued.mergePosture).toBeNull();
57
+ const running = deriveScanCardSummary(null, "running");
58
+ expect(running.headline).toBe("Scan in progress");
59
+ });
60
+ it("returns failed copy", () => {
61
+ const failed = deriveScanCardSummary(null, "failed");
62
+ expect(failed.headline).toBe("Analysis could not be completed");
63
+ expect(failed.mergePosture).toBeNull();
64
+ });
65
+ it("derives posture headline and risk index for done scans", () => {
66
+ const r = {
67
+ ...baseResult,
68
+ totalScore: 72,
69
+ decision: {
70
+ recommendation: "risky",
71
+ confidence: "high",
72
+ reasoning: ["Auth boundary change"],
73
+ },
74
+ };
75
+ const summary = deriveScanCardSummary(r, "done");
76
+ expect(summary.mergePosture).toBe("risky");
77
+ expect(summary.headline).toBe("Risky");
78
+ expect(summary.riskIndex).toBe(72);
79
+ expect(summary.riskIndexBand).toBe("high");
80
+ expect(summary.summaryLine).toBe("Auth boundary change");
81
+ });
82
+ it("uses safe copy when posture is safe and no findings", () => {
83
+ const r = {
84
+ ...baseResult,
85
+ totalScore: 12,
86
+ decision: {
87
+ recommendation: "safe",
88
+ confidence: "high",
89
+ reasoning: [],
90
+ },
91
+ };
92
+ const summary = deriveScanCardSummary(r, "done");
93
+ expect(summary.summaryLine).toBe("No merge blockers detected");
94
+ });
95
+ it("caps top affected areas at 2", () => {
96
+ const r = {
97
+ ...baseResult,
98
+ decision: {
99
+ recommendation: "needs_review",
100
+ confidence: "medium",
101
+ reasoning: ["Review needed"],
102
+ },
103
+ explain: {
104
+ reasons: [
105
+ { id: "1", layer: "security", title: "A", scoreImpact: -10 },
106
+ { id: "2", layer: "security", title: "B", scoreImpact: -9 },
107
+ { id: "3", layer: "security", title: "C", scoreImpact: -8 },
108
+ ],
109
+ },
110
+ };
111
+ const summary = deriveScanCardSummary(r, "done");
112
+ expect(summary.topAffectedAreas).toHaveLength(2);
113
+ });
114
+ it("exports stale subline constant", () => {
115
+ expect(staleScanSubline()).toBe("Based on earlier commit");
116
+ });
117
+ });
118
+ describe("resolvePipelineStatus", () => {
119
+ it("promotes running to done when completion evidence exists", () => {
120
+ expect(resolvePipelineStatus("running", {
121
+ scannedAt: "2026-01-01T00:00:00.000Z",
122
+ decision: "risky",
123
+ })).toBe("done");
124
+ });
125
+ it("promotes running to done with decision only (no scannedAt)", () => {
126
+ expect(resolvePipelineStatus("running", {
127
+ decision: "risky",
128
+ })).toBe("done");
129
+ });
130
+ it("promotes running to done with totalScore only", () => {
131
+ expect(resolvePipelineStatus("running", { totalScore: 72 })).toBe("done");
132
+ });
133
+ it("promotes running to done with scannedAt only", () => {
134
+ expect(resolvePipelineStatus("running", {
135
+ scannedAt: "2026-01-01T00:00:00.000Z",
136
+ })).toBe("done");
137
+ });
138
+ it("keeps running when no completion evidence", () => {
139
+ expect(resolvePipelineStatus("running", {})).toBe("running");
140
+ });
141
+ });
142
+ describe("isPipelineCardSummary", () => {
143
+ it("detects scanning placeholder summaries", () => {
144
+ expect(isPipelineCardSummary({
145
+ mergePosture: null,
146
+ riskIndex: null,
147
+ riskIndexBand: null,
148
+ headline: "Scan in progress",
149
+ summaryLine: "Waiting for results…",
150
+ findingCounts: null,
151
+ topAffectedAreas: [],
152
+ })).toBe(true);
153
+ });
154
+ it("detects hybrid completed posture with pipeline summary line", () => {
155
+ expect(isPipelineCardSummary({
156
+ mergePosture: "risky",
157
+ riskIndex: 72,
158
+ riskIndexBand: "high",
159
+ headline: "Risky",
160
+ summaryLine: "Waiting for results…",
161
+ findingCounts: null,
162
+ topAffectedAreas: [],
163
+ })).toBe(true);
164
+ });
165
+ });
166
+ describe("isPipelinePlaceholderCopy", () => {
167
+ it("identifies waiting copy", () => {
168
+ expect(isPipelinePlaceholderCopy("Waiting for results…")).toBe(true);
169
+ expect(isPipelinePlaceholderCopy("Auth change")).toBe(false);
170
+ });
171
+ });
172
+ describe("deriveScanCardSummaryFromDenormalized", () => {
173
+ it("builds risky summary from denormalized columns", () => {
174
+ const summary = deriveScanCardSummaryFromDenormalized("risky", 72, "Auth boundary change", "done");
175
+ expect(summary.mergePosture).toBe("risky");
176
+ expect(summary.headline).toBe("Risky");
177
+ expect(summary.riskIndex).toBe(72);
178
+ expect(summary.summaryLine).toBe("Auth boundary change");
179
+ });
180
+ it("does not inject pipeline placeholder summaryText", () => {
181
+ const summary = deriveScanCardSummaryFromDenormalized("risky", 72, "Waiting for results…", "done");
182
+ expect(summary.mergePosture).toBe("risky");
183
+ expect(summary.summaryLine).not.toBe("Waiting for results…");
184
+ });
185
+ });
186
+ describe("resolvePrScanCardSummary", () => {
187
+ it("returns risk summary when wire status is running but decision exists", () => {
188
+ const summary = resolvePrScanCardSummary({
189
+ pipelineStatus: "running",
190
+ decision: "risky",
191
+ totalScore: 80,
192
+ summaryText: "Waiting for results…",
193
+ result: null,
194
+ });
195
+ expect(summary.mergePosture).toBe("risky");
196
+ expect(summary.headline).toBe("Risky");
197
+ expect(summary.summaryLine).not.toBe("Waiting for results…");
198
+ });
199
+ });
@@ -60,7 +60,7 @@ export declare const scanSurfaceCopy: {
60
60
  };
61
61
  /** GitHub App Check Run (PR) — calm, concise; not Actions step summary. */
62
62
  readonly checkRun: {
63
- readonly titleBase: "MergeSignal scan - PR dependency change";
63
+ readonly titleBase: "PR dependency change";
64
64
  readonly titleBaselineSuffix: "baseline scan only";
65
65
  readonly baselineOutcomePrimary: "No actionable dependency concerns showed up for this PR in this scan.";
66
66
  readonly baselineOutcomeScope: "This run emphasizes repository-wide context; open the scan for PR-specific detail when available.";
@@ -60,7 +60,7 @@ export const scanSurfaceCopy = {
60
60
  },
61
61
  /** GitHub App Check Run (PR) — calm, concise; not Actions step summary. */
62
62
  checkRun: {
63
- titleBase: "MergeSignal scan - PR dependency change",
63
+ titleBase: "PR dependency change",
64
64
  titleBaselineSuffix: "baseline scan only",
65
65
  baselineOutcomePrimary: "No actionable dependency concerns showed up for this PR in this scan.",
66
66
  baselineOutcomeScope: "This run emphasizes repository-wide context; open the scan for PR-specific detail when available.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mergesignal/shared",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "MergeSignal shared scan contract, presentation policy, and trusted validation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",