@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.
- package/dist/actionsStepSummary.d.ts.map +1 -1
- package/dist/actionsStepSummary.js +2 -29
- package/dist/collectNarrativeWhyBullets.d.ts +12 -0
- package/dist/collectNarrativeWhyBullets.d.ts.map +1 -0
- package/dist/collectNarrativeWhyBullets.js +104 -0
- package/dist/deriveScanNarrative.d.ts +10 -0
- package/dist/deriveScanNarrative.d.ts.map +1 -0
- package/dist/deriveScanNarrative.js +370 -0
- package/dist/deriveScanNarrative.test.d.ts +2 -0
- package/dist/deriveScanNarrative.test.d.ts.map +1 -0
- package/dist/deriveScanNarrative.test.js +164 -0
- package/dist/extractRepositoryContextFacts.d.ts +16 -0
- package/dist/extractRepositoryContextFacts.d.ts.map +1 -0
- package/dist/extractRepositoryContextFacts.js +71 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts +10 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -0
- package/dist/fixtures/repoIntelligenceFixtures.js +93 -0
- package/dist/formatInsight.d.ts +2 -2
- package/dist/formatInsight.d.ts.map +1 -1
- package/dist/formatInsight.js +2 -7
- package/dist/formatInsight.test.js +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/narrativeParity.test.d.ts +2 -0
- package/dist/narrativeParity.test.d.ts.map +1 -0
- package/dist/narrativeParity.test.js +64 -0
- package/dist/narrativePresentation.d.ts +50 -0
- package/dist/narrativePresentation.d.ts.map +1 -0
- package/dist/narrativePresentation.js +180 -0
- package/dist/narrativePresentation.test.d.ts +2 -0
- package/dist/narrativePresentation.test.d.ts.map +1 -0
- package/dist/narrativePresentation.test.js +53 -0
- package/dist/normalizeGeneratedText.d.ts +8 -0
- package/dist/normalizeGeneratedText.d.ts.map +1 -0
- package/dist/normalizeGeneratedText.js +31 -0
- package/dist/normalizeGeneratedText.test.d.ts +2 -0
- package/dist/normalizeGeneratedText.test.d.ts.map +1 -0
- package/dist/normalizeGeneratedText.test.js +18 -0
- package/dist/prCheckRunPresentation.d.ts +4 -3
- package/dist/prCheckRunPresentation.d.ts.map +1 -1
- package/dist/prCheckRunPresentation.js +56 -51
- package/dist/prCheckRunPresentation.test.js +2 -2
- package/dist/presentGitHubPrComment.d.ts +9 -0
- package/dist/presentGitHubPrComment.d.ts.map +1 -0
- package/dist/presentGitHubPrComment.js +80 -0
- package/dist/presentScanCardSummary.d.ts +16 -0
- package/dist/presentScanCardSummary.d.ts.map +1 -0
- package/dist/presentScanCardSummary.js +212 -0
- package/dist/productMessaging.d.ts +1 -1
- package/dist/productMessaging.js +1 -1
- package/dist/repoIntelligenceSchema.d.ts +123 -0
- package/dist/repoIntelligenceSchema.d.ts.map +1 -0
- package/dist/repoIntelligenceSchema.js +174 -0
- package/dist/riskVocabulary.js +1 -1
- package/dist/scanCardSummary.d.ts +21 -3
- package/dist/scanCardSummary.d.ts.map +1 -1
- package/dist/scanCardSummary.js +17 -58
- package/dist/scanCardSummary.test.js +25 -8
- package/dist/scanDetailViewModel.d.ts +22 -0
- package/dist/scanDetailViewModel.d.ts.map +1 -1
- package/dist/scanDetailViewModel.js +247 -31
- package/dist/scanDetailViewModel.test.js +29 -3
- package/dist/scanNarrativeFacts.d.ts +92 -0
- package/dist/scanNarrativeFacts.d.ts.map +1 -0
- package/dist/scanNarrativeFacts.js +24 -0
- package/dist/scanSurfaceCopy.d.ts +49 -23
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +49 -23
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actionsStepSummary.d.ts","sourceRoot":"","sources":["../src/actionsStepSummary.ts"],"names":[],"mappings":"
|
|
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 {
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"deriveScanNarrative.test.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.test.ts"],"names":[],"mappings":""}
|