@mergesignal/shared 0.2.7 → 0.2.8

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 (58) hide show
  1. package/dist/actionsStepSummary.d.ts.map +1 -1
  2. package/dist/actionsStepSummary.js +2 -29
  3. package/dist/collectNarrativeWhyBullets.d.ts +12 -0
  4. package/dist/collectNarrativeWhyBullets.d.ts.map +1 -0
  5. package/dist/collectNarrativeWhyBullets.js +104 -0
  6. package/dist/deriveScanNarrative.d.ts.map +1 -1
  7. package/dist/deriveScanNarrative.js +157 -54
  8. package/dist/deriveScanNarrative.test.js +56 -1
  9. package/dist/fixtures/repoIntelligenceFixtures.d.ts +2 -0
  10. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
  11. package/dist/fixtures/repoIntelligenceFixtures.js +46 -0
  12. package/dist/formatInsight.d.ts +2 -2
  13. package/dist/formatInsight.d.ts.map +1 -1
  14. package/dist/formatInsight.js +2 -7
  15. package/dist/formatInsight.test.js +1 -1
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -0
  19. package/dist/narrativeParity.test.d.ts +2 -0
  20. package/dist/narrativeParity.test.d.ts.map +1 -0
  21. package/dist/narrativeParity.test.js +64 -0
  22. package/dist/narrativePresentation.d.ts +50 -0
  23. package/dist/narrativePresentation.d.ts.map +1 -0
  24. package/dist/narrativePresentation.js +180 -0
  25. package/dist/narrativePresentation.test.d.ts +2 -0
  26. package/dist/narrativePresentation.test.d.ts.map +1 -0
  27. package/dist/narrativePresentation.test.js +53 -0
  28. package/dist/normalizeGeneratedText.d.ts +8 -0
  29. package/dist/normalizeGeneratedText.d.ts.map +1 -0
  30. package/dist/normalizeGeneratedText.js +31 -0
  31. package/dist/normalizeGeneratedText.test.d.ts +2 -0
  32. package/dist/normalizeGeneratedText.test.d.ts.map +1 -0
  33. package/dist/normalizeGeneratedText.test.js +18 -0
  34. package/dist/prCheckRunPresentation.d.ts +4 -3
  35. package/dist/prCheckRunPresentation.d.ts.map +1 -1
  36. package/dist/prCheckRunPresentation.js +56 -51
  37. package/dist/prCheckRunPresentation.test.js +2 -2
  38. package/dist/presentGitHubPrComment.d.ts +9 -0
  39. package/dist/presentGitHubPrComment.d.ts.map +1 -0
  40. package/dist/presentGitHubPrComment.js +80 -0
  41. package/dist/presentScanCardSummary.d.ts.map +1 -1
  42. package/dist/presentScanCardSummary.js +69 -64
  43. package/dist/riskVocabulary.js +1 -1
  44. package/dist/scanCardSummary.d.ts +9 -1
  45. package/dist/scanCardSummary.d.ts.map +1 -1
  46. package/dist/scanCardSummary.js +1 -1
  47. package/dist/scanCardSummary.test.js +12 -8
  48. package/dist/scanDetailViewModel.d.ts +9 -1
  49. package/dist/scanDetailViewModel.d.ts.map +1 -1
  50. package/dist/scanDetailViewModel.js +164 -37
  51. package/dist/scanDetailViewModel.test.js +27 -1
  52. package/dist/scanNarrativeFacts.d.ts +21 -8
  53. package/dist/scanNarrativeFacts.d.ts.map +1 -1
  54. package/dist/scanNarrativeFacts.js +6 -0
  55. package/dist/scanSurfaceCopy.d.ts +26 -26
  56. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  57. package/dist/scanSurfaceCopy.js +26 -26
  58. package/package.json +1 -1
@@ -2,9 +2,12 @@
2
2
  * GitHub Check Run markdown — policy → sections → dumb renderer.
3
3
  * Thin projection of ScanResult for PR decision support (not a presentation framework).
4
4
  */
5
- import { deriveScanSummaryText } from "./deriveScanSummaryText.js";
5
+ import { collectWhyBullets } from "./collectNarrativeWhyBullets.js";
6
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
6
7
  import { formatInsight } from "./formatInsight.js";
7
- import { humanizeEngineSurfaceText, layerDriverSummary, sortPRInsightsForDisplay, sortRecommendationsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
8
+ import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
9
+ import { selectReviewerGuidance } from "./narrativePresentation.js";
10
+ import { layerDriverSummary, sortPRInsightsForDisplay, sortRecommendationsForDisplay, truncateWithEllipsis, } from "./actionsStepSummary.js";
8
11
  import { mergePostureLabel } from "./riskVocabulary.js";
9
12
  import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
10
13
  // --- Caps (change only with tests) ---
@@ -40,43 +43,17 @@ export function formatScanDashboardUrl(webAppOrigin, scanId) {
40
43
  export function buildPrCheckRunTitle(opts = {}) {
41
44
  const base = scanSurfaceCopy.checkRun.titleBase;
42
45
  if (opts.baselineOnly) {
43
- return `${base} ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
46
+ return `${base} - ${scanSurfaceCopy.checkRun.titleBaselineSuffix}`;
44
47
  }
45
48
  return base;
46
49
  }
47
- function collectWhyBullets(result, max) {
48
- const out = [];
49
- const reasoning = result.decision?.reasoning;
50
- if (Array.isArray(reasoning)) {
51
- for (const r of reasoning) {
52
- const s = humanizeEngineSurfaceText(String(r).trim());
53
- if (s && !out.includes(s))
54
- out.push(s);
55
- if (out.length >= max)
56
- return out.slice(0, max);
57
- }
58
- }
59
- const reasons = result.explain?.reasons;
60
- if (Array.isArray(reasons) && out.length < max) {
61
- for (const r of reasons) {
62
- const title = String(r?.title ?? r?.id ?? "").trim();
63
- const readable = humanizeEngineSurfaceText(title);
64
- if (readable && !out.includes(readable))
65
- out.push(readable);
66
- if (out.length >= max)
67
- break;
68
- }
69
- }
70
- if (out.length === 0) {
71
- const one = deriveScanSummaryText(result);
72
- if (one)
73
- out.push(one);
74
- }
75
- return out.slice(0, max);
76
- }
77
50
  /** Strong drivers for optional repo-context line (full mode only). */
78
- export function hasStrongRepoGraphDrivers(result) {
79
- const phrases = collectWhyBullets(result, CHECK_RUN_MAX_REPO_DRIVER_PHRASES);
51
+ export function hasStrongRepoGraphDrivers(result, facts) {
52
+ if (facts?.availability.mode === "pr_intelligence" ||
53
+ facts?.availability.tiersPresent.tier1) {
54
+ return false;
55
+ }
56
+ const phrases = collectWhyBullets(facts ?? deriveScanNarrative(result), result, CHECK_RUN_MAX_REPO_DRIVER_PHRASES);
80
57
  return phrases.some((p) => p.trim().length >= 12);
81
58
  }
82
59
  function hasActionableBullets(result) {
@@ -92,7 +69,7 @@ function insightToBullet(insight) {
92
69
  const msg = truncateWithEllipsis(f.message, CHECK_RUN_ACTION_BULLET_MAX_CHARS);
93
70
  const action = f.action.trim();
94
71
  if (action && action.length < 120) {
95
- return `${msg} ${truncateWithEllipsis(action, 100)}`;
72
+ return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
96
73
  }
97
74
  return msg;
98
75
  }
@@ -100,7 +77,7 @@ function recommendationToBullet(rec) {
100
77
  const title = String(rec.title ?? "").trim();
101
78
  const rat = String(rec.rationale ?? "").trim();
102
79
  const pkgs = Array.isArray(rec.packages) && rec.packages.length > 0
103
- ? ` (${rec.packages.slice(0, 3).join(", ")}${rec.packages.length > 3 ? ", " : ""})`
80
+ ? ` (${rec.packages.slice(0, 3).join(", ")}${rec.packages.length > 3 ? ", ..." : ""})`
104
81
  : "";
105
82
  if (rat && (!title || rat.length > title.length)) {
106
83
  return truncateWithEllipsis(rat, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkgs;
@@ -113,6 +90,31 @@ function findingToBullet(f) {
113
90
  const pkg = f.packageName ? ` (\`${f.packageName}\`)` : "";
114
91
  return (truncateWithEllipsis(title || f.description, CHECK_RUN_ACTION_BULLET_MAX_CHARS) + pkg);
115
92
  }
93
+ function guidanceToActionBullet(g) {
94
+ const msg = truncateWithEllipsis(g.message.trim(), CHECK_RUN_ACTION_BULLET_MAX_CHARS);
95
+ const action = g.remediation?.trim();
96
+ if (action && action.length < 120) {
97
+ return normalizeGeneratedText(`${msg} - ${truncateWithEllipsis(action, 100)}`);
98
+ }
99
+ return normalizeGeneratedText(msg);
100
+ }
101
+ function buildActionBulletsFromFacts(facts, result, max) {
102
+ const prIntel = facts.availability.mode === "pr_intelligence" ||
103
+ facts.availability.tiersPresent.tier1;
104
+ if (!prIntel) {
105
+ return buildActionBullets(result, max);
106
+ }
107
+ const out = [];
108
+ for (const g of selectReviewerGuidance(facts, { scope: "changed", max })) {
109
+ out.push(guidanceToActionBullet(g));
110
+ if (out.length >= max)
111
+ return out.slice(0, max);
112
+ }
113
+ if (out.length >= max)
114
+ return out.slice(0, max);
115
+ const legacy = buildActionBullets(result, max - out.length);
116
+ return [...out, ...legacy].slice(0, max);
117
+ }
116
118
  function buildActionBullets(result, max) {
117
119
  const out = [];
118
120
  const sortedI = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
@@ -156,12 +158,13 @@ function layerScoreRows(result) {
156
158
  }
157
159
  return rows;
158
160
  }
159
- export function deriveCheckRunPolicy(result, ctx) {
161
+ export function deriveCheckRunPolicy(result, ctx, facts) {
162
+ const resolvedFacts = facts ?? deriveScanNarrative(result);
160
163
  const baseline = ctx.baseline;
161
- const actionBullets = buildActionBullets(result, CHECK_RUN_MAX_ACTION_BULLETS);
164
+ const actionBullets = buildActionBulletsFromFacts(resolvedFacts, result, CHECK_RUN_MAX_ACTION_BULLETS);
162
165
  const layerRows = layerScoreRows(result);
163
166
  const showRepoGraphContext = !baseline &&
164
- hasStrongRepoGraphDrivers(result) &&
167
+ hasStrongRepoGraphDrivers(result, resolvedFacts) &&
165
168
  typeof result.totalScore === "number" &&
166
169
  Number.isFinite(result.totalScore);
167
170
  const showLayerScores = !baseline && layerRows.length > 0;
@@ -189,14 +192,15 @@ function buildLeadSection(result, policy) {
189
192
  : null;
190
193
  return { kind: "lead", posture, riskIndexLine };
191
194
  }
192
- export function buildPrCheckRunSections(policy, result, ctx) {
195
+ export function buildPrCheckRunSections(policy, result, ctx, facts) {
196
+ const resolvedFacts = facts ?? deriveScanNarrative(result);
193
197
  const sections = {};
194
198
  sections.lead = buildLeadSection(result, policy);
195
- const why = collectWhyBullets(result, policy.maxWhyBullets);
199
+ const why = collectWhyBullets(resolvedFacts, result, policy.maxWhyBullets);
196
200
  if (why.length > 0) {
197
201
  sections.why = { kind: "why", bullets: why };
198
202
  }
199
- const actions = buildActionBullets(result, policy.maxActionBullets);
203
+ const actions = buildActionBulletsFromFacts(resolvedFacts, result, policy.maxActionBullets);
200
204
  if (actions.length > 0) {
201
205
  sections.actions = { kind: "actions", bullets: actions };
202
206
  }
@@ -212,7 +216,7 @@ export function buildPrCheckRunSections(policy, result, ctx) {
212
216
  sections.repoContext = {
213
217
  kind: "repoContext",
214
218
  score,
215
- driverPhrases: collectWhyBullets(result, policy.maxRepoDriverPhrases),
219
+ driverPhrases: collectWhyBullets(resolvedFacts, result, policy.maxRepoDriverPhrases),
216
220
  };
217
221
  }
218
222
  if (policy.showLayerScores) {
@@ -270,8 +274,8 @@ export function renderCheckRunMarkdown(sections) {
270
274
  break;
271
275
  }
272
276
  case "repoContext": {
273
- const drivers = section.driverPhrases.join(" · ");
274
- parts.push(`${scanSurfaceCopy.checkRun.repoContextLabel} ${section.score}/100 ${drivers}`);
277
+ const drivers = section.driverPhrases.join(" | ");
278
+ parts.push(normalizeGeneratedText(`${scanSurfaceCopy.checkRun.repoContextLabel} ${section.score}/100 - ${drivers}`));
275
279
  parts.push("");
276
280
  break;
277
281
  }
@@ -280,7 +284,7 @@ export function renderCheckRunMarkdown(sections) {
280
284
  parts.push(`<summary>${scanSurfaceCopy.checkRun.layerScoresDetailsSummary}</summary>`);
281
285
  parts.push("");
282
286
  for (const row of section.rows) {
283
- parts.push(`- **${row.label}** ${row.score}/100 ${row.driver}`);
287
+ parts.push(normalizeGeneratedText(`- **${row.label}** ${row.score}/100 - ${row.driver}`));
284
288
  }
285
289
  parts.push("");
286
290
  parts.push("</details>");
@@ -297,15 +301,16 @@ export function renderCheckRunMarkdown(sections) {
297
301
  }
298
302
  }
299
303
  }
300
- let md = parts.join("\n").trimEnd();
304
+ let md = normalizeGeneratedText(parts.join("\n").trimEnd());
301
305
  if (md.length > CHECK_RUN_SOFT_MAX_CHARS) {
302
- md = md.slice(0, CHECK_RUN_SOFT_MAX_CHARS - 1) + "";
306
+ md = md.slice(0, CHECK_RUN_SOFT_MAX_CHARS - 1) + "...";
303
307
  }
304
308
  return md;
305
309
  }
306
310
  export function buildPrCheckRunSummaryMarkdown(ctx) {
307
- const policy = deriveCheckRunPolicy(ctx.result, { baseline: ctx.baseline });
308
- const sections = buildPrCheckRunSections(policy, ctx.result, ctx);
311
+ const facts = deriveScanNarrative(ctx.result);
312
+ const policy = deriveCheckRunPolicy(ctx.result, { baseline: ctx.baseline }, facts);
313
+ const sections = buildPrCheckRunSections(policy, ctx.result, ctx, facts);
309
314
  return renderCheckRunMarkdown(sections);
310
315
  }
311
316
  /** Whether baseline scan has no actionable PR bullets (for tests / callers). */
@@ -38,7 +38,7 @@ describe("buildPrCheckRunTitle", () => {
38
38
  expect(buildPrCheckRunTitle()).not.toContain("—");
39
39
  });
40
40
  it("appends baseline suffix when requested", () => {
41
- expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change baseline scan only");
41
+ expect(buildPrCheckRunTitle({ baselineOnly: true })).toBe("PR dependency change - baseline scan only");
42
42
  });
43
43
  });
44
44
  describe("formatScanDashboardUrl", () => {
@@ -271,7 +271,7 @@ describe("buildPrCheckRunSummaryMarkdown", () => {
271
271
  baseline: false,
272
272
  });
273
273
  if (md.includes("Layer scores")) {
274
- expect(md).toMatch(/Maintainability.*\/100 —/);
274
+ expect(md).toMatch(/Maintainability.*\/100 -/);
275
275
  expect(md).not.toMatch(/\| Security \| 0 \|/);
276
276
  }
277
277
  });
@@ -0,0 +1,9 @@
1
+ import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
2
+ import type { PRDecision, PRInsight, ScanResult } from "./types.js";
3
+ /**
4
+ * PR comment markdown from narrative facts (compressed dashboard story).
5
+ */
6
+ export declare function presentGitHubPrComment(facts: ScanNarrativeFacts, result: ScanResult): string;
7
+ /** Backward-compatible entry: derives facts then presents. */
8
+ export declare function renderInsightsAsMarkdown(insights: PRInsight[], decision: PRDecision): string;
9
+ //# sourceMappingURL=presentGitHubPrComment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentGitHubPrComment.d.ts","sourceRoot":"","sources":["../src/presentGitHubPrComment.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoBpE;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,UAAU,GACjB,MAAM,CA6CR;AAED,8DAA8D;AAC9D,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,EAAE,EACrB,QAAQ,EAAE,UAAU,GACnB,MAAM,CAgBR"}
@@ -0,0 +1,80 @@
1
+ import { deriveScanNarrative } from "./deriveScanNarrative.js";
2
+ import { composeVerificationPrompt, formatChangedPackagesShort, formatUsageSummaryLine, selectReviewerGuidance, } from "./narrativePresentation.js";
3
+ import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
4
+ import { MERGE_POSTURE_LABEL, mergePostureFromDecision, } from "./riskVocabulary.js";
5
+ import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
6
+ function renderGuidanceBlock(message, where, action) {
7
+ return [
8
+ normalizeGeneratedText(message),
9
+ "",
10
+ "**Where it shows up**",
11
+ "",
12
+ normalizeGeneratedText(where),
13
+ "",
14
+ "**What to do**",
15
+ "",
16
+ normalizeGeneratedText(action),
17
+ ].join("\n");
18
+ }
19
+ /**
20
+ * PR comment markdown from narrative facts (compressed dashboard story).
21
+ */
22
+ export function presentGitHubPrComment(facts, result) {
23
+ const posture = facts.mergePosture ??
24
+ mergePostureFromDecision(result.decision?.recommendation);
25
+ const title = posture
26
+ ? `**${MERGE_POSTURE_LABEL[posture]}**`
27
+ : `**${scanSurfaceCopy.checkRun.mergePostureUnavailable}**`;
28
+ const introLines = [];
29
+ const changed = formatChangedPackagesShort(facts, 3);
30
+ if (changed)
31
+ introLines.push(`Changed: ${changed}`);
32
+ const usage = formatUsageSummaryLine(facts, 1);
33
+ if (usage)
34
+ introLines.push(usage);
35
+ const verify = composeVerificationPrompt(facts);
36
+ if (verify)
37
+ introLines.push(`Verify: ${verify}`);
38
+ const guidance = selectReviewerGuidance(facts, { scope: "changed", max: 3 });
39
+ if (guidance.length === 0) {
40
+ const fallback = selectReviewerGuidance(facts, { max: 3 });
41
+ guidance.push(...fallback.slice(0, 3 - guidance.length));
42
+ }
43
+ const blocks = guidance.map((g) => {
44
+ const where = g.context?.trim() ||
45
+ facts.packageUsage
46
+ .flatMap((u) => u.paths.slice(0, 1))
47
+ .filter(Boolean)[0] ||
48
+ "See scan detail for affected paths.";
49
+ const action = g.remediation?.trim() ||
50
+ composeVerificationPrompt(facts) ||
51
+ "Review before merge.";
52
+ return renderGuidanceBlock(g.message, where, action);
53
+ });
54
+ const parts = [title];
55
+ if (introLines.length > 0) {
56
+ parts.push("", introLines.join("\n"));
57
+ }
58
+ if (blocks.length > 0) {
59
+ parts.push("", ...blocks);
60
+ }
61
+ return parts.join("\n\n---\n\n").trimEnd();
62
+ }
63
+ /** Backward-compatible entry: derives facts then presents. */
64
+ export function renderInsightsAsMarkdown(insights, decision) {
65
+ const result = {
66
+ totalScore: 0,
67
+ layerScores: {
68
+ security: 0,
69
+ maintainability: 0,
70
+ ecosystem: 0,
71
+ upgradeImpact: 0,
72
+ },
73
+ findings: [],
74
+ generatedAt: new Date().toISOString(),
75
+ insights,
76
+ decision,
77
+ };
78
+ const facts = deriveScanNarrative(result);
79
+ return presentGitHubPrComment(facts, result);
80
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"presentScanCardSummary.d.ts","sourceRoot":"","sources":["../src/presentScanCardSummary.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,6BAA6B,GAAG;IAC1C,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;CACnC,CAAC;AA2HF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,6BAAkC,GAC1C,eAAe,CAkGjB;AAED,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAElD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,GAAG,IAAI,CAE5B"}
1
+ {"version":3,"file":"presentScanCardSummary.d.ts","sourceRoot":"","sources":["../src/presentScanCardSummary.ts"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,gCAAgC,CAAC;AAkBxC,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,6BAA6B,GAAG;IAC1C,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACpC,QAAQ,CAAC,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;CACnC,CAAC;AA2HF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,6BAAkC,GAC1C,eAAe,CA8HjB;AAED,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAElD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,GAAG,IAAI,CAE5B"}
@@ -1,35 +1,11 @@
1
- import { phraseForFamily, } from "./cardObservationCatalog.js";
2
- import { aggregateFindingCounts, deriveRiskIndexBand, } from "./scanCardSummary.js";
1
+ import { isCatalogPhrase, phraseForFamily, } from "./cardObservationCatalog.js";
2
+ import { aggregateFindingCounts, deriveRiskIndexBand, SCAN_CARD_SCANNING_SUMMARY, } from "./scanCardSummary.js";
3
3
  import { deriveCardExposureDisplay, } from "./formatCardExposureDisplay.js";
4
4
  import { formatCardAreaLabels } from "./formatCardAreaLabels.js";
5
+ import { composeContextLineFromFacts, composeVerificationPrompt, formatBlastRadiusDetailLine, formatChangedPackagesShort, formatFrameworksSummary, formatUsageSummaryLine, labelBlastRadiusLevel, labelReachabilityKind, labelRuntimeSurface, } from "./narrativePresentation.js";
6
+ import { normalizeGeneratedText, normalizeGeneratedTextNullable, } from "./normalizeGeneratedText.js";
5
7
  import { MERGE_POSTURE_LABEL } from "./riskVocabulary.js";
6
8
  import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
7
- function formatChangedPackagesDisplay(facts) {
8
- const { primary, others } = facts.changedPackages;
9
- if (!primary)
10
- return null;
11
- if (others.length === 0)
12
- return primary;
13
- return `${primary} +${others.length}`;
14
- }
15
- function labelRuntimeSurface(facts) {
16
- const rs = facts.runtimeSurface;
17
- if (!rs)
18
- return null;
19
- return scanSurfaceCopy.narrativeCard.runtimeSurface[rs.kind];
20
- }
21
- function labelReachability(facts) {
22
- const r = facts.reachability;
23
- if (!r)
24
- return null;
25
- return scanSurfaceCopy.narrativeCard.reachability[r.kind];
26
- }
27
- function labelBlastRadius(facts) {
28
- const br = facts.blastRadius;
29
- if (!br)
30
- return null;
31
- return scanSurfaceCopy.narrativeCard.blastRadius[br.level];
32
- }
33
9
  function formatAffectedAreaLabels(facts) {
34
10
  const ordered = [];
35
11
  for (const area of facts.affectedAreas) {
@@ -62,7 +38,7 @@ function selectSecondaryLines(facts, exclude) {
62
38
  }
63
39
  return lines;
64
40
  }
65
- function repositoryContextPhrases(facts) {
41
+ function repositoryContextPhrases(facts, max) {
66
42
  const phrases = [];
67
43
  const seen = new Set();
68
44
  for (const ctx of facts.repositoryContext) {
@@ -71,32 +47,13 @@ function repositoryContextPhrases(facts) {
71
47
  continue;
72
48
  seen.add(phrase);
73
49
  phrases.push(phrase);
74
- if (phrases.length >= 3)
50
+ if (phrases.length >= max)
75
51
  break;
76
52
  }
77
53
  return phrases;
78
54
  }
79
- function composeContextLine(facts) {
80
- const parts = [];
81
- const runtime = labelRuntimeSurface(facts);
82
- const reach = labelReachability(facts);
83
- const blast = labelBlastRadius(facts);
84
- if (runtime)
85
- parts.push(runtime);
86
- if (reach)
87
- parts.push(reach);
88
- if (blast)
89
- parts.push(blast);
90
- const areas = formatAffectedAreaLabels(facts);
91
- if (areas.length > 0) {
92
- parts.push(areas.join(scanSurfaceCopy.narrativeCard.areasSeparator));
93
- }
94
- if (parts.length === 0)
95
- return null;
96
- return parts.join(scanSurfaceCopy.narrativeCard.contextSeparator);
97
- }
98
- function buildLegacyObservations(facts, primaryInsight) {
99
- const repoPhrases = repositoryContextPhrases(facts);
55
+ function buildLegacyObservations(facts, primaryInsight, repoPhraseCap) {
56
+ const repoPhrases = repositoryContextPhrases(facts, repoPhraseCap);
100
57
  const secondary = selectSecondaryLines(facts, primaryInsight);
101
58
  if (primaryInsight) {
102
59
  return {
@@ -110,10 +67,34 @@ function buildLegacyObservations(facts, primaryInsight) {
110
67
  return { operationalObservations: [], supportingLine: null };
111
68
  }
112
69
  return {
113
- operationalObservations: repoPhrases.slice(0, 3),
70
+ operationalObservations: repoPhrases.slice(0, repoPhraseCap),
114
71
  supportingLine: repoPhrases.length === 1 ? (secondary[0] ?? null) : null,
115
72
  };
116
73
  }
74
+ function normalizeCardSummary(summary) {
75
+ return {
76
+ ...summary,
77
+ headline: normalizeGeneratedText(summary.headline),
78
+ summaryLine: normalizeGeneratedTextNullable(summary.summaryLine),
79
+ supportingLine: normalizeGeneratedTextNullable(summary.supportingLine),
80
+ changedPackagesDisplay: normalizeGeneratedTextNullable(summary.changedPackagesDisplay),
81
+ runtimeSurfaceLabel: normalizeGeneratedTextNullable(summary.runtimeSurfaceLabel),
82
+ reachabilityLabel: normalizeGeneratedTextNullable(summary.reachabilityLabel),
83
+ blastRadiusLabel: normalizeGeneratedTextNullable(summary.blastRadiusLabel),
84
+ primaryInsight: normalizeGeneratedTextNullable(summary.primaryInsight),
85
+ structuralOnlyDisclaimer: normalizeGeneratedTextNullable(summary.structuralOnlyDisclaimer),
86
+ usageSummary: normalizeGeneratedTextNullable(summary.usageSummary),
87
+ verificationLine: normalizeGeneratedTextNullable(summary.verificationLine),
88
+ blastRadiusDetail: normalizeGeneratedTextNullable(summary.blastRadiusDetail),
89
+ frameworksSummary: normalizeGeneratedTextNullable(summary.frameworksSummary),
90
+ affectedAreas: summary.affectedAreas.map((a) => normalizeGeneratedText(a)),
91
+ topAffectedAreas: summary.topAffectedAreas.map((a) => normalizeGeneratedText(a)),
92
+ operationalObservations: summary.operationalObservations.map((p) => {
93
+ const normalized = normalizeGeneratedText(p);
94
+ return isCatalogPhrase(normalized) ? normalized : p;
95
+ }),
96
+ };
97
+ }
117
98
  /**
118
99
  * Dashboard card presentation DTO from consumer-agnostic facts.
119
100
  */
@@ -129,23 +110,27 @@ export function presentScanCardSummary(facts, options = {}) {
129
110
  affectedAreas: [],
130
111
  primaryInsight: null,
131
112
  structuralOnlyDisclaimer: null,
113
+ usageSummary: null,
114
+ verificationLine: null,
115
+ blastRadiusDetail: null,
116
+ frameworksSummary: null,
132
117
  };
133
118
  if (pipelineStatus === "queued" || pipelineStatus === "running") {
134
- return {
119
+ return normalizeCardSummary({
135
120
  mergePosture: null,
136
121
  riskIndex: null,
137
122
  riskIndexBand: null,
138
123
  headline: scanSurfaceCopy.pipeline.scanRunning,
139
- summaryLine: "Waiting for results…",
124
+ summaryLine: SCAN_CARD_SCANNING_SUMMARY,
140
125
  findingCounts: null,
141
126
  topAffectedAreas: [],
142
127
  operationalObservations: [],
143
128
  supportingLine: null,
144
129
  ...emptyPresentationFields,
145
- };
130
+ });
146
131
  }
147
132
  if (pipelineStatus === "failed") {
148
- return {
133
+ return normalizeCardSummary({
149
134
  mergePosture: null,
150
135
  riskIndex: null,
151
136
  riskIndexBand: null,
@@ -156,7 +141,7 @@ export function presentScanCardSummary(facts, options = {}) {
156
141
  operationalObservations: [],
157
142
  supportingLine: null,
158
143
  ...emptyPresentationFields,
159
- };
144
+ });
160
145
  }
161
146
  const posture = facts.mergePosture;
162
147
  const riskIndex = facts.riskIndex;
@@ -167,12 +152,17 @@ export function presentScanCardSummary(facts, options = {}) {
167
152
  const findingCounts = options.findings
168
153
  ? aggregateFindingCounts(options.findings)
169
154
  : null;
170
- const changedPackagesDisplay = formatChangedPackagesDisplay(facts);
155
+ const prIntelligence = facts.availability.mode === "pr_intelligence" ||
156
+ facts.availability.tiersPresent.tier1;
157
+ const changedPackagesDisplay = formatChangedPackagesShort(facts, 2);
171
158
  const runtimeSurfaceLabel = labelRuntimeSurface(facts);
172
- const reachabilityLabel = labelReachability(facts);
173
- const blastRadiusLabel = labelBlastRadius(facts);
159
+ const reachabilityLabel = labelReachabilityKind(facts);
160
+ const blastRadiusLabel = labelBlastRadiusLevel(facts);
174
161
  const affectedAreas = formatAffectedAreaLabels(facts);
175
- const contextLine = composeContextLine(facts);
162
+ const contextLine = composeContextLineFromFacts(facts, {
163
+ includePathSample: prIntelligence,
164
+ maxAreas: 2,
165
+ });
176
166
  const structuralOnlyDisclaimer = facts.availability.mode === "graph_fallback" &&
177
167
  !facts.availability.tiersPresent.tier1
178
168
  ? scanSurfaceCopy.narrativeCard.structuralOnlyDisclaimer
@@ -180,8 +170,19 @@ export function presentScanCardSummary(facts, options = {}) {
180
170
  const insightMessage = selectPrimaryInsight(facts);
181
171
  const primaryInsight = insightMessage ??
182
172
  (facts.availability.mode !== "graph_fallback" ? contextLine : null);
183
- const { operationalObservations, supportingLine } = buildLegacyObservations(facts, primaryInsight);
184
- return {
173
+ const usageSummary = prIntelligence ? formatUsageSummaryLine(facts, 1) : null;
174
+ const verificationLine = prIntelligence
175
+ ? composeVerificationPrompt(facts)
176
+ : null;
177
+ const blastRadiusDetail = prIntelligence
178
+ ? formatBlastRadiusDetailLine(facts, 1)
179
+ : null;
180
+ const frameworksSummary = prIntelligence
181
+ ? formatFrameworksSummary(facts, 2)
182
+ : null;
183
+ const repoPhraseCap = prIntelligence ? 0 : 3;
184
+ const { operationalObservations, supportingLine } = buildLegacyObservations(facts, primaryInsight, repoPhraseCap);
185
+ return normalizeCardSummary({
185
186
  mergePosture: posture,
186
187
  riskIndex,
187
188
  riskIndexBand,
@@ -200,7 +201,11 @@ export function presentScanCardSummary(facts, options = {}) {
200
201
  affectedAreas,
201
202
  primaryInsight,
202
203
  structuralOnlyDisclaimer,
203
- };
204
+ usageSummary,
205
+ verificationLine,
206
+ blastRadiusDetail,
207
+ frameworksSummary,
208
+ });
204
209
  }
205
210
  export function exposureFromFacts(facts) {
206
211
  return deriveCardExposureDisplay(facts.riskIndex) ?? null;
@@ -34,7 +34,7 @@ export function mergePostureFromDecision(decision) {
34
34
  return null;
35
35
  }
36
36
  /** Returns the display label for a decision, or a fallback string. */
37
- export function mergePostureLabel(decision, fallback = "") {
37
+ export function mergePostureLabel(decision, fallback = "-") {
38
38
  const posture = mergePostureFromDecision(decision);
39
39
  return posture ? MERGE_POSTURE_LABEL[posture] : fallback;
40
40
  }
@@ -34,8 +34,16 @@ export type ScanCardSummary = {
34
34
  affectedAreas: string[];
35
35
  primaryInsight: string | null;
36
36
  structuralOnlyDisclaimer: string | null;
37
+ /** One-line usage hint from packageUsage paths or areas. */
38
+ usageSummary: string | null;
39
+ /** Single verification prompt from changed-scope guidance or usage. */
40
+ verificationLine: string | null;
41
+ /** Optional blast factor subline when factors exist. */
42
+ blastRadiusDetail: string | null;
43
+ /** Short framework list (max 2 on card). */
44
+ frameworksSummary: string | null;
37
45
  };
38
- export declare const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results\u2026";
46
+ export declare const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results...";
39
47
  export type PipelineCompletionEvidence = {
40
48
  scannedAt?: string | null;
41
49
  decision?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"scanCardSummary.d.ts","sourceRoot":"","sources":["../src/scanCardSummary.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scanCardSummary.d.ts","sourceRoot":"","sources":["../src/scanCardSummary.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { presentScanCardSummary } from "./presentScanCardSummary.js";
3
3
  import { mergePostureFromDecision, } from "./riskVocabulary.js";
4
4
  import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
5
5
  const STALE_SUBLINE = "Based on earlier commit";
6
- export const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results";
6
+ export const SCAN_CARD_SCANNING_SUMMARY = "Waiting for results...";
7
7
  function normalizePipelineStatus(status) {
8
8
  const st = String(status ?? "")
9
9
  .trim()
@@ -11,6 +11,10 @@ const pipelinePresentationDefaults = {
11
11
  affectedAreas: [],
12
12
  primaryInsight: null,
13
13
  structuralOnlyDisclaimer: null,
14
+ usageSummary: null,
15
+ verificationLine: null,
16
+ blastRadiusDetail: null,
17
+ frameworksSummary: null,
14
18
  };
15
19
  const baseResult = {
16
20
  totalScore: 10,
@@ -64,7 +68,7 @@ describe("deriveScanCardSummary", () => {
64
68
  it("returns scanning copy for queued/running", () => {
65
69
  const queued = deriveScanCardSummary(null, "queued");
66
70
  expect(queued.headline).toBe("Scan in progress");
67
- expect(queued.summaryLine).toBe("Waiting for results");
71
+ expect(queued.summaryLine).toBe("Waiting for results...");
68
72
  expect(queued.mergePosture).toBeNull();
69
73
  const running = deriveScanCardSummary(null, "running");
70
74
  expect(running.headline).toBe("Scan in progress");
@@ -184,7 +188,7 @@ describe("isPipelineCardSummary", () => {
184
188
  riskIndex: null,
185
189
  riskIndexBand: null,
186
190
  headline: "Scan in progress",
187
- summaryLine: "Waiting for results",
191
+ summaryLine: "Waiting for results...",
188
192
  findingCounts: null,
189
193
  topAffectedAreas: [],
190
194
  operationalObservations: [],
@@ -198,7 +202,7 @@ describe("isPipelineCardSummary", () => {
198
202
  riskIndex: 72,
199
203
  riskIndexBand: "high",
200
204
  headline: "Risky",
201
- summaryLine: "Waiting for results",
205
+ summaryLine: "Waiting for results...",
202
206
  findingCounts: null,
203
207
  topAffectedAreas: [],
204
208
  operationalObservations: [],
@@ -209,7 +213,7 @@ describe("isPipelineCardSummary", () => {
209
213
  });
210
214
  describe("isPipelinePlaceholderCopy", () => {
211
215
  it("identifies waiting copy", () => {
212
- expect(isPipelinePlaceholderCopy("Waiting for results")).toBe(true);
216
+ expect(isPipelinePlaceholderCopy("Waiting for results...")).toBe(true);
213
217
  expect(isPipelinePlaceholderCopy("Auth change")).toBe(false);
214
218
  });
215
219
  });
@@ -223,9 +227,9 @@ describe("deriveScanCardSummaryFromDenormalized", () => {
223
227
  expect(summary.operationalObservations).toEqual([]);
224
228
  });
225
229
  it("does not inject pipeline placeholder summaryText", () => {
226
- const summary = deriveScanCardSummaryFromDenormalized("risky", 72, "Waiting for results", "done");
230
+ const summary = deriveScanCardSummaryFromDenormalized("risky", 72, "Waiting for results...", "done");
227
231
  expect(summary.mergePosture).toBe("risky");
228
- expect(summary.summaryLine).not.toBe("Waiting for results");
232
+ expect(summary.summaryLine).not.toBe("Waiting for results...");
229
233
  });
230
234
  it("does not inject raw generic summaryText on quiet safe cards", () => {
231
235
  const summary = deriveScanCardSummaryFromDenormalized("safe", 12, "No high-confidence merge risks from this PR dependency change", "done");
@@ -239,11 +243,11 @@ describe("resolvePrScanCardSummary", () => {
239
243
  pipelineStatus: "running",
240
244
  decision: "risky",
241
245
  totalScore: 80,
242
- summaryText: "Waiting for results",
246
+ summaryText: "Waiting for results...",
243
247
  result: null,
244
248
  });
245
249
  expect(summary.mergePosture).toBe("risky");
246
250
  expect(summary.headline).toBe("Risky");
247
- expect(summary.summaryLine).not.toBe("Waiting for results");
251
+ expect(summary.summaryLine).not.toBe("Waiting for results...");
248
252
  });
249
253
  });