@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.
- 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.map +1 -1
- package/dist/deriveScanNarrative.js +157 -54
- package/dist/deriveScanNarrative.test.js +56 -1
- package/dist/fixtures/repoIntelligenceFixtures.d.ts +2 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
- package/dist/fixtures/repoIntelligenceFixtures.js +46 -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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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.map +1 -1
- package/dist/presentScanCardSummary.js +69 -64
- package/dist/riskVocabulary.js +1 -1
- package/dist/scanCardSummary.d.ts +9 -1
- package/dist/scanCardSummary.d.ts.map +1 -1
- package/dist/scanCardSummary.js +1 -1
- package/dist/scanCardSummary.test.js +12 -8
- package/dist/scanDetailViewModel.d.ts +9 -1
- package/dist/scanDetailViewModel.d.ts.map +1 -1
- package/dist/scanDetailViewModel.js +164 -37
- package/dist/scanDetailViewModel.test.js +27 -1
- package/dist/scanNarrativeFacts.d.ts +21 -8
- package/dist/scanNarrativeFacts.d.ts.map +1 -1
- package/dist/scanNarrativeFacts.js +6 -0
- package/dist/scanSurfaceCopy.d.ts +26 -26
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +26 -26
- 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
|
+
}
|
|
@@ -1 +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,
|
|
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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractRepositoryContextFacts } from "./extractRepositoryContextFacts.js";
|
|
2
2
|
import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind, parseRepoIntelligence, readBlastLevel, readReachabilityFromLoose, readSurfaceFromLoose, } from "./repoIntelligenceSchema.js";
|
|
3
|
-
import { EMPTY_SCAN_NARRATIVE_FACTS, } from "./scanNarrativeFacts.js";
|
|
3
|
+
import { EMPTY_SCAN_NARRATIVE_FACTS, NARRATIVE_FACT_MAX_AREAS, NARRATIVE_FACT_MAX_HOTSPOTS, NARRATIVE_FACT_MAX_PATHS, } from "./scanNarrativeFacts.js";
|
|
4
4
|
import { mergePostureFromDecision } from "./riskVocabulary.js";
|
|
5
5
|
import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
|
|
6
6
|
function parseChangedPackages(changed) {
|
|
@@ -20,28 +20,132 @@ function blastLevelFromChangedCount(count) {
|
|
|
20
20
|
return "moderate";
|
|
21
21
|
return "narrow";
|
|
22
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
|
+
}
|
|
23
133
|
function extractTier1FromRepoIntelligence(ri, changed) {
|
|
24
134
|
const primary = changed.primary;
|
|
25
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);
|
|
26
139
|
const runtimeRaw = pkgIntel?.runtimeSurface ?? ri.runtimeSurface ?? undefined;
|
|
27
140
|
const reachRaw = pkgIntel?.reachability ?? ri.reachability ?? undefined;
|
|
28
141
|
const runtimeKind = readSurfaceFromLoose(runtimeRaw) ?? (primary ? "unknown" : null);
|
|
29
142
|
const reachKind = readReachabilityFromLoose(reachRaw);
|
|
30
|
-
const usageEntry = pkgIntel?.usage ??
|
|
31
|
-
pkgIntel?.packageUsage ??
|
|
32
|
-
ri.packageUsage?.find((u) => (u.packageName ?? u.package) === primary ||
|
|
33
|
-
changed.all.includes(u.packageName ?? u.package ?? ""));
|
|
34
|
-
const paths = [
|
|
35
|
-
...(usageEntry?.paths ?? []),
|
|
36
|
-
...(usageEntry?.criticalPaths ?? []),
|
|
37
|
-
...(usageEntry?.files ?? []),
|
|
38
|
-
].filter(Boolean);
|
|
39
143
|
const runtimeSurface = runtimeKind != null
|
|
40
144
|
? {
|
|
41
145
|
kind: runtimeKind,
|
|
42
146
|
evidence: {
|
|
43
147
|
packages: primary ? [primary] : changed.all.slice(0, 3),
|
|
44
|
-
paths
|
|
148
|
+
paths,
|
|
45
149
|
},
|
|
46
150
|
}
|
|
47
151
|
: null;
|
|
@@ -49,8 +153,8 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
49
153
|
? {
|
|
50
154
|
kind: reachKind,
|
|
51
155
|
evidence: {
|
|
52
|
-
paths
|
|
53
|
-
frameworks
|
|
156
|
+
paths,
|
|
157
|
+
frameworks,
|
|
54
158
|
},
|
|
55
159
|
}
|
|
56
160
|
: null;
|
|
@@ -63,44 +167,37 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
63
167
|
? {
|
|
64
168
|
level: blastLevel,
|
|
65
169
|
changedPackageCount: blastFromRi?.changedPackageCount ?? changed.all.length,
|
|
66
|
-
factors: blastFromRi?.factors,
|
|
170
|
+
factors: uniqueStrings(blastFromRi?.factors ?? []),
|
|
67
171
|
}
|
|
68
172
|
: null;
|
|
69
173
|
const areasRaw = ri.applicationAreas ?? ri.affectedAreas ?? [];
|
|
70
|
-
const usageAreas = usageEntry?.areas ?? pkgIntel?.areas ?? [];
|
|
71
174
|
const affectedAreas = [];
|
|
72
175
|
for (const area of areasRaw) {
|
|
73
176
|
affectedAreas.push({ id: area.id, label: area.label });
|
|
74
177
|
}
|
|
75
|
-
for (const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
affectedAreas.
|
|
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
|
+
}
|
|
79
184
|
}
|
|
80
185
|
}
|
|
81
|
-
const
|
|
82
|
-
const riHotspots = ri.hotspots ?? pkgIntel?.hotspots ?? [];
|
|
83
|
-
for (const h of riHotspots) {
|
|
84
|
-
const name = h.packageName ?? primary;
|
|
85
|
-
if (!name)
|
|
86
|
-
continue;
|
|
87
|
-
hotspots.push({
|
|
88
|
-
packageName: name,
|
|
89
|
-
source: h.source === "graph" ? "graph" : "code",
|
|
90
|
-
depth: h.depth,
|
|
91
|
-
paths: h.paths,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
186
|
+
const codeHotspots = collectCodeHotspots(ri, primary);
|
|
94
187
|
return {
|
|
188
|
+
packageUsage,
|
|
189
|
+
frameworks,
|
|
95
190
|
runtimeSurface,
|
|
96
191
|
reachability,
|
|
97
192
|
blastRadius,
|
|
98
|
-
affectedAreas: affectedAreas.slice(0,
|
|
99
|
-
hotspots:
|
|
193
|
+
affectedAreas: affectedAreas.slice(0, NARRATIVE_FACT_MAX_AREAS),
|
|
194
|
+
hotspots: codeHotspots.slice(0, NARRATIVE_FACT_MAX_HOTSPOTS),
|
|
100
195
|
};
|
|
101
196
|
}
|
|
102
197
|
function hasMeaningfulTier1(tier1) {
|
|
103
|
-
return (tier1.
|
|
198
|
+
return (tier1.packageUsage.length > 0 ||
|
|
199
|
+
tier1.frameworks.length > 0 ||
|
|
200
|
+
tier1.runtimeSurface != null ||
|
|
104
201
|
tier1.reachability != null ||
|
|
105
202
|
tier1.blastRadius != null ||
|
|
106
203
|
tier1.affectedAreas.length > 0 ||
|
|
@@ -176,6 +273,24 @@ function resolveAvailabilityMode(tier1, tier2, codeIntelligenceAvailable) {
|
|
|
176
273
|
return "insights";
|
|
177
274
|
return "graph_fallback";
|
|
178
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
|
+
}
|
|
179
294
|
/**
|
|
180
295
|
* Consumer-agnostic scan intelligence — structured facts only (no UI copy).
|
|
181
296
|
*/
|
|
@@ -191,28 +306,17 @@ export function deriveScanNarrative(result) {
|
|
|
191
306
|
const codeIntelligenceAvailable = result.analysisPreparation?.codeIntelligenceAvailable !== false;
|
|
192
307
|
const parsedRi = parseRepoIntelligence(result.repoIntelligence);
|
|
193
308
|
const corpusGate = codeIntelligenceAvailable && parsedRi != null;
|
|
194
|
-
let tier1Blocks =
|
|
195
|
-
runtimeSurface: null,
|
|
196
|
-
reachability: null,
|
|
197
|
-
blastRadius: null,
|
|
198
|
-
affectedAreas: [],
|
|
199
|
-
hotspots: [],
|
|
200
|
-
};
|
|
309
|
+
let tier1Blocks = emptyTier1Blocks();
|
|
201
310
|
if (corpusGate && parsedRi) {
|
|
202
311
|
tier1Blocks = extractTier1FromRepoIntelligence(parsedRi, changedPackages);
|
|
203
312
|
}
|
|
204
313
|
else if (changedPackages.all.length > 0) {
|
|
205
314
|
tier1Blocks = {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
blastRadius: {
|
|
209
|
-
level: blastLevelFromChangedCount(changedPackages.all.length),
|
|
210
|
-
changedPackageCount: changedPackages.all.length,
|
|
211
|
-
},
|
|
212
|
-
affectedAreas: [],
|
|
213
|
-
hotspots: [],
|
|
315
|
+
...emptyTier1Blocks(),
|
|
316
|
+
blastRadius: blastRadiusFromChangedCount(changedPackages.all.length),
|
|
214
317
|
};
|
|
215
318
|
}
|
|
319
|
+
tier1Blocks.hotspots = mergeGraphHotspots(tier1Blocks.hotspots, result.graphInsights?.hotspots);
|
|
216
320
|
const tier1 = corpusGate && hasMeaningfulTier1(tier1Blocks);
|
|
217
321
|
const reviewerGuidance = collectReviewerGuidance(result);
|
|
218
322
|
const tier2 = reviewerGuidance.some((g) => g.scope === "changed" || g.kind === "insight");
|
|
@@ -228,10 +332,7 @@ export function deriveScanNarrative(result) {
|
|
|
228
332
|
: null,
|
|
229
333
|
};
|
|
230
334
|
if (!tier1 && mode === "graph_fallback" && changedPackages.all.length > 0) {
|
|
231
|
-
tier1Blocks.blastRadius =
|
|
232
|
-
level: blastLevelFromChangedCount(changedPackages.all.length),
|
|
233
|
-
changedPackageCount: changedPackages.all.length,
|
|
234
|
-
};
|
|
335
|
+
tier1Blocks.blastRadius = blastRadiusFromChangedCount(changedPackages.all.length);
|
|
235
336
|
}
|
|
236
337
|
let affectedAreas = tier1Blocks.affectedAreas;
|
|
237
338
|
if (affectedAreas.length === 0) {
|
|
@@ -251,6 +352,8 @@ export function deriveScanNarrative(result) {
|
|
|
251
352
|
},
|
|
252
353
|
},
|
|
253
354
|
changedPackages,
|
|
355
|
+
packageUsage: tier1Blocks.packageUsage,
|
|
356
|
+
frameworks: tier1Blocks.frameworks,
|
|
254
357
|
runtimeSurface: tier1Blocks.runtimeSurface,
|
|
255
358
|
reachability: tier1Blocks.reachability,
|
|
256
359
|
blastRadius: tier1Blocks.blastRadius,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
-
import { fixtureRepoIntelligenceEmpty, fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.js";
|
|
3
|
+
import { fixtureRepoIntelligenceEmpty, fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceMultiPackage, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.js";
|
|
4
4
|
const baseResult = {
|
|
5
5
|
totalScore: 10,
|
|
6
6
|
layerScores: {
|
|
@@ -96,6 +96,61 @@ describe("deriveScanNarrative", () => {
|
|
|
96
96
|
expect(facts.reachability?.kind).toBe("build_only");
|
|
97
97
|
expect(facts.blastRadius?.level).toBe("narrow");
|
|
98
98
|
});
|
|
99
|
+
it("extracts packageUsage for all changed packages with blast factors and frameworks", () => {
|
|
100
|
+
const result = {
|
|
101
|
+
...baseResult,
|
|
102
|
+
changedPackages: ["lodash", "axios"],
|
|
103
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
104
|
+
repoIntelligence: fixtureRepoIntelligenceMultiPackage,
|
|
105
|
+
};
|
|
106
|
+
const facts = deriveScanNarrative(result);
|
|
107
|
+
expect(facts.packageUsage).toHaveLength(2);
|
|
108
|
+
expect(facts.packageUsage.map((u) => u.packageName)).toEqual([
|
|
109
|
+
"lodash",
|
|
110
|
+
"axios",
|
|
111
|
+
]);
|
|
112
|
+
expect(facts.packageUsage[0]?.paths).toContain("apps/billing/export.ts");
|
|
113
|
+
expect(facts.blastRadius?.factors).toEqual([
|
|
114
|
+
"multiple_runtime_consumers",
|
|
115
|
+
"shared_middleware",
|
|
116
|
+
]);
|
|
117
|
+
expect(facts.frameworks).toEqual(["express", "react"]);
|
|
118
|
+
expect(facts.reachability?.evidence.frameworks).toEqual([
|
|
119
|
+
"express",
|
|
120
|
+
"react",
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
it("merges graph hotspots without duplicating code hotspots", () => {
|
|
124
|
+
const result = {
|
|
125
|
+
...baseResult,
|
|
126
|
+
changedPackages: ["fastify"],
|
|
127
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
128
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
129
|
+
graphInsights: {
|
|
130
|
+
maxDepth: 3,
|
|
131
|
+
nodes: 10,
|
|
132
|
+
edges: 12,
|
|
133
|
+
hotspots: [
|
|
134
|
+
{
|
|
135
|
+
kind: "hotspot",
|
|
136
|
+
packageName: "fastify",
|
|
137
|
+
direct: true,
|
|
138
|
+
depth: 2,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
kind: "hotspot",
|
|
142
|
+
packageName: "transitive-only-pkg",
|
|
143
|
+
direct: false,
|
|
144
|
+
depth: 4,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const facts = deriveScanNarrative(result);
|
|
150
|
+
expect(facts.hotspots.map((h) => h.packageName)).toContain("fastify");
|
|
151
|
+
expect(facts.hotspots.map((h) => h.packageName)).toContain("transitive-only-pkg");
|
|
152
|
+
expect(facts.hotspots.filter((h) => h.packageName === "fastify")).toHaveLength(1);
|
|
153
|
+
});
|
|
99
154
|
it("treats empty packages-only repoIntelligence as absent", () => {
|
|
100
155
|
const result = {
|
|
101
156
|
...baseResult,
|
|
@@ -5,4 +5,6 @@ export declare const fixtureRepoIntelligenceFastify: RepoIntelligence;
|
|
|
5
5
|
export declare const fixtureRepoIntelligenceTypescript: RepoIntelligence;
|
|
6
6
|
/** Empty corpus stub (engine-test-fixture default). */
|
|
7
7
|
export declare const fixtureRepoIntelligenceEmpty: RepoIntelligence;
|
|
8
|
+
/** Multi-package PR with distinct usage per package. */
|
|
9
|
+
export declare const fixtureRepoIntelligenceMultiPackage: RepoIntelligence;
|
|
8
10
|
//# sourceMappingURL=repoIntelligenceFixtures.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repoIntelligenceFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,EAAE,gBA8B5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,EAAE,gBAS/C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,EAAE,gBAE1C,CAAC"}
|
|
1
|
+
{"version":3,"file":"repoIntelligenceFixtures.d.ts","sourceRoot":"","sources":["../../src/fixtures/repoIntelligenceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,EAAE,gBA8B5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iCAAiC,EAAE,gBAS/C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,EAAE,gBAE1C,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,mCAAmC,EAAE,gBA4CjD,CAAC"}
|
|
@@ -45,3 +45,49 @@ export const fixtureRepoIntelligenceTypescript = {
|
|
|
45
45
|
export const fixtureRepoIntelligenceEmpty = {
|
|
46
46
|
packages: {},
|
|
47
47
|
};
|
|
48
|
+
/** Multi-package PR with distinct usage per package. */
|
|
49
|
+
export const fixtureRepoIntelligenceMultiPackage = {
|
|
50
|
+
packages: {
|
|
51
|
+
lodash: {
|
|
52
|
+
runtimeSurface: "runtime",
|
|
53
|
+
reachability: "on_runtime_paths",
|
|
54
|
+
usage: {
|
|
55
|
+
packageName: "lodash",
|
|
56
|
+
paths: ["apps/billing/export.ts"],
|
|
57
|
+
areas: ["Billing"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
axios: {
|
|
61
|
+
runtimeSurface: "runtime",
|
|
62
|
+
reachability: "on_runtime_paths",
|
|
63
|
+
usage: {
|
|
64
|
+
packageName: "axios",
|
|
65
|
+
paths: ["apps/api/client.ts"],
|
|
66
|
+
areas: ["API"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
packageUsage: [
|
|
71
|
+
{
|
|
72
|
+
packageName: "lodash",
|
|
73
|
+
paths: ["apps/billing/export.ts"],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
packageName: "axios",
|
|
77
|
+
paths: ["apps/api/client.ts"],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
blastRadius: {
|
|
81
|
+
level: "moderate",
|
|
82
|
+
factors: ["multiple_runtime_consumers", "shared_middleware"],
|
|
83
|
+
changedPackageCount: 2,
|
|
84
|
+
},
|
|
85
|
+
frameworks: ["express", "react"],
|
|
86
|
+
hotspots: [
|
|
87
|
+
{
|
|
88
|
+
packageName: "lodash",
|
|
89
|
+
source: "code",
|
|
90
|
+
paths: ["apps/billing/export.ts"],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
package/dist/formatInsight.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PRInsight,
|
|
1
|
+
import type { PRInsight, PRDecisionRecommendation } from "./types.js";
|
|
2
2
|
export type FormattedInsight = {
|
|
3
3
|
message: string;
|
|
4
4
|
where: string;
|
|
@@ -6,5 +6,5 @@ export type FormattedInsight = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function decisionTitle(recommendation: PRDecisionRecommendation): string;
|
|
8
8
|
export declare function formatInsight(insight: PRInsight): FormattedInsight;
|
|
9
|
-
export
|
|
9
|
+
export { renderInsightsAsMarkdown } from "./presentGitHubPrComment.js";
|
|
10
10
|
//# sourceMappingURL=formatInsight.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatInsight.d.ts","sourceRoot":"","sources":["../src/formatInsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,
|
|
1
|
+
{"version":3,"file":"formatInsight.d.ts","sourceRoot":"","sources":["../src/formatInsight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAET,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AASF,wBAAgB,aAAa,CAC3B,cAAc,EAAE,wBAAwB,GACvC,MAAM,CAER;AAgBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAMlE;AAiBD,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC"}
|
package/dist/formatInsight.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const DECISION_TITLES = {
|
|
2
2
|
safe: "No significant dependency risks detected",
|
|
3
3
|
needs_review: "Elevated dependency merge risk",
|
|
4
|
-
risky: "Merge blocked
|
|
4
|
+
risky: "Merge blocked - critical dependency risk",
|
|
5
5
|
};
|
|
6
6
|
// Title rendered once per PR comment, not per insight
|
|
7
7
|
export function decisionTitle(recommendation) {
|
|
@@ -41,9 +41,4 @@ function renderInsightAsMarkdown(f) {
|
|
|
41
41
|
f.action,
|
|
42
42
|
].join("\n");
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
export function renderInsightsAsMarkdown(insights, decision) {
|
|
46
|
-
const title = `**${decisionTitle(decision.recommendation)}**`;
|
|
47
|
-
const blocks = insights.map((insight) => renderInsightAsMarkdown(formatInsight(insight)));
|
|
48
|
-
return [title, "", ...blocks].join("\n\n---\n\n").trimEnd();
|
|
49
|
-
}
|
|
44
|
+
export { renderInsightsAsMarkdown } from "./presentGitHubPrComment.js";
|
|
@@ -88,7 +88,7 @@ describe("renderInsightsAsMarkdown", () => {
|
|
|
88
88
|
remediation: "fix text",
|
|
89
89
|
});
|
|
90
90
|
const output = renderInsightsAsMarkdown([insight], decision);
|
|
91
|
-
expect(output).toContain("
|
|
91
|
+
expect(output).toContain("Needs review");
|
|
92
92
|
expect(output).toContain("msg text");
|
|
93
93
|
expect(output).toContain("ctx text");
|
|
94
94
|
expect(output).toContain("fix text");
|