@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
+
import { fixtureRepoIntelligenceEmpty, fixtureRepoIntelligenceFastify, fixtureRepoIntelligenceMultiPackage, fixtureRepoIntelligenceTypescript, } from "./fixtures/repoIntelligenceFixtures.js";
|
|
4
|
+
const baseResult = {
|
|
5
|
+
totalScore: 10,
|
|
6
|
+
layerScores: {
|
|
7
|
+
security: 1,
|
|
8
|
+
maintainability: 2,
|
|
9
|
+
ecosystem: 3,
|
|
10
|
+
upgradeImpact: 4,
|
|
11
|
+
},
|
|
12
|
+
findings: [],
|
|
13
|
+
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
14
|
+
};
|
|
15
|
+
describe("deriveScanNarrative", () => {
|
|
16
|
+
it("returns denormalized empty facts for null result", () => {
|
|
17
|
+
const facts = deriveScanNarrative(null);
|
|
18
|
+
expect(facts.availability.mode).toBe("denormalized");
|
|
19
|
+
expect(facts.changedPackages.all).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
it("populates tier1 from repoIntelligence when corpus available", () => {
|
|
22
|
+
const result = {
|
|
23
|
+
...baseResult,
|
|
24
|
+
changedPackages: ["fastify"],
|
|
25
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
26
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
27
|
+
};
|
|
28
|
+
const facts = deriveScanNarrative(result);
|
|
29
|
+
expect(facts.availability.mode).toBe("pr_intelligence");
|
|
30
|
+
expect(facts.availability.codeIntelligenceAvailable).toBe(true);
|
|
31
|
+
expect(facts.changedPackages.primary).toBe("fastify");
|
|
32
|
+
expect(facts.runtimeSurface?.kind).toBe("runtime");
|
|
33
|
+
expect(facts.reachability?.kind).toBe("on_runtime_paths");
|
|
34
|
+
expect(facts.blastRadius?.level).toBe("moderate");
|
|
35
|
+
expect(facts.affectedAreas.map((a) => a.id)).toContain("api");
|
|
36
|
+
expect(facts.hotspots[0]?.source).toBe("code");
|
|
37
|
+
});
|
|
38
|
+
it("prefers changed-scope insights for reviewer guidance ordering", () => {
|
|
39
|
+
const result = {
|
|
40
|
+
...baseResult,
|
|
41
|
+
changedPackages: ["lodash"],
|
|
42
|
+
insights: [
|
|
43
|
+
{
|
|
44
|
+
type: "dependency_risk",
|
|
45
|
+
priority: "medium",
|
|
46
|
+
confidence: "likely",
|
|
47
|
+
scope: "all",
|
|
48
|
+
message: "Repository-wide duplicate paths",
|
|
49
|
+
context: "c",
|
|
50
|
+
remediation: "r",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "usage_risk",
|
|
54
|
+
priority: "high",
|
|
55
|
+
confidence: "confirmed",
|
|
56
|
+
scope: "changed",
|
|
57
|
+
message: "lodash used in billing export",
|
|
58
|
+
context: "c2",
|
|
59
|
+
remediation: "r2",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
const facts = deriveScanNarrative(result);
|
|
64
|
+
expect(facts.reviewerGuidance[0]?.scope).toBe("changed");
|
|
65
|
+
expect(facts.reviewerGuidance[0]?.message).toContain("billing");
|
|
66
|
+
});
|
|
67
|
+
it("uses graph_fallback with structured repository context", () => {
|
|
68
|
+
const result = {
|
|
69
|
+
...baseResult,
|
|
70
|
+
explain: {
|
|
71
|
+
reasons: [
|
|
72
|
+
{
|
|
73
|
+
id: "1",
|
|
74
|
+
layer: "ecosystem",
|
|
75
|
+
title: "graph.transitive volume",
|
|
76
|
+
scoreImpact: 18,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const facts = deriveScanNarrative(result);
|
|
82
|
+
expect(facts.availability.mode).toBe("graph_fallback");
|
|
83
|
+
expect(facts.repositoryContext.length).toBeGreaterThan(0);
|
|
84
|
+
expect(facts.repositoryContext[0]?.family).toBe("transitive_volume");
|
|
85
|
+
});
|
|
86
|
+
it("populates build-only runtime surface from repoIntelligence", () => {
|
|
87
|
+
const result = {
|
|
88
|
+
...baseResult,
|
|
89
|
+
changedPackages: ["typescript"],
|
|
90
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
91
|
+
repoIntelligence: fixtureRepoIntelligenceTypescript,
|
|
92
|
+
};
|
|
93
|
+
const facts = deriveScanNarrative(result);
|
|
94
|
+
expect(facts.availability.mode).toBe("pr_intelligence");
|
|
95
|
+
expect(facts.runtimeSurface?.kind).toBe("build");
|
|
96
|
+
expect(facts.reachability?.kind).toBe("build_only");
|
|
97
|
+
expect(facts.blastRadius?.level).toBe("narrow");
|
|
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
|
+
});
|
|
154
|
+
it("treats empty packages-only repoIntelligence as absent", () => {
|
|
155
|
+
const result = {
|
|
156
|
+
...baseResult,
|
|
157
|
+
changedPackages: ["react"],
|
|
158
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
159
|
+
repoIntelligence: fixtureRepoIntelligenceEmpty,
|
|
160
|
+
};
|
|
161
|
+
const facts = deriveScanNarrative(result);
|
|
162
|
+
expect(facts.availability.tiersPresent.tier1).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ObservationSignalFamily } from "./cardObservationCatalog.js";
|
|
2
|
+
import type { RepositoryContextSource } from "./scanNarrativeFacts.js";
|
|
3
|
+
import type { ScanResult } from "./types.js";
|
|
4
|
+
export type RepositoryContextFact = {
|
|
5
|
+
family: ObservationSignalFamily;
|
|
6
|
+
source: RepositoryContextSource;
|
|
7
|
+
refs?: {
|
|
8
|
+
packageNames?: string[];
|
|
9
|
+
metric?: Record<string, number>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Tier 3 structured signal refs for the intelligence layer (no catalog phrases).
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractRepositoryContextFacts(result: ScanResult): RepositoryContextFact[];
|
|
16
|
+
//# sourceMappingURL=extractRepositoryContextFacts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractRepositoryContextFacts.d.ts","sourceRoot":"","sources":["../src/extractRepositoryContextFacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,uBAAuB,EAC7B,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,uBAAuB,CAAC;IAChC,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACrE,CAAC;AAYF;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,UAAU,GACjB,qBAAqB,EAAE,CAiEzB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mapExplainReasonToCatalogPhrase, mapFindingToCatalogPhrase, mapGraphInsightsToCatalogPhrases, mapRecommendationToCatalogPhrase, matchSignalFamily, } from "./cardObservationCatalog.js";
|
|
2
|
+
function addFact(bucket, seen, fact) {
|
|
3
|
+
if (seen.has(fact.family))
|
|
4
|
+
return;
|
|
5
|
+
seen.add(fact.family);
|
|
6
|
+
bucket.push(fact);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Tier 3 structured signal refs for the intelligence layer (no catalog phrases).
|
|
10
|
+
*/
|
|
11
|
+
export function extractRepositoryContextFacts(result) {
|
|
12
|
+
const facts = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const gi = result.graphInsights;
|
|
15
|
+
if (gi && typeof gi === "object") {
|
|
16
|
+
for (const { family } of mapGraphInsightsToCatalogPhrases(gi)) {
|
|
17
|
+
const metric = {};
|
|
18
|
+
if (typeof gi.nodes === "number")
|
|
19
|
+
metric.nodes = gi.nodes;
|
|
20
|
+
if (typeof gi.maxDepth === "number")
|
|
21
|
+
metric.maxDepth = gi.maxDepth;
|
|
22
|
+
const vuln = Array.isArray(gi.vulnerable) ? gi.vulnerable : [];
|
|
23
|
+
const hotspots = Array.isArray(gi.hotspots) ? gi.hotspots : [];
|
|
24
|
+
addFact(facts, seen, {
|
|
25
|
+
family,
|
|
26
|
+
source: "graphInsights",
|
|
27
|
+
refs: {
|
|
28
|
+
packageNames: [
|
|
29
|
+
...vuln.map((v) => v.packageName),
|
|
30
|
+
...hotspots.map((h) => h.packageName),
|
|
31
|
+
].slice(0, 12),
|
|
32
|
+
metric: Object.keys(metric).length > 0 ? metric : undefined,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(result.explain?.reasons)) {
|
|
38
|
+
for (const reason of result.explain.reasons) {
|
|
39
|
+
const mapped = mapExplainReasonToCatalogPhrase(reason);
|
|
40
|
+
if (!mapped) {
|
|
41
|
+
const family = matchSignalFamily(`${reason.id} ${reason.title} ${reason.layer}`);
|
|
42
|
+
if (family) {
|
|
43
|
+
addFact(facts, seen, { family, source: "explain" });
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
addFact(facts, seen, { family: mapped.family, source: "explain" });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(result.findings)) {
|
|
51
|
+
for (const finding of result.findings) {
|
|
52
|
+
const mapped = mapFindingToCatalogPhrase(finding);
|
|
53
|
+
if (mapped) {
|
|
54
|
+
addFact(facts, seen, {
|
|
55
|
+
family: mapped.family,
|
|
56
|
+
source: "finding",
|
|
57
|
+
refs: { packageNames: [finding.packageName] },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(result.recommendations)) {
|
|
63
|
+
for (const rec of result.recommendations) {
|
|
64
|
+
const mapped = mapRecommendationToCatalogPhrase(rec);
|
|
65
|
+
if (mapped) {
|
|
66
|
+
addFact(facts, seen, { family: mapped.family, source: "finding" });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return facts;
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RepoIntelligence } from "../repoIntelligenceSchema.js";
|
|
2
|
+
/** Corpus-enabled runtime framework upgrade (auth/API paths). */
|
|
3
|
+
export declare const fixtureRepoIntelligenceFastify: RepoIntelligence;
|
|
4
|
+
/** Build-only toolchain change — no runtime paths. */
|
|
5
|
+
export declare const fixtureRepoIntelligenceTypescript: RepoIntelligence;
|
|
6
|
+
/** Empty corpus stub (engine-test-fixture default). */
|
|
7
|
+
export declare const fixtureRepoIntelligenceEmpty: RepoIntelligence;
|
|
8
|
+
/** Multi-package PR with distinct usage per package. */
|
|
9
|
+
export declare const fixtureRepoIntelligenceMultiPackage: RepoIntelligence;
|
|
10
|
+
//# sourceMappingURL=repoIntelligenceFixtures.d.ts.map
|
|
@@ -0,0 +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;AAEF,wDAAwD;AACxD,eAAO,MAAM,mCAAmC,EAAE,gBA4CjD,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Corpus-enabled runtime framework upgrade (auth/API paths). */
|
|
2
|
+
export const fixtureRepoIntelligenceFastify = {
|
|
3
|
+
packages: {
|
|
4
|
+
fastify: {
|
|
5
|
+
runtimeSurface: "runtime",
|
|
6
|
+
reachability: "on_runtime_paths",
|
|
7
|
+
usage: {
|
|
8
|
+
packageName: "fastify",
|
|
9
|
+
paths: ["apps/api/src/server.ts", "apps/api/src/routes/account.ts"],
|
|
10
|
+
criticalPaths: ["apps/api/src/middleware/auth.ts"],
|
|
11
|
+
areas: ["API", "Auth middleware"],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
blastRadius: {
|
|
16
|
+
level: "moderate",
|
|
17
|
+
factors: ["middleware_ordering", "route_handlers"],
|
|
18
|
+
changedPackageCount: 1,
|
|
19
|
+
},
|
|
20
|
+
applicationAreas: [
|
|
21
|
+
{ id: "api", label: "API routes" },
|
|
22
|
+
{ id: "auth", label: "Auth middleware" },
|
|
23
|
+
],
|
|
24
|
+
hotspots: [
|
|
25
|
+
{
|
|
26
|
+
packageName: "fastify",
|
|
27
|
+
source: "code",
|
|
28
|
+
paths: ["apps/api/src/middleware/auth.ts"],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
frameworks: ["fastify"],
|
|
32
|
+
};
|
|
33
|
+
/** Build-only toolchain change — no runtime paths. */
|
|
34
|
+
export const fixtureRepoIntelligenceTypescript = {
|
|
35
|
+
packages: {
|
|
36
|
+
typescript: {
|
|
37
|
+
runtimeSurface: "build",
|
|
38
|
+
reachability: "build_only",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
blastRadius: { level: "narrow", changedPackageCount: 1 },
|
|
42
|
+
applicationAreas: [],
|
|
43
|
+
};
|
|
44
|
+
/** Empty corpus stub (engine-test-fixture default). */
|
|
45
|
+
export const fixtureRepoIntelligenceEmpty = {
|
|
46
|
+
packages: {},
|
|
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");
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,15 @@ export * from "./formatCardExposureDisplay.js";
|
|
|
11
11
|
export * from "./cardObservationCatalog.js";
|
|
12
12
|
export * from "./deriveCardOperationalObservations.js";
|
|
13
13
|
export * from "./deriveCardDisplaySummary.js";
|
|
14
|
+
export * from "./scanNarrativeFacts.js";
|
|
15
|
+
export * from "./repoIntelligenceSchema.js";
|
|
16
|
+
export * from "./deriveScanNarrative.js";
|
|
17
|
+
export * from "./extractRepositoryContextFacts.js";
|
|
18
|
+
export * from "./presentScanCardSummary.js";
|
|
19
|
+
export * from "./narrativePresentation.js";
|
|
20
|
+
export * from "./normalizeGeneratedText.js";
|
|
21
|
+
export * from "./collectNarrativeWhyBullets.js";
|
|
22
|
+
export * from "./presentGitHubPrComment.js";
|
|
14
23
|
export * from "./scanCardSummary.js";
|
|
15
24
|
export * from "./deriveScanDetailRecommendations.js";
|
|
16
25
|
export * from "./deriveRiskScoreSummary.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,15 @@ export * from "./formatCardExposureDisplay.js";
|
|
|
11
11
|
export * from "./cardObservationCatalog.js";
|
|
12
12
|
export * from "./deriveCardOperationalObservations.js";
|
|
13
13
|
export * from "./deriveCardDisplaySummary.js";
|
|
14
|
+
export * from "./scanNarrativeFacts.js";
|
|
15
|
+
export * from "./repoIntelligenceSchema.js";
|
|
16
|
+
export * from "./deriveScanNarrative.js";
|
|
17
|
+
export * from "./extractRepositoryContextFacts.js";
|
|
18
|
+
export * from "./presentScanCardSummary.js";
|
|
19
|
+
export * from "./narrativePresentation.js";
|
|
20
|
+
export * from "./normalizeGeneratedText.js";
|
|
21
|
+
export * from "./collectNarrativeWhyBullets.js";
|
|
22
|
+
export * from "./presentGitHubPrComment.js";
|
|
14
23
|
export * from "./scanCardSummary.js";
|
|
15
24
|
export * from "./deriveScanDetailRecommendations.js";
|
|
16
25
|
export * from "./deriveRiskScoreSummary.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrativeParity.test.d.ts","sourceRoot":"","sources":["../src/narrativeParity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { deriveScanNarrative } from "./deriveScanNarrative.js";
|
|
3
|
+
import { buildPrCheckRunSummaryMarkdown } from "./prCheckRunPresentation.js";
|
|
4
|
+
import { deriveScanCardSummary } from "./scanCardSummary.js";
|
|
5
|
+
import { deriveScanDetailViewModel } from "./scanDetailViewModel.js";
|
|
6
|
+
import { presentGitHubPrComment } from "./presentGitHubPrComment.js";
|
|
7
|
+
import { fixtureRepoIntelligenceFastify } from "./fixtures/repoIntelligenceFixtures.js";
|
|
8
|
+
const ORIGIN = "https://app.example.com";
|
|
9
|
+
const SCAN_ID = "22222222-2222-4222-8222-222222222222";
|
|
10
|
+
describe("narrative parity across consumers", () => {
|
|
11
|
+
it("card, detail, GitHub share primary package and reachability story", () => {
|
|
12
|
+
const result = {
|
|
13
|
+
totalScore: 55,
|
|
14
|
+
layerScores: {
|
|
15
|
+
security: 10,
|
|
16
|
+
maintainability: 20,
|
|
17
|
+
ecosystem: 30,
|
|
18
|
+
upgradeImpact: 15,
|
|
19
|
+
},
|
|
20
|
+
findings: [],
|
|
21
|
+
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
22
|
+
changedPackages: ["fastify"],
|
|
23
|
+
analysisPreparation: { codeIntelligenceAvailable: true, warnings: [] },
|
|
24
|
+
repoIntelligence: fixtureRepoIntelligenceFastify,
|
|
25
|
+
decision: {
|
|
26
|
+
recommendation: "needs_review",
|
|
27
|
+
confidence: "medium",
|
|
28
|
+
reasoning: [],
|
|
29
|
+
},
|
|
30
|
+
insights: [
|
|
31
|
+
{
|
|
32
|
+
type: "usage_risk",
|
|
33
|
+
priority: "high",
|
|
34
|
+
confidence: "confirmed",
|
|
35
|
+
scope: "changed",
|
|
36
|
+
message: "Review fastify middleware ordering",
|
|
37
|
+
context: "apps/api/src/middleware/auth.ts",
|
|
38
|
+
remediation: "Run auth smoke tests",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
const card = deriveScanCardSummary(result, "done");
|
|
43
|
+
const detail = deriveScanDetailViewModel(result, {
|
|
44
|
+
scanId: SCAN_ID,
|
|
45
|
+
status: "done",
|
|
46
|
+
});
|
|
47
|
+
const checkRun = buildPrCheckRunSummaryMarkdown({
|
|
48
|
+
result,
|
|
49
|
+
scanId: SCAN_ID,
|
|
50
|
+
webAppOrigin: ORIGIN,
|
|
51
|
+
baseline: false,
|
|
52
|
+
});
|
|
53
|
+
const prComment = presentGitHubPrComment(deriveScanNarrative(result), result);
|
|
54
|
+
expect(card.changedPackagesDisplay).toContain("fastify");
|
|
55
|
+
expect(detail?.narrativeContext.changedPackagesDisplay).toContain("fastify");
|
|
56
|
+
expect(card.reachabilityLabel).toBe("On runtime paths");
|
|
57
|
+
expect(detail?.narrativeContext.reachabilityLabel).toBe("On runtime paths");
|
|
58
|
+
const asciiOnly = /^[\x00-\x7F]*$/;
|
|
59
|
+
expect(asciiOnly.test(checkRun)).toBe(true);
|
|
60
|
+
expect(asciiOnly.test(prComment)).toBe(true);
|
|
61
|
+
expect(checkRun.toLowerCase()).toContain("fastify");
|
|
62
|
+
expect(prComment.toLowerCase()).toContain("fastify");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ScanNarrativeFacts } from "./scanNarrativeFacts.js";
|
|
2
|
+
export type SummarizePackageUsageOptions = {
|
|
3
|
+
maxPaths?: number;
|
|
4
|
+
maxPackages?: number;
|
|
5
|
+
};
|
|
6
|
+
export type PackageUsageSummary = {
|
|
7
|
+
pathCount: number;
|
|
8
|
+
pathSamples: string[];
|
|
9
|
+
areaLabels: string[];
|
|
10
|
+
packageNames: string[];
|
|
11
|
+
};
|
|
12
|
+
export type ReachabilitySummary = {
|
|
13
|
+
kindLabel: string | null;
|
|
14
|
+
pathSamples: string[];
|
|
15
|
+
frameworks: string[];
|
|
16
|
+
};
|
|
17
|
+
export type BlastRadiusSummary = {
|
|
18
|
+
levelLabel: string | null;
|
|
19
|
+
factors: string[];
|
|
20
|
+
changedPackageCount: number | null;
|
|
21
|
+
};
|
|
22
|
+
export declare function selectPrimaryChangedPackage(facts: ScanNarrativeFacts): string | null;
|
|
23
|
+
export declare function formatChangedPackagesShort(facts: ScanNarrativeFacts, maxNames?: number): string | null;
|
|
24
|
+
export declare function formatChangedPackagesDetail(facts: ScanNarrativeFacts): string[];
|
|
25
|
+
export declare function summarizePackageUsage(facts: ScanNarrativeFacts, options?: SummarizePackageUsageOptions): PackageUsageSummary | null;
|
|
26
|
+
declare function labelRuntimeSurface(facts: ScanNarrativeFacts): string | null;
|
|
27
|
+
declare function labelReachabilityKind(facts: ScanNarrativeFacts): string | null;
|
|
28
|
+
declare function labelBlastRadiusLevel(facts: ScanNarrativeFacts): string | null;
|
|
29
|
+
export declare function summarizeReachability(facts: ScanNarrativeFacts, maxPaths?: number): ReachabilitySummary;
|
|
30
|
+
export declare function summarizeBlastRadius(facts: ScanNarrativeFacts, maxFactors?: number): BlastRadiusSummary;
|
|
31
|
+
export declare function summarizeHotspots(facts: ScanNarrativeFacts, max?: number): Array<{
|
|
32
|
+
packageName: string;
|
|
33
|
+
source: "code" | "graph";
|
|
34
|
+
pathSample: string | null;
|
|
35
|
+
}>;
|
|
36
|
+
export type SelectReviewerGuidanceOptions = {
|
|
37
|
+
scope?: "changed" | "all" | "any";
|
|
38
|
+
max?: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function selectReviewerGuidance(facts: ScanNarrativeFacts, options?: SelectReviewerGuidanceOptions): ScanNarrativeFacts["reviewerGuidance"];
|
|
41
|
+
export declare function composeVerificationPrompt(facts: ScanNarrativeFacts): string | null;
|
|
42
|
+
export declare function formatUsageSummaryLine(facts: ScanNarrativeFacts, maxPaths?: number): string | null;
|
|
43
|
+
export declare function formatBlastRadiusDetailLine(facts: ScanNarrativeFacts, maxFactors?: number): string | null;
|
|
44
|
+
export declare function formatFrameworksSummary(facts: ScanNarrativeFacts, max?: number): string | null;
|
|
45
|
+
export declare function composeContextLineFromFacts(facts: ScanNarrativeFacts, options?: {
|
|
46
|
+
includePathSample?: boolean;
|
|
47
|
+
maxAreas?: number;
|
|
48
|
+
}): string | null;
|
|
49
|
+
export { labelRuntimeSurface, labelReachabilityKind, labelBlastRadiusLevel };
|
|
50
|
+
//# sourceMappingURL=narrativePresentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrativePresentation.d.ts","sourceRoot":"","sources":["../src/narrativePresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGlE,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,GACxB,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,kBAAkB,EACzB,QAAQ,SAAI,GACX,MAAM,GAAG,IAAI,CAQf;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,GACxB,MAAM,EAAE,CAEV;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,4BAAiC,GACzC,mBAAmB,GAAG,IAAI,CA6B5B;AAED,iBAAS,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAIrE;AAED,iBAAS,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAIvE;AAED,iBAAS,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAIvE;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,kBAAkB,EACzB,QAAQ,SAAI,GACX,mBAAmB,CAUrB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,kBAAkB,EACzB,UAAU,SAAI,GACb,kBAAkB,CAOpB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,EACzB,GAAG,SAAI,GACN,KAAK,CAAC;IACP,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAMD;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,KAAK,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,6BAAkC,GAC1C,kBAAkB,CAAC,kBAAkB,CAAC,CASxC;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,kBAAkB,GACxB,MAAM,GAAG,IAAI,CAYf;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,EACzB,QAAQ,SAAI,GACX,MAAM,GAAG,IAAI,CAiBf;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,EACzB,UAAU,SAAI,GACb,MAAM,GAAG,IAAI,CAQf;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,kBAAkB,EACzB,GAAG,SAAI,GACN,MAAM,GAAG,IAAI,CAKf;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,MAAM,GAAG,IAAI,CA4Bf;AAED,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,CAAC"}
|