@mergesignal/shared 0.2.6 → 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 (72) 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 +10 -0
  7. package/dist/deriveScanNarrative.d.ts.map +1 -0
  8. package/dist/deriveScanNarrative.js +370 -0
  9. package/dist/deriveScanNarrative.test.d.ts +2 -0
  10. package/dist/deriveScanNarrative.test.d.ts.map +1 -0
  11. package/dist/deriveScanNarrative.test.js +164 -0
  12. package/dist/extractRepositoryContextFacts.d.ts +16 -0
  13. package/dist/extractRepositoryContextFacts.d.ts.map +1 -0
  14. package/dist/extractRepositoryContextFacts.js +71 -0
  15. package/dist/fixtures/repoIntelligenceFixtures.d.ts +10 -0
  16. package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -0
  17. package/dist/fixtures/repoIntelligenceFixtures.js +93 -0
  18. package/dist/formatInsight.d.ts +2 -2
  19. package/dist/formatInsight.d.ts.map +1 -1
  20. package/dist/formatInsight.js +2 -7
  21. package/dist/formatInsight.test.js +1 -1
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -0
  25. package/dist/narrativeParity.test.d.ts +2 -0
  26. package/dist/narrativeParity.test.d.ts.map +1 -0
  27. package/dist/narrativeParity.test.js +64 -0
  28. package/dist/narrativePresentation.d.ts +50 -0
  29. package/dist/narrativePresentation.d.ts.map +1 -0
  30. package/dist/narrativePresentation.js +180 -0
  31. package/dist/narrativePresentation.test.d.ts +2 -0
  32. package/dist/narrativePresentation.test.d.ts.map +1 -0
  33. package/dist/narrativePresentation.test.js +53 -0
  34. package/dist/normalizeGeneratedText.d.ts +8 -0
  35. package/dist/normalizeGeneratedText.d.ts.map +1 -0
  36. package/dist/normalizeGeneratedText.js +31 -0
  37. package/dist/normalizeGeneratedText.test.d.ts +2 -0
  38. package/dist/normalizeGeneratedText.test.d.ts.map +1 -0
  39. package/dist/normalizeGeneratedText.test.js +18 -0
  40. package/dist/prCheckRunPresentation.d.ts +4 -3
  41. package/dist/prCheckRunPresentation.d.ts.map +1 -1
  42. package/dist/prCheckRunPresentation.js +56 -51
  43. package/dist/prCheckRunPresentation.test.js +2 -2
  44. package/dist/presentGitHubPrComment.d.ts +9 -0
  45. package/dist/presentGitHubPrComment.d.ts.map +1 -0
  46. package/dist/presentGitHubPrComment.js +80 -0
  47. package/dist/presentScanCardSummary.d.ts +16 -0
  48. package/dist/presentScanCardSummary.d.ts.map +1 -0
  49. package/dist/presentScanCardSummary.js +212 -0
  50. package/dist/productMessaging.d.ts +1 -1
  51. package/dist/productMessaging.js +1 -1
  52. package/dist/repoIntelligenceSchema.d.ts +123 -0
  53. package/dist/repoIntelligenceSchema.d.ts.map +1 -0
  54. package/dist/repoIntelligenceSchema.js +174 -0
  55. package/dist/riskVocabulary.js +1 -1
  56. package/dist/scanCardSummary.d.ts +21 -3
  57. package/dist/scanCardSummary.d.ts.map +1 -1
  58. package/dist/scanCardSummary.js +17 -58
  59. package/dist/scanCardSummary.test.js +25 -8
  60. package/dist/scanDetailViewModel.d.ts +22 -0
  61. package/dist/scanDetailViewModel.d.ts.map +1 -1
  62. package/dist/scanDetailViewModel.js +247 -31
  63. package/dist/scanDetailViewModel.test.js +29 -3
  64. package/dist/scanNarrativeFacts.d.ts +92 -0
  65. package/dist/scanNarrativeFacts.d.ts.map +1 -0
  66. package/dist/scanNarrativeFacts.js +24 -0
  67. package/dist/scanSurfaceCopy.d.ts +49 -23
  68. package/dist/scanSurfaceCopy.d.ts.map +1 -1
  69. package/dist/scanSurfaceCopy.js +49 -23
  70. package/dist/types.d.ts +1 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"actionsStepSummary.d.ts","sourceRoot":"","sources":["../src/actionsStepSummary.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE9D,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,KAAK,CAAC;AACpD,gDAAgD;AAChD,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,6EAA6E;AAC7E,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,4DAA4D;AAC5D,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAyBnD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAK3E;AAOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA6C7D;AA4ED,8GAA8G;AAC9G,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAiBf;AASD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAa3E;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,cAAc,EAAE,GACrB,cAAc,EAAE,CAelB;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxE;AAaD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxE;AAgiBD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE;IACpD,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,GAAG,MAAM,CAoJT"}
1
+ {"version":3,"file":"actionsStepSummary.d.ts","sourceRoot":"","sources":["../src/actionsStepSummary.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE9D,8EAA8E;AAC9E,eAAO,MAAM,iCAAiC,KAAK,CAAC;AACpD,gDAAgD;AAChD,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,6EAA6E;AAC7E,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,4DAA4D;AAC5D,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAyBnD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAK3E;AAOD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA6C7D;AA4ED,8GAA8G;AAC9G,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAiBf;AASD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAa3E;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,cAAc,EAAE,GACrB,cAAc,EAAE,CAelB;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxE;AAaD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxE;AAygBD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE;IACpD,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,GAAG,MAAM,CAoJT"}
@@ -9,7 +9,7 @@
9
9
  * - `## Recommended actions` + time heuristics → default “what to do” + `<details> More actions`
10
10
  * - `<details> Risk breakdown` + layer table + graph one-liner → score + graph `<details>` blocks
11
11
  */
12
- import { deriveScanSummaryText } from "./deriveScanSummaryText.js";
12
+ import { collectWhyBulletsFromResult } from "./collectNarrativeWhyBullets.js";
13
13
  import { formatInsight } from "./formatInsight.js";
14
14
  import { mergePostureLabel } from "./riskVocabulary.js";
15
15
  import { scanSurfaceCopy } from "./scanSurfaceCopy.js";
@@ -516,34 +516,7 @@ function buildTrustedTitleLines(result, flat) {
516
516
  return titleLines;
517
517
  }
518
518
  function collectWhyBullets(result, max) {
519
- const out = [];
520
- const reasoning = result.decision?.reasoning;
521
- if (Array.isArray(reasoning)) {
522
- for (const r of reasoning) {
523
- const s = humanizeEngineSurfaceText(String(r).trim());
524
- if (s && !out.includes(s))
525
- out.push(s);
526
- if (out.length >= max)
527
- return dedupeDriverDisplayPhrases(out).slice(0, max);
528
- }
529
- }
530
- const reasons = result.explain?.reasons;
531
- if (Array.isArray(reasons) && out.length < max) {
532
- for (const r of reasons) {
533
- const title = String(r?.title ?? r?.id ?? "").trim();
534
- const readable = humanizeEngineSurfaceText(title);
535
- if (readable && !out.includes(readable))
536
- out.push(readable);
537
- if (out.length >= max)
538
- break;
539
- }
540
- }
541
- if (out.length === 0) {
542
- const one = deriveScanSummaryText(result);
543
- if (one)
544
- out.push(one);
545
- }
546
- return dedupeDriverDisplayPhrases(out).slice(0, max);
519
+ return dedupeDriverDisplayPhrases(collectWhyBulletsFromResult(result, max));
547
520
  }
548
521
  function buildWhatToDoItems(result) {
549
522
  const sortedI = sortPRInsightsForDisplay(Array.isArray(result.insights) ? result.insights : []);
@@ -0,0 +1,12 @@
1
+ import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
2
+ import type { ScanResult } from "./types.js";
3
+ /**
4
+ * Why bullets from narrative facts (tier-1/tier-2). Preferred for GitHub + Actions.
5
+ */
6
+ export declare function collectWhyBulletsFromFacts(facts: ScanNarrativeFacts, max: number): string[];
7
+ /**
8
+ * Legacy why bullets from raw ScanResult (fallback when facts are thin).
9
+ */
10
+ export declare function collectWhyBulletsFromResult(result: ScanResult, max: number): string[];
11
+ export declare function collectWhyBullets(facts: ScanNarrativeFacts, result: ScanResult, max: number): string[];
12
+ //# sourceMappingURL=collectNarrativeWhyBullets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collectNarrativeWhyBullets.d.ts","sourceRoot":"","sources":["../src/collectNarrativeWhyBullets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAS7C;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,MAAM,GACV,MAAM,EAAE,CAsCV;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,EAAE,CAwBV;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,GACV,MAAM,EAAE,CAiBV"}
@@ -0,0 +1,104 @@
1
+ import { deriveScanSummaryText } from "./deriveScanSummaryText.js";
2
+ import { humanizeEngineSurfaceText, truncateWithEllipsis, } from "./actionsStepSummary.js";
3
+ import { composeContextLineFromFacts, formatBlastRadiusDetailLine, formatUsageSummaryLine, labelReachabilityKind, selectReviewerGuidance, } from "./narrativePresentation.js";
4
+ import { normalizeGeneratedText } from "./normalizeGeneratedText.js";
5
+ function pushUnique(out, line, max) {
6
+ const t = normalizeGeneratedText(line.trim());
7
+ if (!t || out.includes(t))
8
+ return;
9
+ out.push(t);
10
+ if (out.length > max)
11
+ out.length = max;
12
+ }
13
+ /**
14
+ * Why bullets from narrative facts (tier-1/tier-2). Preferred for GitHub + Actions.
15
+ */
16
+ export function collectWhyBulletsFromFacts(facts, max) {
17
+ const prIntel = facts.availability.mode === "pr_intelligence" ||
18
+ facts.availability.tiersPresent.tier1;
19
+ if (!prIntel)
20
+ return [];
21
+ const out = [];
22
+ if (prIntel) {
23
+ const context = composeContextLineFromFacts(facts, {
24
+ includePathSample: true,
25
+ maxAreas: 2,
26
+ });
27
+ if (context)
28
+ pushUnique(out, context, max);
29
+ const usage = formatUsageSummaryLine(facts, 1);
30
+ if (usage)
31
+ pushUnique(out, usage, max);
32
+ const blast = formatBlastRadiusDetailLine(facts, 2);
33
+ if (blast)
34
+ pushUnique(out, blast, max);
35
+ for (const area of facts.affectedAreas) {
36
+ if (out.length >= max)
37
+ break;
38
+ pushUnique(out, area.label, max);
39
+ }
40
+ const reach = labelReachabilityKind(facts);
41
+ if (reach)
42
+ pushUnique(out, reach, max);
43
+ }
44
+ if (out.length < max) {
45
+ for (const g of selectReviewerGuidance(facts, { max: max - out.length })) {
46
+ if (out.length >= max)
47
+ break;
48
+ pushUnique(out, truncateWithEllipsis(g.message.trim(), 200), max);
49
+ }
50
+ }
51
+ return out.slice(0, max);
52
+ }
53
+ /**
54
+ * Legacy why bullets from raw ScanResult (fallback when facts are thin).
55
+ */
56
+ export function collectWhyBulletsFromResult(result, max) {
57
+ const out = [];
58
+ const reasoning = result.decision?.reasoning;
59
+ if (Array.isArray(reasoning)) {
60
+ for (const r of reasoning) {
61
+ const s = humanizeEngineSurfaceText(String(r).trim());
62
+ if (s)
63
+ pushUnique(out, s, max);
64
+ if (out.length >= max)
65
+ return out.slice(0, max);
66
+ }
67
+ }
68
+ const reasons = result.explain?.reasons;
69
+ if (Array.isArray(reasons) && out.length < max) {
70
+ for (const r of reasons) {
71
+ const title = String(r?.title ?? r?.id ?? "").trim();
72
+ const readable = humanizeEngineSurfaceText(title);
73
+ if (readable)
74
+ pushUnique(out, readable, max);
75
+ if (out.length >= max)
76
+ break;
77
+ }
78
+ }
79
+ if (out.length === 0) {
80
+ const one = deriveScanSummaryText(result);
81
+ if (one)
82
+ pushUnique(out, one, max);
83
+ }
84
+ return out.slice(0, max);
85
+ }
86
+ export function collectWhyBullets(facts, result, max) {
87
+ const fromFacts = collectWhyBulletsFromFacts(facts, max);
88
+ if (fromFacts.length >= max)
89
+ return fromFacts;
90
+ if (facts.availability.mode === "pr_intelligence" ||
91
+ facts.availability.tiersPresent.tier1) {
92
+ return fromFacts;
93
+ }
94
+ const legacy = collectWhyBulletsFromResult(result, max);
95
+ if (fromFacts.length === 0)
96
+ return legacy;
97
+ const merged = [...fromFacts];
98
+ for (const line of legacy) {
99
+ pushUnique(merged, line, max);
100
+ if (merged.length >= max)
101
+ break;
102
+ }
103
+ return merged.slice(0, max);
104
+ }
@@ -0,0 +1,10 @@
1
+ import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind } from "./repoIntelligenceSchema.js";
2
+ import { type ScanNarrativeFacts } from "./scanNarrativeFacts.js";
3
+ import type { ScanResult } from "./types.js";
4
+ /**
5
+ * Consumer-agnostic scan intelligence — structured facts only (no UI copy).
6
+ */
7
+ export declare function deriveScanNarrative(result: ScanResult | null | undefined): ScanNarrativeFacts;
8
+ /** @internal Test helper — normalize loose engine strings. */
9
+ export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
10
+ //# sourceMappingURL=deriveScanNarrative.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,2BAA2B,EAM5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAYL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAKV,UAAU,EACX,MAAM,YAAY,CAAC;AAkZpB;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACpC,kBAAkB,CAiGpB;AAED,8DAA8D;AAC9D,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,CAAC"}
@@ -0,0 +1,370 @@
1
+ import { extractRepositoryContextFacts } from "./extractRepositoryContextFacts.js";
2
+ import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind, parseRepoIntelligence, readBlastLevel, readReachabilityFromLoose, readSurfaceFromLoose, } from "./repoIntelligenceSchema.js";
3
+ import { EMPTY_SCAN_NARRATIVE_FACTS, NARRATIVE_FACT_MAX_AREAS, NARRATIVE_FACT_MAX_HOTSPOTS, NARRATIVE_FACT_MAX_PATHS, } from "./scanNarrativeFacts.js";
4
+ import { mergePostureFromDecision } from "./riskVocabulary.js";
5
+ import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
6
+ function parseChangedPackages(changed) {
7
+ const all = Array.isArray(changed)
8
+ ? changed.map((p) => p.trim()).filter(Boolean)
9
+ : [];
10
+ return {
11
+ primary: all[0] ?? null,
12
+ others: all.slice(1),
13
+ all,
14
+ };
15
+ }
16
+ function blastLevelFromChangedCount(count) {
17
+ if (count >= 8)
18
+ return "wide";
19
+ if (count >= 3)
20
+ return "moderate";
21
+ return "narrow";
22
+ }
23
+ function uniqueStrings(values) {
24
+ const seen = new Set();
25
+ const out = [];
26
+ for (const v of values) {
27
+ const t = v.trim();
28
+ if (!t || seen.has(t))
29
+ continue;
30
+ seen.add(t);
31
+ out.push(t);
32
+ }
33
+ return out;
34
+ }
35
+ function usageEntryPackageName(entry) {
36
+ const name = (entry.packageName ?? entry.package ?? "").trim();
37
+ return name || null;
38
+ }
39
+ function collectPackageUsageForChanged(ri, changed) {
40
+ const byPackage = new Map();
41
+ const ensure = (packageName) => {
42
+ let row = byPackage.get(packageName);
43
+ if (!row) {
44
+ row = {
45
+ packageName,
46
+ paths: [],
47
+ criticalPaths: [],
48
+ files: [],
49
+ areas: [],
50
+ };
51
+ byPackage.set(packageName, row);
52
+ }
53
+ return row;
54
+ };
55
+ const mergeEntry = (entry, packageNameOverride) => {
56
+ const name = packageNameOverride ?? usageEntryPackageName(entry);
57
+ if (!name || !changed.all.includes(name))
58
+ return;
59
+ const row = ensure(name);
60
+ row.paths = uniqueStrings([...row.paths, ...(entry.paths ?? [])]);
61
+ row.criticalPaths = uniqueStrings([
62
+ ...row.criticalPaths,
63
+ ...(entry.criticalPaths ?? []),
64
+ ]);
65
+ row.files = uniqueStrings([...row.files, ...(entry.files ?? [])]);
66
+ row.areas = uniqueStrings([...row.areas, ...(entry.areas ?? [])]);
67
+ };
68
+ for (const name of changed.all) {
69
+ const pkgIntel = ri.packages?.[name];
70
+ if (pkgIntel?.usage)
71
+ mergeEntry(pkgIntel.usage, name);
72
+ if (pkgIntel?.packageUsage)
73
+ mergeEntry(pkgIntel.packageUsage, name);
74
+ if (pkgIntel?.areas?.length) {
75
+ const row = ensure(name);
76
+ row.areas = uniqueStrings([...row.areas, ...pkgIntel.areas]);
77
+ }
78
+ }
79
+ if (Array.isArray(ri.packageUsage)) {
80
+ for (const entry of ri.packageUsage) {
81
+ mergeEntry(entry);
82
+ }
83
+ }
84
+ return changed.all
85
+ .map((name) => byPackage.get(name))
86
+ .filter((row) => row != null);
87
+ }
88
+ function allUsagePaths(usage) {
89
+ const paths = [];
90
+ for (const row of usage) {
91
+ paths.push(...row.paths, ...row.criticalPaths, ...row.files);
92
+ }
93
+ return uniqueStrings(paths).slice(0, NARRATIVE_FACT_MAX_PATHS);
94
+ }
95
+ function collectFrameworks(ri) {
96
+ return uniqueStrings(ri.frameworks ?? []);
97
+ }
98
+ function collectCodeHotspots(ri, primary) {
99
+ const hotspots = [];
100
+ const pkgIntel = primary && ri.packages?.[primary] ? ri.packages[primary] : undefined;
101
+ const riHotspots = ri.hotspots ?? pkgIntel?.hotspots ?? [];
102
+ for (const h of riHotspots) {
103
+ const name = (h.packageName ?? primary ?? "").trim();
104
+ if (!name)
105
+ continue;
106
+ hotspots.push({
107
+ packageName: name,
108
+ source: h.source === "graph" ? "graph" : "code",
109
+ depth: h.depth,
110
+ paths: uniqueStrings(h.paths ?? []),
111
+ });
112
+ }
113
+ return hotspots;
114
+ }
115
+ function mergeGraphHotspots(codeHotspots, graphHotspots) {
116
+ const byName = new Map();
117
+ for (const h of codeHotspots) {
118
+ byName.set(h.packageName, h);
119
+ }
120
+ for (const g of graphHotspots ?? []) {
121
+ const name = g.packageName?.trim();
122
+ if (!name || byName.has(name))
123
+ continue;
124
+ byName.set(name, {
125
+ packageName: name,
126
+ source: "graph",
127
+ depth: g.depth,
128
+ paths: [],
129
+ });
130
+ }
131
+ return [...byName.values()].slice(0, NARRATIVE_FACT_MAX_HOTSPOTS);
132
+ }
133
+ function extractTier1FromRepoIntelligence(ri, changed) {
134
+ const primary = changed.primary;
135
+ const pkgIntel = primary && ri.packages?.[primary] ? ri.packages[primary] : undefined;
136
+ const packageUsage = collectPackageUsageForChanged(ri, changed);
137
+ const frameworks = collectFrameworks(ri);
138
+ const paths = allUsagePaths(packageUsage);
139
+ const runtimeRaw = pkgIntel?.runtimeSurface ?? ri.runtimeSurface ?? undefined;
140
+ const reachRaw = pkgIntel?.reachability ?? ri.reachability ?? undefined;
141
+ const runtimeKind = readSurfaceFromLoose(runtimeRaw) ?? (primary ? "unknown" : null);
142
+ const reachKind = readReachabilityFromLoose(reachRaw);
143
+ const runtimeSurface = runtimeKind != null
144
+ ? {
145
+ kind: runtimeKind,
146
+ evidence: {
147
+ packages: primary ? [primary] : changed.all.slice(0, 3),
148
+ paths,
149
+ },
150
+ }
151
+ : null;
152
+ const reachability = reachKind
153
+ ? {
154
+ kind: reachKind,
155
+ evidence: {
156
+ paths,
157
+ frameworks,
158
+ },
159
+ }
160
+ : null;
161
+ const blastFromRi = ri.blastRadius;
162
+ const blastLevel = readBlastLevel(blastFromRi?.level) ??
163
+ (changed.all.length > 0
164
+ ? blastLevelFromChangedCount(changed.all.length)
165
+ : null);
166
+ const blastRadius = blastLevel
167
+ ? {
168
+ level: blastLevel,
169
+ changedPackageCount: blastFromRi?.changedPackageCount ?? changed.all.length,
170
+ factors: uniqueStrings(blastFromRi?.factors ?? []),
171
+ }
172
+ : null;
173
+ const areasRaw = ri.applicationAreas ?? ri.affectedAreas ?? [];
174
+ const affectedAreas = [];
175
+ for (const area of areasRaw) {
176
+ affectedAreas.push({ id: area.id, label: area.label });
177
+ }
178
+ for (const row of packageUsage) {
179
+ for (const label of row.areas) {
180
+ const id = label.toLowerCase().replace(/\s+/g, "_");
181
+ if (!affectedAreas.some((a) => a.id === id)) {
182
+ affectedAreas.push({ id, label });
183
+ }
184
+ }
185
+ }
186
+ const codeHotspots = collectCodeHotspots(ri, primary);
187
+ return {
188
+ packageUsage,
189
+ frameworks,
190
+ runtimeSurface,
191
+ reachability,
192
+ blastRadius,
193
+ affectedAreas: affectedAreas.slice(0, NARRATIVE_FACT_MAX_AREAS),
194
+ hotspots: codeHotspots.slice(0, NARRATIVE_FACT_MAX_HOTSPOTS),
195
+ };
196
+ }
197
+ function hasMeaningfulTier1(tier1) {
198
+ return (tier1.packageUsage.length > 0 ||
199
+ tier1.frameworks.length > 0 ||
200
+ tier1.runtimeSurface != null ||
201
+ tier1.reachability != null ||
202
+ tier1.blastRadius != null ||
203
+ tier1.affectedAreas.length > 0 ||
204
+ tier1.hotspots.length > 0);
205
+ }
206
+ function insightScope(scope) {
207
+ if (scope === "changed" || scope === "all")
208
+ return scope;
209
+ return "unknown";
210
+ }
211
+ function guidanceFromInsight(insight, index) {
212
+ return {
213
+ kind: "insight",
214
+ id: `insight:${index}:${insight.type}`,
215
+ scope: insightScope(insight.scope),
216
+ priority: insight.priority,
217
+ message: insight.message,
218
+ context: insight.context,
219
+ remediation: insight.remediation,
220
+ affectedFiles: insight.affectedFiles,
221
+ };
222
+ }
223
+ function guidanceFromFinding(finding) {
224
+ return {
225
+ kind: "finding",
226
+ id: finding.id,
227
+ scope: "unknown",
228
+ priority: finding.severity,
229
+ message: finding.title,
230
+ context: finding.description,
231
+ remediation: finding.recommendation,
232
+ };
233
+ }
234
+ function guidanceFromRecommendation(rec) {
235
+ return {
236
+ kind: "recommendation",
237
+ id: rec.id,
238
+ scope: "unknown",
239
+ priority: String(rec.priorityScore ?? rec.rank ?? "medium"),
240
+ message: rec.title,
241
+ context: rec.rationale,
242
+ remediation: rec.impact,
243
+ };
244
+ }
245
+ function collectReviewerGuidance(result) {
246
+ const guidance = [];
247
+ const insights = Array.isArray(result.insights) ? result.insights : [];
248
+ const changedFirst = [
249
+ ...insights.filter((i) => i.scope === "changed"),
250
+ ...insights.filter((i) => i.scope !== "changed"),
251
+ ];
252
+ changedFirst.forEach((insight, index) => {
253
+ guidance.push(guidanceFromInsight(insight, index));
254
+ });
255
+ const findings = Array.isArray(result.findings) ? result.findings : [];
256
+ const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
257
+ const sortedFindings = [...findings].sort((a, b) => (severityOrder[b.severity] ?? 0) - (severityOrder[a.severity] ?? 0));
258
+ for (const finding of sortedFindings) {
259
+ guidance.push(guidanceFromFinding(finding));
260
+ }
261
+ const recs = Array.isArray(result.recommendations)
262
+ ? result.recommendations
263
+ : [];
264
+ for (const rec of recs) {
265
+ guidance.push(guidanceFromRecommendation(rec));
266
+ }
267
+ return guidance;
268
+ }
269
+ function resolveAvailabilityMode(tier1, tier2, codeIntelligenceAvailable) {
270
+ if (tier1 && codeIntelligenceAvailable)
271
+ return "pr_intelligence";
272
+ if (tier2)
273
+ return "insights";
274
+ return "graph_fallback";
275
+ }
276
+ function emptyTier1Blocks() {
277
+ return {
278
+ packageUsage: [],
279
+ frameworks: [],
280
+ runtimeSurface: null,
281
+ reachability: null,
282
+ blastRadius: null,
283
+ affectedAreas: [],
284
+ hotspots: [],
285
+ };
286
+ }
287
+ function blastRadiusFromChangedCount(count) {
288
+ return {
289
+ level: blastLevelFromChangedCount(count),
290
+ changedPackageCount: count,
291
+ factors: [],
292
+ };
293
+ }
294
+ /**
295
+ * Consumer-agnostic scan intelligence — structured facts only (no UI copy).
296
+ */
297
+ export function deriveScanNarrative(result) {
298
+ if (!result) {
299
+ return { ...EMPTY_SCAN_NARRATIVE_FACTS };
300
+ }
301
+ const changedPackages = parseChangedPackages(result.changedPackages);
302
+ const mergePosture = mergePostureFromDecision(result.decision?.recommendation) ?? null;
303
+ const riskIndex = typeof result.totalScore === "number" && Number.isFinite(result.totalScore)
304
+ ? result.totalScore
305
+ : null;
306
+ const codeIntelligenceAvailable = result.analysisPreparation?.codeIntelligenceAvailable !== false;
307
+ const parsedRi = parseRepoIntelligence(result.repoIntelligence);
308
+ const corpusGate = codeIntelligenceAvailable && parsedRi != null;
309
+ let tier1Blocks = emptyTier1Blocks();
310
+ if (corpusGate && parsedRi) {
311
+ tier1Blocks = extractTier1FromRepoIntelligence(parsedRi, changedPackages);
312
+ }
313
+ else if (changedPackages.all.length > 0) {
314
+ tier1Blocks = {
315
+ ...emptyTier1Blocks(),
316
+ blastRadius: blastRadiusFromChangedCount(changedPackages.all.length),
317
+ };
318
+ }
319
+ tier1Blocks.hotspots = mergeGraphHotspots(tier1Blocks.hotspots, result.graphInsights?.hotspots);
320
+ const tier1 = corpusGate && hasMeaningfulTier1(tier1Blocks);
321
+ const reviewerGuidance = collectReviewerGuidance(result);
322
+ const tier2 = reviewerGuidance.some((g) => g.scope === "changed" || g.kind === "insight");
323
+ const repositoryContext = extractRepositoryContextFacts(result);
324
+ const tier3 = repositoryContext.length > 0;
325
+ const mode = resolveAvailabilityMode(tier1, tier2 || reviewerGuidance.length > 0, codeIntelligenceAvailable);
326
+ const decisionConf = result.decision?.confidence;
327
+ const confidence = {
328
+ decision: decisionConf === "low" ||
329
+ decisionConf === "medium" ||
330
+ decisionConf === "high"
331
+ ? decisionConf
332
+ : null,
333
+ };
334
+ if (!tier1 && mode === "graph_fallback" && changedPackages.all.length > 0) {
335
+ tier1Blocks.blastRadius = blastRadiusFromChangedCount(changedPackages.all.length);
336
+ }
337
+ let affectedAreas = tier1Blocks.affectedAreas;
338
+ if (affectedAreas.length === 0) {
339
+ affectedAreas = selectTopAffectedAreas(result, { max: 2 }).map((label) => ({
340
+ id: label.toLowerCase().replace(/\s+/g, "_").slice(0, 48),
341
+ label,
342
+ }));
343
+ }
344
+ return {
345
+ availability: {
346
+ mode,
347
+ codeIntelligenceAvailable,
348
+ tiersPresent: {
349
+ tier1,
350
+ tier2: reviewerGuidance.length > 0,
351
+ tier3,
352
+ },
353
+ },
354
+ changedPackages,
355
+ packageUsage: tier1Blocks.packageUsage,
356
+ frameworks: tier1Blocks.frameworks,
357
+ runtimeSurface: tier1Blocks.runtimeSurface,
358
+ reachability: tier1Blocks.reachability,
359
+ blastRadius: tier1Blocks.blastRadius,
360
+ affectedAreas,
361
+ hotspots: tier1Blocks.hotspots,
362
+ reviewerGuidance,
363
+ confidence,
364
+ repositoryContext,
365
+ mergePosture,
366
+ riskIndex,
367
+ };
368
+ }
369
+ /** @internal Test helper — normalize loose engine strings. */
370
+ export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deriveScanNarrative.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deriveScanNarrative.test.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.test.ts"],"names":[],"mappings":""}