gitnexus 1.6.7-rc.4 → 1.6.7-rc.6
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/core/ingestion/languages/cpp/capture-side-channel.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/capture-side-channel.js +9 -2
- package/dist/core/ingestion/languages/cpp/captures.js +2 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.js +7 -0
- package/dist/core/ingestion/languages/cpp/member-lookup.d.ts +32 -0
- package/dist/core/ingestion/languages/cpp/member-lookup.js +461 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +4 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +17 -2
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +56 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/lbug/pool-adapter.js +14 -0
- package/dist/mcp/local/local-backend.js +124 -45
- package/package.json +1 -1
- package/scripts/bench/fts-evict-reload-rss.mjs +374 -0
- package/scripts/bench/fts-rss-verdict.mjs +105 -0
- package/scripts/install-duckdb-extension.mjs +22 -6
|
@@ -47,7 +47,7 @@ import type { ResolutionOutcomeRecorder } from '../resolution-outcome.js';
|
|
|
47
47
|
/** Subset of `ScopeResolver` consumed by this pass. Accepting the
|
|
48
48
|
* subset rather than the full provider keeps tests and partial
|
|
49
49
|
* refactors lighter — callers only need to populate what we read. */
|
|
50
|
-
type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'isSuperReceiverInContext' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule' | 'resolveQualifiedReceiverMember' | 'resolveThisViaEnclosingClass' | 'conversionRankFn' | 'constraintCompatibility' | 'isStaticOnly'>;
|
|
50
|
+
type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'isSuperReceiverInContext' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule' | 'resolveQualifiedReceiverMember' | 'resolveReceiverMember' | 'resolveThisViaEnclosingClass' | 'conversionRankFn' | 'constraintCompatibility' | 'isStaticOnly'>;
|
|
51
51
|
export declare function emitReceiverBoundCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, handledSites: Set<string>, provider: ReceiverBoundProviderSubset, index: WorkspaceResolutionIndex, model: SemanticModel, options?: {
|
|
52
52
|
readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
|
|
53
53
|
}): number;
|
|
@@ -274,6 +274,34 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
274
274
|
if (provider.resolveThisViaEnclosingClass === true && receiverName === 'this') {
|
|
275
275
|
const enclosingClass = findEnclosingClassDef(site.inScope, scopes);
|
|
276
276
|
if (enclosingClass !== undefined) {
|
|
277
|
+
const languageResolution = provider.resolveReceiverMember?.(enclosingClass, memberName, site, scopes, model);
|
|
278
|
+
if (languageResolution?.kind === 'ambiguous') {
|
|
279
|
+
options.recordResolutionOutcome?.({
|
|
280
|
+
kind: 'suppressed',
|
|
281
|
+
phase: 'receiver-bound-calls',
|
|
282
|
+
filePath: parsed.filePath,
|
|
283
|
+
name: site.name,
|
|
284
|
+
range: site.atRange,
|
|
285
|
+
reason: 'member-lookup-ambiguous',
|
|
286
|
+
candidateIds: languageResolution.candidateIds,
|
|
287
|
+
});
|
|
288
|
+
handledSites.add(siteKey);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (languageResolution?.kind === 'resolved') {
|
|
292
|
+
const memberDef = languageResolution.definition;
|
|
293
|
+
const reason = site.kind === 'write' || site.kind === 'read'
|
|
294
|
+
? site.kind
|
|
295
|
+
: memberDef.filePath !== parsed.filePath
|
|
296
|
+
? 'import-resolved'
|
|
297
|
+
: 'global';
|
|
298
|
+
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
|
|
299
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen, confidence, collapse);
|
|
300
|
+
if (ok)
|
|
301
|
+
emitted++;
|
|
302
|
+
handledSites.add(siteKey);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
277
305
|
const chain = [
|
|
278
306
|
enclosingClass.nodeId,
|
|
279
307
|
...scopes.methodDispatch.mroFor(enclosingClass.nodeId),
|
|
@@ -530,6 +558,34 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
530
558
|
ownerDef = resolveCompoundReceiverClass(receiverName, site.inScope, scopes, index, compoundOpts);
|
|
531
559
|
}
|
|
532
560
|
if (ownerDef !== undefined) {
|
|
561
|
+
const languageResolution = provider.resolveReceiverMember?.(ownerDef, memberName, site, scopes, model);
|
|
562
|
+
if (languageResolution?.kind === 'ambiguous') {
|
|
563
|
+
options.recordResolutionOutcome?.({
|
|
564
|
+
kind: 'suppressed',
|
|
565
|
+
phase: 'receiver-bound-calls',
|
|
566
|
+
filePath: parsed.filePath,
|
|
567
|
+
name: site.name,
|
|
568
|
+
range: site.atRange,
|
|
569
|
+
reason: 'member-lookup-ambiguous',
|
|
570
|
+
candidateIds: languageResolution.candidateIds,
|
|
571
|
+
});
|
|
572
|
+
handledSites.add(siteKey);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (languageResolution?.kind === 'resolved') {
|
|
576
|
+
const memberDef = languageResolution.definition;
|
|
577
|
+
const reason = site.kind === 'write' || site.kind === 'read'
|
|
578
|
+
? site.kind
|
|
579
|
+
: memberDef.filePath !== parsed.filePath
|
|
580
|
+
? 'import-resolved'
|
|
581
|
+
: 'global';
|
|
582
|
+
const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
|
|
583
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen, confidence, collapse);
|
|
584
|
+
if (ok)
|
|
585
|
+
emitted++;
|
|
586
|
+
handledSites.add(siteKey);
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
533
589
|
const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
|
|
534
590
|
let memberDef;
|
|
535
591
|
let ambiguous = false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Range } from '../../../_shared/index.js';
|
|
2
|
-
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
|
|
2
|
+
export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
|
|
3
3
|
export type ResolutionOutcome = {
|
|
4
4
|
readonly kind: 'resolved';
|
|
5
5
|
readonly targetId: string;
|
|
@@ -39,6 +39,17 @@ const MAX_POOL_SIZE = 5;
|
|
|
39
39
|
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
40
40
|
/** Max connections per repo (caps concurrent queries per repo) */
|
|
41
41
|
const MAX_CONNS_PER_REPO = 8;
|
|
42
|
+
// Behavior-neutral RSS tracing for the FTS evict→reload memory repro
|
|
43
|
+
// (gitnexus/scripts/bench/fts-evict-reload-rss.mjs). Two invariants keep it safe
|
|
44
|
+
// in the pool init/close hot path: it writes ONLY to stderr (stdout is the MCP
|
|
45
|
+
// JSON-RPC channel), and the GITNEXUS_POOL_RSS_TRACE gate makes it a no-op — one
|
|
46
|
+
// env-var compare per call, nothing else — unless a harness explicitly enables it.
|
|
47
|
+
function traceRss(event, repoId) {
|
|
48
|
+
if (process.env.GITNEXUS_POOL_RSS_TRACE !== '1')
|
|
49
|
+
return;
|
|
50
|
+
const rssMb = Math.round(process.memoryUsage().rss / (1024 * 1024));
|
|
51
|
+
process.stderr.write(`[pool-rss] ${event} repo=${repoId} pool=${pool.size} dbCache=${dbCache.size} rssMB=${rssMb}\n`);
|
|
52
|
+
}
|
|
42
53
|
let idleTimer = null;
|
|
43
54
|
// Stdout-capture state lives in `gitnexus/src/mcp/stdio-capture.ts` — a leaf
|
|
44
55
|
// module with zero non-`node:` imports. We re-export the same symbols here
|
|
@@ -166,6 +177,7 @@ function closeOne(repoId) {
|
|
|
166
177
|
// Isolate listener failures — teardown must complete.
|
|
167
178
|
}
|
|
168
179
|
}
|
|
180
|
+
traceRss('close', repoId);
|
|
169
181
|
}
|
|
170
182
|
/**
|
|
171
183
|
* Create a new Connection from a repo's Database.
|
|
@@ -511,6 +523,7 @@ async function doInitLbug(repoId, dbPath) {
|
|
|
511
523
|
closed: false,
|
|
512
524
|
});
|
|
513
525
|
ensureIdleTimer();
|
|
526
|
+
traceRss('init', repoId);
|
|
514
527
|
}
|
|
515
528
|
/**
|
|
516
529
|
* Initialize a pool entry from a pre-existing Database object.
|
|
@@ -565,6 +578,7 @@ export async function initLbugWithDb(repoId, existingDb, dbPath) {
|
|
|
565
578
|
closed: false,
|
|
566
579
|
});
|
|
567
580
|
ensureIdleTimer();
|
|
581
|
+
traceRss('init', repoId);
|
|
568
582
|
}
|
|
569
583
|
/**
|
|
570
584
|
* Checkout a connection from the pool.
|
|
@@ -143,6 +143,18 @@ function logQueryError(context, err) {
|
|
|
143
143
|
const msg = err instanceof Error ? err.message : String(err);
|
|
144
144
|
logger.error({ context, err: msg }, 'GitNexus query failed');
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* A "missing table/label/relation" prepare error is benign for the query tool's
|
|
148
|
+
* best-effort enrichment: a repo analyzed without processes or communities simply
|
|
149
|
+
* has no `Process`/`Community` tables, so the `STEP_IN_PROCESS` / `MEMBER_OF`
|
|
150
|
+
* enrichment queries fail to prepare. That is a normal configuration, NOT a
|
|
151
|
+
* degraded result — it must not raise the `partial` flag (which callers would
|
|
152
|
+
* then learn to ignore). Real failures (timeouts, locks, native faults) do.
|
|
153
|
+
*/
|
|
154
|
+
function isBenignMissingTableError(err) {
|
|
155
|
+
const msg = err instanceof Error ? err.message : String(err ?? '');
|
|
156
|
+
return /does not exist|no such (table|label|rel)|unknown (table|label)|not (defined|found)/i.test(msg);
|
|
157
|
+
}
|
|
146
158
|
const isReadOnlyDbError = (err) => {
|
|
147
159
|
// Walk the `cause` chain (bounded) so a wrapped read-only error (e.g. the
|
|
148
160
|
// pool adapter's `{ cause }` wrapper) is still detected here — this is the
|
|
@@ -925,61 +937,117 @@ export class LocalBackend {
|
|
|
925
937
|
timer.start('symbol_lookup');
|
|
926
938
|
const processMap = new Map();
|
|
927
939
|
const definitions = []; // standalone symbols not in any process
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
940
|
+
// Batch-fetch process participation, cohesion, and (optionally) content for
|
|
941
|
+
// ALL matched symbols in 2-3 graph queries instead of 2-3 *per symbol*. The
|
|
942
|
+
// previous per-symbol loop issued up to 3N sequential pool round-trips
|
|
943
|
+
// (searchLimit symbols × {STEP_IN_PROCESS, MEMBER_OF, content}); on a warm
|
|
944
|
+
// repo the IPC + query-setup overhead of those round-trips dominated query
|
|
945
|
+
// latency. Collapsing to `WHERE n.id IN $nodeIds` preserves identical output
|
|
946
|
+
// (the aggregation loop below is unchanged) while cutting the round-trips.
|
|
947
|
+
// Array params bind through the pool exactly as bm25Search's
|
|
948
|
+
// `WHERE n.id IN $nodeIds` already does. (Ported from gitnexus-enterprise
|
|
949
|
+
// PR #222 — N+1 → 2-3 batched queries.)
|
|
950
|
+
const nodeIds = merged.map(([, m]) => m.data?.nodeId).filter((id) => !!id);
|
|
951
|
+
const processRowsByNode = new Map();
|
|
952
|
+
const cohesionByNode = new Map();
|
|
953
|
+
const contentByNode = new Map();
|
|
954
|
+
// Set when a batched enrichment query throws a REAL failure (timeout, lock,
|
|
955
|
+
// native fault) — NOT the benign "no Process/Community table" case, which is
|
|
956
|
+
// a normal config (a repo analyzed without processes/communities) and must
|
|
957
|
+
// not raise a `partial` flag callers would learn to ignore. See
|
|
958
|
+
// isBenignMissingTableError + the response build below.
|
|
959
|
+
let enrichmentDegraded = false;
|
|
960
|
+
// Chunk the IN-list like the impact path (CHUNK_SIZE=100) so a large result
|
|
961
|
+
// set never builds an unbounded `IN` parameter. Default batch is
|
|
962
|
+
// processLimit*maxSymbolsPerProcess (≤ one chunk), but chunk for robustness.
|
|
963
|
+
const QUERY_CHUNK_SIZE = 100;
|
|
964
|
+
for (let i = 0; i < nodeIds.length; i += QUERY_CHUNK_SIZE) {
|
|
965
|
+
const ids = nodeIds.slice(i, i + QUERY_CHUNK_SIZE);
|
|
966
|
+
// Processes each symbol participates in. `n.id AS nodeId` is prepended as
|
|
967
|
+
// column 0 so rows from many symbols can be re-associated to their symbol.
|
|
941
968
|
try {
|
|
942
|
-
|
|
943
|
-
MATCH (n
|
|
944
|
-
|
|
945
|
-
|
|
969
|
+
const rows = await executeParameterized(repo.lbugPath, `
|
|
970
|
+
MATCH (n)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
971
|
+
WHERE n.id IN $nodeIds
|
|
972
|
+
RETURN n.id AS nodeId, p.id AS pid, p.label AS label, p.heuristicLabel AS heuristicLabel, p.processType AS processType, p.stepCount AS stepCount, r.step AS step
|
|
973
|
+
`, { nodeIds: ids });
|
|
974
|
+
for (const row of rows) {
|
|
975
|
+
const nid = row.nodeId ?? row[0];
|
|
976
|
+
let list = processRowsByNode.get(nid);
|
|
977
|
+
if (!list)
|
|
978
|
+
processRowsByNode.set(nid, (list = []));
|
|
979
|
+
list.push(row);
|
|
980
|
+
}
|
|
946
981
|
}
|
|
947
982
|
catch (e) {
|
|
948
983
|
logQueryError('query:process-lookup', e);
|
|
984
|
+
if (!isBenignMissingTableError(e))
|
|
985
|
+
enrichmentDegraded = true;
|
|
949
986
|
}
|
|
950
|
-
//
|
|
951
|
-
|
|
952
|
-
|
|
987
|
+
// Cluster membership + cohesion. Keep the FIRST community row per node to
|
|
988
|
+
// mirror the prior per-symbol `LIMIT 1` (each symbol keeps ITS community,
|
|
989
|
+
// not one community for the whole batch).
|
|
953
990
|
try {
|
|
954
|
-
const
|
|
955
|
-
MATCH (n
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
`, {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
991
|
+
const rows = await executeParameterized(repo.lbugPath, `
|
|
992
|
+
MATCH (n)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
|
993
|
+
WHERE n.id IN $nodeIds
|
|
994
|
+
RETURN n.id AS nodeId, c.cohesion AS cohesion, c.heuristicLabel AS module
|
|
995
|
+
`, { nodeIds: ids });
|
|
996
|
+
for (const row of rows) {
|
|
997
|
+
const nid = row.nodeId ?? row[0];
|
|
998
|
+
if (!cohesionByNode.has(nid)) {
|
|
999
|
+
cohesionByNode.set(nid, {
|
|
1000
|
+
cohesion: (row.cohesion ?? row[1]) || 0,
|
|
1001
|
+
module: row.module ?? row[2],
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
962
1004
|
}
|
|
963
1005
|
}
|
|
964
1006
|
catch (e) {
|
|
965
1007
|
logQueryError('query:cluster-info', e);
|
|
1008
|
+
if (!isBenignMissingTableError(e))
|
|
1009
|
+
enrichmentDegraded = true;
|
|
966
1010
|
}
|
|
967
|
-
// Optionally fetch content
|
|
968
|
-
let content;
|
|
1011
|
+
// Optionally fetch content for every matched symbol.
|
|
969
1012
|
if (includeContent) {
|
|
970
1013
|
try {
|
|
971
|
-
const
|
|
972
|
-
MATCH (n
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1014
|
+
const rows = await executeParameterized(repo.lbugPath, `
|
|
1015
|
+
MATCH (n)
|
|
1016
|
+
WHERE n.id IN $nodeIds
|
|
1017
|
+
RETURN n.id AS nodeId, n.content AS content
|
|
1018
|
+
`, { nodeIds: ids });
|
|
1019
|
+
for (const row of rows) {
|
|
1020
|
+
const nid = row.nodeId ?? row[0];
|
|
1021
|
+
contentByNode.set(nid, row.content ?? row[1]);
|
|
977
1022
|
}
|
|
978
1023
|
}
|
|
979
1024
|
catch (e) {
|
|
980
1025
|
logQueryError('query:content-fetch', e);
|
|
1026
|
+
if (!isBenignMissingTableError(e))
|
|
1027
|
+
enrichmentDegraded = true;
|
|
981
1028
|
}
|
|
982
1029
|
}
|
|
1030
|
+
}
|
|
1031
|
+
// Aggregation is unchanged from the per-symbol version — it now reads the
|
|
1032
|
+
// pre-fetched maps instead of issuing a query per symbol. Iterating `merged`
|
|
1033
|
+
// in the same (sorted) order preserves processMap insertion order, the
|
|
1034
|
+
// definitions order, and the item.score association exactly.
|
|
1035
|
+
for (const [_, item] of merged) {
|
|
1036
|
+
const sym = item.data;
|
|
1037
|
+
if (!sym.nodeId) {
|
|
1038
|
+
// File-level results go to definitions
|
|
1039
|
+
definitions.push({
|
|
1040
|
+
name: sym.name,
|
|
1041
|
+
type: sym.type || 'File',
|
|
1042
|
+
filePath: sym.filePath,
|
|
1043
|
+
});
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const processRows = processRowsByNode.get(sym.nodeId) ?? [];
|
|
1047
|
+
const coh = cohesionByNode.get(sym.nodeId);
|
|
1048
|
+
const cohesion = coh?.cohesion ?? 0;
|
|
1049
|
+
const module = coh?.module;
|
|
1050
|
+
const content = includeContent ? contentByNode.get(sym.nodeId) : undefined;
|
|
983
1051
|
const symbolEntry = {
|
|
984
1052
|
id: sym.nodeId,
|
|
985
1053
|
name: sym.name,
|
|
@@ -997,12 +1065,13 @@ export class LocalBackend {
|
|
|
997
1065
|
else {
|
|
998
1066
|
// Add to each process it belongs to
|
|
999
1067
|
for (const row of processRows) {
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
const
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1068
|
+
// Positional fallbacks shift +1 because `n.id AS nodeId` is column 0.
|
|
1069
|
+
const pid = row.pid ?? row[1];
|
|
1070
|
+
const label = row.label ?? row[2];
|
|
1071
|
+
const hLabel = row.heuristicLabel ?? row[3];
|
|
1072
|
+
const pType = row.processType ?? row[4];
|
|
1073
|
+
const stepCount = row.stepCount ?? row[5];
|
|
1074
|
+
const step = row.step ?? row[6];
|
|
1006
1075
|
if (!processMap.has(pid)) {
|
|
1007
1076
|
processMap.set(pid, {
|
|
1008
1077
|
id: pid,
|
|
@@ -1066,14 +1135,24 @@ export class LocalBackend {
|
|
|
1066
1135
|
timer.mark('wall', performance.now() - wallStart);
|
|
1067
1136
|
const timing = timer.summary();
|
|
1068
1137
|
logQueryTiming(searchQuery, timing);
|
|
1138
|
+
// Compose a single `warning` from all degraded conditions (FTS-missing
|
|
1139
|
+
// and/or a real enrichment failure) so neither overwrites the other, and
|
|
1140
|
+
// flag `partial` when enrichment was lost. Both are omitted on the clean
|
|
1141
|
+
// path, leaving the success-path response shape byte-identical.
|
|
1142
|
+
const warnings = [];
|
|
1143
|
+
if (!ftsUsed) {
|
|
1144
|
+
warnings.push('FTS indexes missing — keyword search degraded. Run: gitnexus analyze --repair-fts (or gitnexus analyze --force) to rebuild indexes.');
|
|
1145
|
+
}
|
|
1146
|
+
if (enrichmentDegraded) {
|
|
1147
|
+
warnings.push('Symbol enrichment partially failed — some process/cohesion/content data may be missing from these results (see server logs).');
|
|
1148
|
+
}
|
|
1069
1149
|
return {
|
|
1070
1150
|
processes,
|
|
1071
1151
|
process_symbols: dedupedSymbols,
|
|
1072
1152
|
definitions: definitions.slice(0, 20), // cap standalone definitions
|
|
1073
1153
|
timing,
|
|
1074
|
-
...(
|
|
1075
|
-
|
|
1076
|
-
}),
|
|
1154
|
+
...(warnings.length > 0 && { warning: warnings.join(' ') }),
|
|
1155
|
+
...(enrichmentDegraded && { partial: true }),
|
|
1077
1156
|
};
|
|
1078
1157
|
}
|
|
1079
1158
|
/**
|
package/package.json
CHANGED