@mergesignal/shared 0.2.7 → 0.3.0
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 +0 -3
- package/dist/deriveScanNarrative.d.ts.map +1 -1
- package/dist/deriveScanNarrative.js +177 -63
- package/dist/deriveScanNarrative.test.js +90 -6
- package/dist/engineWireRegression.test.d.ts +2 -0
- package/dist/engineWireRegression.test.d.ts.map +1 -0
- package/dist/engineWireRegression.test.js +57 -0
- package/dist/fixtures/legacyEngineRepoIntelligence.d.ts +28 -0
- package/dist/fixtures/legacyEngineRepoIntelligence.d.ts.map +1 -0
- package/dist/fixtures/legacyEngineRepoIntelligence.js +28 -0
- package/dist/fixtures/repoIntelligenceFixtures.d.ts +2 -2
- package/dist/fixtures/repoIntelligenceFixtures.d.ts.map +1 -1
- package/dist/fixtures/repoIntelligenceFixtures.js +59 -3
- package/dist/fixtures/repoIntelligenceTestHelpers.d.ts +5 -0
- package/dist/fixtures/repoIntelligenceTestHelpers.d.ts.map +1 -0
- package/dist/fixtures/repoIntelligenceTestHelpers.js +28 -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 +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/narrativeParity.test.d.ts +2 -0
- package/dist/narrativeParity.test.d.ts.map +1 -0
- package/dist/narrativeParity.test.js +65 -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 +54 -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/repoIntelligenceMappers.d.ts +20 -0
- package/dist/repoIntelligenceMappers.d.ts.map +1 -0
- package/dist/repoIntelligenceMappers.js +60 -0
- package/dist/repoIntelligenceSchema.contract.test.d.ts +2 -0
- package/dist/repoIntelligenceSchema.contract.test.d.ts.map +1 -0
- package/dist/repoIntelligenceSchema.contract.test.js +19 -0
- package/dist/repoIntelligenceSchema.d.ts +52 -83
- package/dist/repoIntelligenceSchema.d.ts.map +1 -1
- package/dist/repoIntelligenceSchema.js +99 -141
- package/dist/repoIntelligenceValidation.d.ts +18 -0
- package/dist/repoIntelligenceValidation.d.ts.map +1 -0
- package/dist/repoIntelligenceValidation.js +76 -0
- package/dist/repoIntelligenceValidation.persist.test.d.ts +2 -0
- package/dist/repoIntelligenceValidation.persist.test.d.ts.map +1 -0
- package/dist/repoIntelligenceValidation.persist.test.js +44 -0
- 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 +28 -1
- package/dist/scanNarrativeFacts.d.ts +23 -8
- package/dist/scanNarrativeFacts.d.ts.map +1 -1
- package/dist/scanNarrativeFacts.js +7 -0
- package/dist/scanSurfaceCopy.d.ts +26 -26
- package/dist/scanSurfaceCopy.d.ts.map +1 -1
- package/dist/scanSurfaceCopy.js +26 -26
- package/dist/types.d.ts +15 -3
- 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
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { normalizeReachabilityKind, normalizeRuntimeSurfaceKind } from "./repoIntelligenceSchema.js";
|
|
2
1
|
import { type ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
3
2
|
import type { ScanResult } from "./types.js";
|
|
4
3
|
/**
|
|
5
4
|
* Consumer-agnostic scan intelligence — structured facts only (no UI copy).
|
|
6
5
|
*/
|
|
7
6
|
export declare function deriveScanNarrative(result: ScanResult | null | undefined): ScanNarrativeFacts;
|
|
8
|
-
/** @internal Test helper — normalize loose engine strings. */
|
|
9
|
-
export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
|
|
10
7
|
//# sourceMappingURL=deriveScanNarrative.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deriveScanNarrative.d.ts","sourceRoot":"","sources":["../src/deriveScanNarrative.ts"],"names":[],"mappings":"AAMA,OAAO,EAaL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAKV,UAAU,EACX,MAAM,YAAY,CAAC;AAgapB;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACpC,kBAAkB,CAsGpB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractRepositoryContextFacts } from "./extractRepositoryContextFacts.js";
|
|
2
|
-
import {
|
|
3
|
-
import { EMPTY_SCAN_NARRATIVE_FACTS, } from "./scanNarrativeFacts.js";
|
|
2
|
+
import { readBlastLevel, safeParseRepoIntelligence, } 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
4
|
import { mergePostureFromDecision } from "./riskVocabulary.js";
|
|
5
5
|
import { selectTopAffectedAreas } from "./selectTopAffectedAreas.js";
|
|
6
6
|
function parseChangedPackages(changed) {
|
|
@@ -20,28 +20,124 @@ 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 collectPackageUsageForChanged(ri, changed) {
|
|
36
|
+
const byPackage = new Map();
|
|
37
|
+
const ensure = (packageName) => {
|
|
38
|
+
let row = byPackage.get(packageName);
|
|
39
|
+
if (!row) {
|
|
40
|
+
row = {
|
|
41
|
+
packageName,
|
|
42
|
+
paths: [],
|
|
43
|
+
criticalPaths: [],
|
|
44
|
+
files: [],
|
|
45
|
+
areas: [],
|
|
46
|
+
};
|
|
47
|
+
byPackage.set(packageName, row);
|
|
48
|
+
}
|
|
49
|
+
return row;
|
|
50
|
+
};
|
|
51
|
+
const mergeEntry = (entry, packageNameOverride) => {
|
|
52
|
+
const name = packageNameOverride ?? entry.packageName.trim();
|
|
53
|
+
if (!changed.all.includes(name))
|
|
54
|
+
return;
|
|
55
|
+
const row = ensure(name);
|
|
56
|
+
row.paths = uniqueStrings([...row.paths, ...(entry.paths ?? [])]);
|
|
57
|
+
row.criticalPaths = uniqueStrings([
|
|
58
|
+
...row.criticalPaths,
|
|
59
|
+
...(entry.criticalPaths ?? []),
|
|
60
|
+
]);
|
|
61
|
+
row.files = uniqueStrings([...row.files, ...(entry.files ?? [])]);
|
|
62
|
+
row.areas = uniqueStrings([...row.areas, ...(entry.areas ?? [])]);
|
|
63
|
+
};
|
|
64
|
+
for (const name of changed.all) {
|
|
65
|
+
const pkgIntel = ri.packages?.[name];
|
|
66
|
+
if (pkgIntel?.usage)
|
|
67
|
+
mergeEntry(pkgIntel.usage, name);
|
|
68
|
+
if (pkgIntel?.areas?.length) {
|
|
69
|
+
const row = ensure(name);
|
|
70
|
+
row.areas = uniqueStrings([...row.areas, ...pkgIntel.areas]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(ri.packageUsage)) {
|
|
74
|
+
for (const entry of ri.packageUsage) {
|
|
75
|
+
mergeEntry(entry);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return changed.all
|
|
79
|
+
.map((name) => byPackage.get(name))
|
|
80
|
+
.filter((row) => row != null);
|
|
81
|
+
}
|
|
82
|
+
function allUsagePaths(usage) {
|
|
83
|
+
const paths = [];
|
|
84
|
+
for (const row of usage) {
|
|
85
|
+
paths.push(...row.paths, ...row.criticalPaths, ...row.files);
|
|
86
|
+
}
|
|
87
|
+
return uniqueStrings(paths).slice(0, NARRATIVE_FACT_MAX_PATHS);
|
|
88
|
+
}
|
|
89
|
+
function collectFrameworks(ri) {
|
|
90
|
+
return uniqueStrings(ri.frameworks ?? []);
|
|
91
|
+
}
|
|
92
|
+
function collectCodeHotspots(ri, primary) {
|
|
93
|
+
const hotspots = [];
|
|
94
|
+
const pkgIntel = primary && ri.packages?.[primary] ? ri.packages[primary] : undefined;
|
|
95
|
+
const riHotspots = ri.hotspots ?? [];
|
|
96
|
+
for (const h of riHotspots) {
|
|
97
|
+
const name = (h.packageName ?? primary ?? "").trim();
|
|
98
|
+
if (!name)
|
|
99
|
+
continue;
|
|
100
|
+
hotspots.push({
|
|
101
|
+
packageName: name,
|
|
102
|
+
source: h.source === "graph" ? "graph" : "code",
|
|
103
|
+
depth: h.depth,
|
|
104
|
+
paths: uniqueStrings(h.paths ?? []),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return hotspots;
|
|
108
|
+
}
|
|
109
|
+
function mergeGraphHotspots(codeHotspots, graphHotspots) {
|
|
110
|
+
const byName = new Map();
|
|
111
|
+
for (const h of codeHotspots) {
|
|
112
|
+
byName.set(h.packageName, h);
|
|
113
|
+
}
|
|
114
|
+
for (const g of graphHotspots ?? []) {
|
|
115
|
+
const name = g.packageName?.trim();
|
|
116
|
+
if (!name || byName.has(name))
|
|
117
|
+
continue;
|
|
118
|
+
byName.set(name, {
|
|
119
|
+
packageName: name,
|
|
120
|
+
source: "graph",
|
|
121
|
+
depth: g.depth,
|
|
122
|
+
paths: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return [...byName.values()].slice(0, NARRATIVE_FACT_MAX_HOTSPOTS);
|
|
126
|
+
}
|
|
23
127
|
function extractTier1FromRepoIntelligence(ri, changed) {
|
|
24
128
|
const primary = changed.primary;
|
|
25
129
|
const pkgIntel = primary && ri.packages?.[primary] ? ri.packages[primary] : undefined;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
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);
|
|
130
|
+
const packageUsage = collectPackageUsageForChanged(ri, changed);
|
|
131
|
+
const frameworks = collectFrameworks(ri);
|
|
132
|
+
const paths = allUsagePaths(packageUsage);
|
|
133
|
+
const runtimeKind = pkgIntel?.runtimeSurface ?? null;
|
|
134
|
+
const reachKind = pkgIntel?.reachability ?? null;
|
|
39
135
|
const runtimeSurface = runtimeKind != null
|
|
40
136
|
? {
|
|
41
137
|
kind: runtimeKind,
|
|
42
138
|
evidence: {
|
|
43
139
|
packages: primary ? [primary] : changed.all.slice(0, 3),
|
|
44
|
-
paths
|
|
140
|
+
paths,
|
|
45
141
|
},
|
|
46
142
|
}
|
|
47
143
|
: null;
|
|
@@ -49,8 +145,8 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
49
145
|
? {
|
|
50
146
|
kind: reachKind,
|
|
51
147
|
evidence: {
|
|
52
|
-
paths
|
|
53
|
-
frameworks
|
|
148
|
+
paths,
|
|
149
|
+
frameworks,
|
|
54
150
|
},
|
|
55
151
|
}
|
|
56
152
|
: null;
|
|
@@ -63,44 +159,37 @@ function extractTier1FromRepoIntelligence(ri, changed) {
|
|
|
63
159
|
? {
|
|
64
160
|
level: blastLevel,
|
|
65
161
|
changedPackageCount: blastFromRi?.changedPackageCount ?? changed.all.length,
|
|
66
|
-
factors: blastFromRi?.factors,
|
|
162
|
+
factors: uniqueStrings(blastFromRi?.factors ?? []),
|
|
67
163
|
}
|
|
68
164
|
: null;
|
|
69
165
|
const areasRaw = ri.applicationAreas ?? ri.affectedAreas ?? [];
|
|
70
|
-
const usageAreas = usageEntry?.areas ?? pkgIntel?.areas ?? [];
|
|
71
166
|
const affectedAreas = [];
|
|
72
167
|
for (const area of areasRaw) {
|
|
73
168
|
affectedAreas.push({ id: area.id, label: area.label });
|
|
74
169
|
}
|
|
75
|
-
for (const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
affectedAreas.
|
|
170
|
+
for (const row of packageUsage) {
|
|
171
|
+
for (const label of row.areas) {
|
|
172
|
+
const id = label.toLowerCase().replace(/\s+/g, "_");
|
|
173
|
+
if (!affectedAreas.some((a) => a.id === id)) {
|
|
174
|
+
affectedAreas.push({ id, label });
|
|
175
|
+
}
|
|
79
176
|
}
|
|
80
177
|
}
|
|
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
|
-
}
|
|
178
|
+
const codeHotspots = collectCodeHotspots(ri, primary);
|
|
94
179
|
return {
|
|
180
|
+
packageUsage,
|
|
181
|
+
frameworks,
|
|
95
182
|
runtimeSurface,
|
|
96
183
|
reachability,
|
|
97
184
|
blastRadius,
|
|
98
|
-
affectedAreas: affectedAreas.slice(0,
|
|
99
|
-
hotspots:
|
|
185
|
+
affectedAreas: affectedAreas.slice(0, NARRATIVE_FACT_MAX_AREAS),
|
|
186
|
+
hotspots: codeHotspots.slice(0, NARRATIVE_FACT_MAX_HOTSPOTS),
|
|
100
187
|
};
|
|
101
188
|
}
|
|
102
189
|
function hasMeaningfulTier1(tier1) {
|
|
103
|
-
return (tier1.
|
|
190
|
+
return (tier1.packageUsage.length > 0 ||
|
|
191
|
+
tier1.frameworks.length > 0 ||
|
|
192
|
+
tier1.runtimeSurface != null ||
|
|
104
193
|
tier1.reachability != null ||
|
|
105
194
|
tier1.blastRadius != null ||
|
|
106
195
|
tier1.affectedAreas.length > 0 ||
|
|
@@ -176,6 +265,42 @@ function resolveAvailabilityMode(tier1, tier2, codeIntelligenceAvailable) {
|
|
|
176
265
|
return "insights";
|
|
177
266
|
return "graph_fallback";
|
|
178
267
|
}
|
|
268
|
+
function emptyTier1Blocks() {
|
|
269
|
+
return {
|
|
270
|
+
packageUsage: [],
|
|
271
|
+
frameworks: [],
|
|
272
|
+
runtimeSurface: null,
|
|
273
|
+
reachability: null,
|
|
274
|
+
blastRadius: null,
|
|
275
|
+
affectedAreas: [],
|
|
276
|
+
hotspots: [],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function blastRadiusFromChangedCount(count) {
|
|
280
|
+
return {
|
|
281
|
+
level: blastLevelFromChangedCount(count),
|
|
282
|
+
changedPackageCount: count,
|
|
283
|
+
factors: [],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function resolveTrustedRepoIntelligence(result) {
|
|
287
|
+
const raw = result.repoIntelligence;
|
|
288
|
+
const validationStatus = result.analysisPreparation?.repoIntelligenceValidation?.status;
|
|
289
|
+
if (raw == null) {
|
|
290
|
+
return { parsed: null, parseStatus: "absent" };
|
|
291
|
+
}
|
|
292
|
+
if (validationStatus === "invalid") {
|
|
293
|
+
return { parsed: null, parseStatus: "untrusted" };
|
|
294
|
+
}
|
|
295
|
+
if (validationStatus !== "valid") {
|
|
296
|
+
return { parsed: null, parseStatus: "absent" };
|
|
297
|
+
}
|
|
298
|
+
const parsed = safeParseRepoIntelligence(raw);
|
|
299
|
+
if (parsed.ok) {
|
|
300
|
+
return { parsed: parsed.value, parseStatus: "ok" };
|
|
301
|
+
}
|
|
302
|
+
return { parsed: null, parseStatus: "invalid" };
|
|
303
|
+
}
|
|
179
304
|
/**
|
|
180
305
|
* Consumer-agnostic scan intelligence — structured facts only (no UI copy).
|
|
181
306
|
*/
|
|
@@ -189,30 +314,21 @@ export function deriveScanNarrative(result) {
|
|
|
189
314
|
? result.totalScore
|
|
190
315
|
: null;
|
|
191
316
|
const codeIntelligenceAvailable = result.analysisPreparation?.codeIntelligenceAvailable !== false;
|
|
192
|
-
const parsedRi =
|
|
193
|
-
const corpusGate = codeIntelligenceAvailable &&
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
blastRadius: null,
|
|
198
|
-
affectedAreas: [],
|
|
199
|
-
hotspots: [],
|
|
200
|
-
};
|
|
317
|
+
const { parsed: parsedRi, parseStatus: repoIntelligenceParse } = resolveTrustedRepoIntelligence(result);
|
|
318
|
+
const corpusGate = codeIntelligenceAvailable &&
|
|
319
|
+
repoIntelligenceParse === "ok" &&
|
|
320
|
+
parsedRi != null;
|
|
321
|
+
let tier1Blocks = emptyTier1Blocks();
|
|
201
322
|
if (corpusGate && parsedRi) {
|
|
202
323
|
tier1Blocks = extractTier1FromRepoIntelligence(parsedRi, changedPackages);
|
|
203
324
|
}
|
|
204
325
|
else if (changedPackages.all.length > 0) {
|
|
205
326
|
tier1Blocks = {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
blastRadius: {
|
|
209
|
-
level: blastLevelFromChangedCount(changedPackages.all.length),
|
|
210
|
-
changedPackageCount: changedPackages.all.length,
|
|
211
|
-
},
|
|
212
|
-
affectedAreas: [],
|
|
213
|
-
hotspots: [],
|
|
327
|
+
...emptyTier1Blocks(),
|
|
328
|
+
blastRadius: blastRadiusFromChangedCount(changedPackages.all.length),
|
|
214
329
|
};
|
|
215
330
|
}
|
|
331
|
+
tier1Blocks.hotspots = mergeGraphHotspots(tier1Blocks.hotspots, result.graphInsights?.hotspots);
|
|
216
332
|
const tier1 = corpusGate && hasMeaningfulTier1(tier1Blocks);
|
|
217
333
|
const reviewerGuidance = collectReviewerGuidance(result);
|
|
218
334
|
const tier2 = reviewerGuidance.some((g) => g.scope === "changed" || g.kind === "insight");
|
|
@@ -228,10 +344,7 @@ export function deriveScanNarrative(result) {
|
|
|
228
344
|
: null,
|
|
229
345
|
};
|
|
230
346
|
if (!tier1 && mode === "graph_fallback" && changedPackages.all.length > 0) {
|
|
231
|
-
tier1Blocks.blastRadius =
|
|
232
|
-
level: blastLevelFromChangedCount(changedPackages.all.length),
|
|
233
|
-
changedPackageCount: changedPackages.all.length,
|
|
234
|
-
};
|
|
347
|
+
tier1Blocks.blastRadius = blastRadiusFromChangedCount(changedPackages.all.length);
|
|
235
348
|
}
|
|
236
349
|
let affectedAreas = tier1Blocks.affectedAreas;
|
|
237
350
|
if (affectedAreas.length === 0) {
|
|
@@ -249,8 +362,11 @@ export function deriveScanNarrative(result) {
|
|
|
249
362
|
tier2: reviewerGuidance.length > 0,
|
|
250
363
|
tier3,
|
|
251
364
|
},
|
|
365
|
+
repoIntelligenceParse,
|
|
252
366
|
},
|
|
253
367
|
changedPackages,
|
|
368
|
+
packageUsage: tier1Blocks.packageUsage,
|
|
369
|
+
frameworks: tier1Blocks.frameworks,
|
|
254
370
|
runtimeSurface: tier1Blocks.runtimeSurface,
|
|
255
371
|
reachability: tier1Blocks.reachability,
|
|
256
372
|
blastRadius: tier1Blocks.blastRadius,
|
|
@@ -263,5 +379,3 @@ export function deriveScanNarrative(result) {
|
|
|
263
379
|
riskIndex,
|
|
264
380
|
};
|
|
265
381
|
}
|
|
266
|
-
/** @internal Test helper — normalize loose engine strings. */
|
|
267
|
-
export { normalizeReachabilityKind, normalizeRuntimeSurfaceKind };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
-
import {
|
|
3
|
+
import { legacyEngineRepoIntelligenceJwt } from "./fixtures/legacyEngineRepoIntelligence.js";
|
|
4
|
+
import { fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceMultiPackage, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.js";
|
|
5
|
+
import { analysisPreparationWithInvalidRepoIntel, analysisPreparationWithValidRepoIntel, } from "./fixtures/repoIntelligenceTestHelpers.js";
|
|
4
6
|
const baseResult = {
|
|
5
7
|
totalScore: 10,
|
|
6
8
|
layerScores: {
|
|
@@ -18,15 +20,42 @@ describe("deriveScanNarrative", () => {
|
|
|
18
20
|
expect(facts.availability.mode).toBe("denormalized");
|
|
19
21
|
expect(facts.changedPackages.all).toEqual([]);
|
|
20
22
|
});
|
|
23
|
+
it("ignores repoIntelligence when validation status is not valid", () => {
|
|
24
|
+
const result = {
|
|
25
|
+
...baseResult,
|
|
26
|
+
changedPackages: ["fastify"],
|
|
27
|
+
analysisPreparation: {
|
|
28
|
+
codeIntelligenceAvailable: true,
|
|
29
|
+
warnings: [],
|
|
30
|
+
},
|
|
31
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
32
|
+
};
|
|
33
|
+
const facts = deriveScanNarrative(result);
|
|
34
|
+
expect(facts.availability.repoIntelligenceParse).toBe("absent");
|
|
35
|
+
expect(facts.availability.tiersPresent.tier1).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it("treats worker-invalid repoIntelligence as untrusted (raw retained, not consumed)", () => {
|
|
38
|
+
const result = {
|
|
39
|
+
...baseResult,
|
|
40
|
+
changedPackages: ["jsonwebtoken"],
|
|
41
|
+
analysisPreparation: analysisPreparationWithInvalidRepoIntel(),
|
|
42
|
+
repoIntelligence: legacyEngineRepoIntelligenceJwt,
|
|
43
|
+
};
|
|
44
|
+
const facts = deriveScanNarrative(result);
|
|
45
|
+
expect(facts.availability.repoIntelligenceParse).toBe("untrusted");
|
|
46
|
+
expect(facts.availability.tiersPresent.tier1).toBe(false);
|
|
47
|
+
expect(facts.packageUsage).toHaveLength(0);
|
|
48
|
+
});
|
|
21
49
|
it("populates tier1 from repoIntelligence when corpus available", () => {
|
|
22
50
|
const result = {
|
|
23
51
|
...baseResult,
|
|
24
52
|
changedPackages: ["fastify"],
|
|
25
|
-
analysisPreparation:
|
|
53
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
26
54
|
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
27
55
|
};
|
|
28
56
|
const facts = deriveScanNarrative(result);
|
|
29
57
|
expect(facts.availability.mode).toBe("pr_intelligence");
|
|
58
|
+
expect(facts.availability.repoIntelligenceParse).toBe("ok");
|
|
30
59
|
expect(facts.availability.codeIntelligenceAvailable).toBe(true);
|
|
31
60
|
expect(facts.changedPackages.primary).toBe("fastify");
|
|
32
61
|
expect(facts.runtimeSurface?.kind).toBe("runtime");
|
|
@@ -87,7 +116,7 @@ describe("deriveScanNarrative", () => {
|
|
|
87
116
|
const result = {
|
|
88
117
|
...baseResult,
|
|
89
118
|
changedPackages: ["typescript"],
|
|
90
|
-
analysisPreparation:
|
|
119
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
91
120
|
repoIntelligence: fixtureRepoIntelligenceTypescript,
|
|
92
121
|
};
|
|
93
122
|
const facts = deriveScanNarrative(result);
|
|
@@ -96,14 +125,69 @@ describe("deriveScanNarrative", () => {
|
|
|
96
125
|
expect(facts.reachability?.kind).toBe("build_only");
|
|
97
126
|
expect(facts.blastRadius?.level).toBe("narrow");
|
|
98
127
|
});
|
|
99
|
-
it("
|
|
128
|
+
it("extracts packageUsage for all changed packages with blast factors and frameworks", () => {
|
|
129
|
+
const result = {
|
|
130
|
+
...baseResult,
|
|
131
|
+
changedPackages: ["lodash", "axios"],
|
|
132
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
133
|
+
repoIntelligence: fixtureRepoIntelligenceMultiPackage,
|
|
134
|
+
};
|
|
135
|
+
const facts = deriveScanNarrative(result);
|
|
136
|
+
expect(facts.packageUsage).toHaveLength(2);
|
|
137
|
+
expect(facts.packageUsage.map((u) => u.packageName)).toEqual([
|
|
138
|
+
"lodash",
|
|
139
|
+
"axios",
|
|
140
|
+
]);
|
|
141
|
+
expect(facts.packageUsage[0]?.paths).toContain("apps/billing/export.ts");
|
|
142
|
+
expect(facts.blastRadius?.factors).toEqual([
|
|
143
|
+
"multiple_runtime_consumers",
|
|
144
|
+
"shared_middleware",
|
|
145
|
+
]);
|
|
146
|
+
expect(facts.frameworks).toEqual(["express", "react"]);
|
|
147
|
+
expect(facts.reachability?.evidence.frameworks).toEqual([
|
|
148
|
+
"express",
|
|
149
|
+
"react",
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
it("merges graph hotspots without duplicating code hotspots", () => {
|
|
153
|
+
const result = {
|
|
154
|
+
...baseResult,
|
|
155
|
+
changedPackages: ["fastify"],
|
|
156
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
157
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
158
|
+
graphInsights: {
|
|
159
|
+
maxDepth: 3,
|
|
160
|
+
nodes: 10,
|
|
161
|
+
edges: 12,
|
|
162
|
+
hotspots: [
|
|
163
|
+
{
|
|
164
|
+
kind: "hotspot",
|
|
165
|
+
packageName: "fastify",
|
|
166
|
+
direct: true,
|
|
167
|
+
depth: 2,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
kind: "hotspot",
|
|
171
|
+
packageName: "transitive-only-pkg",
|
|
172
|
+
direct: false,
|
|
173
|
+
depth: 4,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const facts = deriveScanNarrative(result);
|
|
179
|
+
expect(facts.hotspots.map((h) => h.packageName)).toContain("fastify");
|
|
180
|
+
expect(facts.hotspots.map((h) => h.packageName)).toContain("transitive-only-pkg");
|
|
181
|
+
expect(facts.hotspots.filter((h) => h.packageName === "fastify")).toHaveLength(1);
|
|
182
|
+
});
|
|
183
|
+
it("does not tier1 when repoIntelligence block is omitted", () => {
|
|
100
184
|
const result = {
|
|
101
185
|
...baseResult,
|
|
102
186
|
changedPackages: ["react"],
|
|
103
|
-
analysisPreparation:
|
|
104
|
-
repoIntelligence: fixtureRepoIntelligenceEmpty,
|
|
187
|
+
analysisPreparation: analysisPreparationWithValidRepoIntel(),
|
|
105
188
|
};
|
|
106
189
|
const facts = deriveScanNarrative(result);
|
|
190
|
+
expect(facts.availability.repoIntelligenceParse).toBe("absent");
|
|
107
191
|
expect(facts.availability.tiersPresent.tier1).toBe(false);
|
|
108
192
|
});
|
|
109
193
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engineWireRegression.test.d.ts","sourceRoot":"","sources":["../src/engineWireRegression.test.ts"],"names":[],"mappings":""}
|