gitnexus 1.6.10-rc.47 → 1.6.10-rc.49
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/README.md +2 -0
- package/dist/core/ingestion/languages/dart/captures.js +49 -2
- package/dist/core/ingestion/languages/dart/extension-type-preprocess.d.ts +7 -0
- package/dist/core/ingestion/languages/dart/extension-type-preprocess.js +30 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/utils/ast-helpers.js +1 -1
- package/dist/core/lbug/lbug-config.d.ts +9 -2
- package/dist/core/lbug/lbug-config.js +57 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -471,6 +471,8 @@ Configure the behavior with these environment variables:
|
|
|
471
471
|
| `GITNEXUS_FTS_CJK_SEGMENTATION` | `none`, `bigram` | `none` | `bigram` inserts overlapping character-bigram boundaries into Chinese/Japanese Han-ideograph spans in `content`/`description` before FTS indexing, so LadybugDB's space-only tokenizer can see sub-phrase word boundaries. Scoped to CJK Unified Ideographs only — Japanese Hiragana/Katakana and Korean Hangul are not currently segmented. Unlike `GITNEXUS_FTS_STEMMER`, this rewrites stored text — enabling it on an already-indexed repo requires a full `gitnexus analyze --force`; neither `--repair-fts` nor a plain incremental `analyze` applies it to previously-indexed files. Set the same value wherever `analyze` and search-serving processes (CLI query, MCP server, web server) run. |
|
|
472
472
|
| `GITNEXUS_COMMUNITY_ENGINE` | `graphology`, `icebug`, `auto` | `graphology` | Community-detection engine used during analyze. `graphology` uses the bundled default path. `icebug` and `auto` currently behave identically: both try the experimental Icebug CSR path and fall back to Graphology if the optional native module is unavailable or incompatible. |
|
|
473
473
|
| `GITNEXUS_WAL_CHECKPOINT_THRESHOLD` | integer `>= -1` | `67108864` (64 MiB) | LadybugDB WAL auto-checkpoint threshold during analyze (bytes). Auto-checkpoint remains enabled; `-1` keeps Ladybug's stock ~16 MiB. Larger thresholds reduce checkpoint frequency but increase the WAL size at rotation time — choose a smaller value on disk-constrained environments. |
|
|
474
|
+
| `GITNEXUS_LBUG_BUFFER_POOL_SIZE` | integer `>= 0` (bytes) | min(2 GiB, 80% RAM) | LadybugDB buffer-pool ceiling for every GitNexus database (analyze, MCP server, serve, group bridges). Bounded so a long-lived `gitnexus mcp` process or a large incremental `analyze` cannot grow toward LadybugDB's native 80%-of-RAM default and OOM the host (#2557). `0` restores that native unbounded default; invalid values warn and fall back to the default. |
|
|
475
|
+
| `GITNEXUS_LBUG_MAX_DB_SIZE` | positive integer (bytes) | `17179869184` (16 GiB) | Upper bound for a single LadybugDB database file. This is an mmap/disk-address-space ceiling, not a memory limit — it does not constrain the buffer pool (use `GITNEXUS_LBUG_BUFFER_POOL_SIZE` for that). Raise it when indexing genuinely huge monorepos; invalid values silently fall back to the default. |
|
|
474
476
|
|
|
475
477
|
```bash
|
|
476
478
|
# Offline/airgapped: never reach the network for extensions
|
|
@@ -36,6 +36,7 @@ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
|
36
36
|
import { encodeMarker } from '../../utils/heritage-marker.js';
|
|
37
37
|
import { DART_BUILT_INS } from './built-ins.js';
|
|
38
38
|
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
|
|
39
|
+
import { preprocessDartExtensionTypes } from './extension-type-preprocess.js';
|
|
39
40
|
const FUNCTION_DECL_TAGS = [
|
|
40
41
|
'@declaration.function',
|
|
41
42
|
'@declaration.method',
|
|
@@ -70,14 +71,15 @@ function dartLexicalFunctionOwner(input) {
|
|
|
70
71
|
return undefined;
|
|
71
72
|
}
|
|
72
73
|
export function emitDartScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
74
|
+
const parseText = preprocessDartExtensionTypes(sourceText);
|
|
73
75
|
let tree;
|
|
74
76
|
if (cachedTree !== undefined && cachedTree !== null) {
|
|
75
77
|
tree = cachedTree;
|
|
76
78
|
recordCacheHit();
|
|
77
79
|
}
|
|
78
80
|
else {
|
|
79
|
-
tree = parseSourceSafe(getDartParser(),
|
|
80
|
-
bufferSize: getTreeSitterBufferSize(
|
|
81
|
+
tree = parseSourceSafe(getDartParser(), parseText, undefined, {
|
|
82
|
+
bufferSize: getTreeSitterBufferSize(parseText),
|
|
81
83
|
});
|
|
82
84
|
recordCacheMiss();
|
|
83
85
|
}
|
|
@@ -161,6 +163,10 @@ export function emitDartScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
161
163
|
emitHeritage(node, out);
|
|
162
164
|
return;
|
|
163
165
|
}
|
|
166
|
+
if (node.type === 'extension_declaration') {
|
|
167
|
+
emitExtensionImplementsHeritage(node, out);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
164
170
|
});
|
|
165
171
|
out.push(...synthesizeCallableFlowCaptures(root, DART_CALLABLE_CAPTURE_OPTIONS));
|
|
166
172
|
return out;
|
|
@@ -475,6 +481,47 @@ function emitHeritage(classNode, out) {
|
|
|
475
481
|
emitHeritageMarkers(interfaces, 'implements', className, out);
|
|
476
482
|
}
|
|
477
483
|
}
|
|
484
|
+
function emitExtensionImplementsHeritage(extensionNode, out) {
|
|
485
|
+
const nameNode = extensionNode.childForFieldName('name');
|
|
486
|
+
if (nameNode === null)
|
|
487
|
+
return;
|
|
488
|
+
const bodyStart = extensionNode.text.indexOf('{');
|
|
489
|
+
const header = bodyStart === -1 ? extensionNode.text : extensionNode.text.slice(0, bodyStart);
|
|
490
|
+
const implementsIndex = header.indexOf('implements');
|
|
491
|
+
if (implementsIndex === -1)
|
|
492
|
+
return;
|
|
493
|
+
const className = nameNode.text;
|
|
494
|
+
const interfaces = header.slice(implementsIndex + 'implements'.length);
|
|
495
|
+
for (const rawInterface of splitTopLevelCommaList(interfaces)) {
|
|
496
|
+
const target = /^[ \t]*([A-Za-z_$][A-Za-z0-9_$]*)/.exec(rawInterface)?.[1];
|
|
497
|
+
if (target === undefined)
|
|
498
|
+
continue;
|
|
499
|
+
const payload = encodeMarker('heritage', ['implements', target, className]);
|
|
500
|
+
out.push({ '@import.heritage': syntheticCapture('@import.heritage', nameNode, payload) });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function splitTopLevelCommaList(text) {
|
|
504
|
+
const parts = [];
|
|
505
|
+
let start = 0;
|
|
506
|
+
let angleDepth = 0;
|
|
507
|
+
for (let i = 0; i < text.length; i++) {
|
|
508
|
+
const ch = text[i];
|
|
509
|
+
if (ch === '<') {
|
|
510
|
+
angleDepth++;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (ch === '>' && angleDepth > 0) {
|
|
514
|
+
angleDepth--;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (ch === ',' && angleDepth === 0) {
|
|
518
|
+
parts.push(text.slice(start, i));
|
|
519
|
+
start = i + 1;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
parts.push(text.slice(start));
|
|
523
|
+
return parts;
|
|
524
|
+
}
|
|
478
525
|
function emitHeritageMarkers(container, kind, className, out) {
|
|
479
526
|
for (let i = 0; i < container.namedChildCount; i++) {
|
|
480
527
|
const c = container.namedChild(i);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrites Dart 3.3 `extension type Name(Type value)` headers into ordinary
|
|
3
|
+
* extension headers before tree-sitter sees the source. The vendored grammar
|
|
4
|
+
* currently recovers these declarations through an ERROR subtree, while the
|
|
5
|
+
* existing Dart ingestion path already handles `extension Name on Type`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function preprocessDartExtensionTypes(sourceText: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DART_EXTENSION_TYPE_HEADER = /\b(extension)([ \t]+type[ \t]+(?:const[ \t]+)?)([A-Za-z_$][A-Za-z0-9_$]*)([ \t]*<[^()\r\n]*>)?([ \t]*)\(([^()\r\n]*)\)/g;
|
|
2
|
+
function representationType(representation) {
|
|
3
|
+
const trimmed = representation.trim();
|
|
4
|
+
const match = /^(.*\S)\s+[A-Za-z_$][A-Za-z0-9_$]*$/.exec(trimmed);
|
|
5
|
+
const typeName = match?.[1]?.trim();
|
|
6
|
+
return typeName && typeName.length > 0 ? typeName : null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Rewrites Dart 3.3 `extension type Name(Type value)` headers into ordinary
|
|
10
|
+
* extension headers before tree-sitter sees the source. The vendored grammar
|
|
11
|
+
* currently recovers these declarations through an ERROR subtree, while the
|
|
12
|
+
* existing Dart ingestion path already handles `extension Name on Type`.
|
|
13
|
+
*/
|
|
14
|
+
export function preprocessDartExtensionTypes(sourceText) {
|
|
15
|
+
return sourceText.replace(DART_EXTENSION_TYPE_HEADER, (match, extensionKeyword, extensionTypeGap, name, typeParameters, beforeRepresentation, representation) => {
|
|
16
|
+
const typeName = representationType(representation);
|
|
17
|
+
if (typeName === null)
|
|
18
|
+
return match;
|
|
19
|
+
const representationSpanLength = beforeRepresentation.length + representation.length + 2;
|
|
20
|
+
const rewrittenRepresentation = ` on ${typeName}`;
|
|
21
|
+
if (rewrittenRepresentation.length > representationSpanLength)
|
|
22
|
+
return match;
|
|
23
|
+
return (extensionKeyword +
|
|
24
|
+
' '.repeat(extensionTypeGap.length) +
|
|
25
|
+
name +
|
|
26
|
+
(typeParameters ?? '') +
|
|
27
|
+
rewrittenRepresentation +
|
|
28
|
+
' '.repeat(representationSpanLength - rewrittenRepresentation.length));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -29,6 +29,7 @@ import { createCallExtractor } from '../call-extractors/generic.js';
|
|
|
29
29
|
import { dartCallConfig } from '../call-extractors/configs/dart.js';
|
|
30
30
|
import { emitDartScopeCaptures, interpretDartImport, interpretDartTypeBinding, dartBindingScopeFor, dartImportOwningScope, dartReceiverBinding, dartMergeBindings, dartArityCompatibility, } from './dart/index.js';
|
|
31
31
|
import { DART_BUILT_INS } from './dart/built-ins.js';
|
|
32
|
+
import { preprocessDartExtensionTypes } from './dart/extension-type-preprocess.js';
|
|
32
33
|
/**
|
|
33
34
|
* Resolve the enclosing function from a `function_body` node by looking at its
|
|
34
35
|
* previous sibling. In Dart's tree-sitter grammar, function_signature and
|
|
@@ -100,6 +101,7 @@ export const dartProvider = defineLanguage({
|
|
|
100
101
|
},
|
|
101
102
|
],
|
|
102
103
|
treeSitterQueries: DART_QUERIES,
|
|
104
|
+
preprocessSource: preprocessDartExtensionTypes,
|
|
103
105
|
typeConfig: dartConfig,
|
|
104
106
|
exportChecker: dartExportChecker,
|
|
105
107
|
importResolver: createImportResolver(dartImportConfig),
|
|
@@ -209,7 +209,7 @@ export const CONTAINER_TYPE_TO_LABEL = {
|
|
|
209
209
|
record_declaration: 'Record',
|
|
210
210
|
protocol_declaration: 'Interface',
|
|
211
211
|
mixin_declaration: 'Mixin',
|
|
212
|
-
extension_declaration: '
|
|
212
|
+
extension_declaration: 'Class',
|
|
213
213
|
class: 'Class',
|
|
214
214
|
// Ruby `module` declarations map to `Trait` so they participate in the
|
|
215
215
|
// class-like type registry used by `lookupClassByName` / inheritance
|
|
@@ -39,8 +39,15 @@ export declare function resolveNativeSafeStorageDir(storagePath: string, subdir:
|
|
|
39
39
|
* `true` (0.16.0). Existing call sites that relied on the positional
|
|
40
40
|
* default must now pass `false` explicitly to preserve behaviour.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* 3. `bufferManagerSize` (not a 0.16.0 change, same pin-explicitly
|
|
43
|
+
* principle): `0` means "native default", and the native default buffer
|
|
44
|
+
* pool is 80% of physical RAM. A long-lived `gitnexus mcp` process or a
|
|
45
|
+
* large incremental analyze can balloon to that ceiling and OOM-kill the
|
|
46
|
+
* host session (#2557), so GitNexus pins an explicit bounded pool — see
|
|
47
|
+
* `resolveBufferManagerSize`.
|
|
48
|
+
*
|
|
49
|
+
* Putting these in one shared module guarantees every `new lbug.Database(...)`
|
|
50
|
+
* call site agrees on the same ceilings and behaviour.
|
|
44
51
|
*/
|
|
45
52
|
/**
|
|
46
53
|
* Upper bound for any single GitNexus LadybugDB file (graph index, group
|
|
@@ -227,8 +227,15 @@ export function resolveNativeSafeStorageDir(storagePath, subdir) {
|
|
|
227
227
|
* `true` (0.16.0). Existing call sites that relied on the positional
|
|
228
228
|
* default must now pass `false` explicitly to preserve behaviour.
|
|
229
229
|
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
230
|
+
* 3. `bufferManagerSize` (not a 0.16.0 change, same pin-explicitly
|
|
231
|
+
* principle): `0` means "native default", and the native default buffer
|
|
232
|
+
* pool is 80% of physical RAM. A long-lived `gitnexus mcp` process or a
|
|
233
|
+
* large incremental analyze can balloon to that ceiling and OOM-kill the
|
|
234
|
+
* host session (#2557), so GitNexus pins an explicit bounded pool — see
|
|
235
|
+
* `resolveBufferManagerSize`.
|
|
236
|
+
*
|
|
237
|
+
* Putting these in one shared module guarantees every `new lbug.Database(...)`
|
|
238
|
+
* call site agrees on the same ceilings and behaviour.
|
|
232
239
|
*/
|
|
233
240
|
/**
|
|
234
241
|
* Upper bound for any single GitNexus LadybugDB file (graph index, group
|
|
@@ -286,6 +293,53 @@ const resolveCheckpointThreshold = () => {
|
|
|
286
293
|
}
|
|
287
294
|
return DEFAULT_WAL_CHECKPOINT_THRESHOLD;
|
|
288
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* Default ceiling for the LadybugDB buffer pool in bytes (#2557).
|
|
298
|
+
*
|
|
299
|
+
* The pool is a page cache with eviction, so the ceiling trades throughput
|
|
300
|
+
* on very large working sets for a machine that stays alive: 2 GiB is
|
|
301
|
+
* ~40× the GitNexus self-index, while the native 80%-of-RAM default let a
|
|
302
|
+
* `detect_changes` call grow a 105 MiB on-disk index to 19.5 GiB RSS and
|
|
303
|
+
* OOM-kill the reporter's session. The `min` with 80% of `os.totalmem()`
|
|
304
|
+
* keeps sub-2.5-GiB machines at the native-equivalent bound (no regression
|
|
305
|
+
* there); the 64 MiB floor keeps tiny containers above any plausible
|
|
306
|
+
* native minimum pool size.
|
|
307
|
+
*/
|
|
308
|
+
const DEFAULT_BUFFER_POOL_CAP = 2 * 1024 * 1024 * 1024;
|
|
309
|
+
const BUFFER_POOL_FLOOR = 64 * 1024 * 1024;
|
|
310
|
+
const parseBufferPoolSize = (raw) => {
|
|
311
|
+
if (raw === undefined)
|
|
312
|
+
return undefined;
|
|
313
|
+
const normalized = raw.trim();
|
|
314
|
+
if (normalized.length === 0)
|
|
315
|
+
return undefined;
|
|
316
|
+
const parsed = Number(normalized);
|
|
317
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
318
|
+
return undefined;
|
|
319
|
+
return Math.floor(parsed);
|
|
320
|
+
};
|
|
321
|
+
const defaultBufferPoolSize = () => Math.min(DEFAULT_BUFFER_POOL_CAP, Math.max(BUFFER_POOL_FLOOR, Math.floor(os.totalmem() * 0.8)));
|
|
322
|
+
/**
|
|
323
|
+
* Resolve the `bufferManagerSize` passed to every `new lbug.Database(...)`.
|
|
324
|
+
* `GITNEXUS_LBUG_BUFFER_POOL_SIZE` (bytes) overrides the default; `0` is a
|
|
325
|
+
* deliberate escape hatch that restores LadybugDB's native unbounded
|
|
326
|
+
* 80%-of-RAM default. Resolved at call time (not module load) so tests can
|
|
327
|
+
* stub the env var and `os.totalmem`.
|
|
328
|
+
*/
|
|
329
|
+
const resolveBufferManagerSize = () => {
|
|
330
|
+
const raw = process.env.GITNEXUS_LBUG_BUFFER_POOL_SIZE;
|
|
331
|
+
if (raw === undefined)
|
|
332
|
+
return defaultBufferPoolSize();
|
|
333
|
+
const parsed = parseBufferPoolSize(raw);
|
|
334
|
+
if (parsed !== undefined)
|
|
335
|
+
return parsed;
|
|
336
|
+
// Non-empty but unparseable input: warn the operator and fall back —
|
|
337
|
+
// mirrors the GITNEXUS_WAL_CHECKPOINT_THRESHOLD env path above.
|
|
338
|
+
if (raw.trim().length > 0) {
|
|
339
|
+
logger.warn({ rawValue: raw, fallback: defaultBufferPoolSize() }, `Ignoring invalid GITNEXUS_LBUG_BUFFER_POOL_SIZE=${raw}; expected integer >= 0 (bytes; 0 restores the native 80%-of-RAM default); falling back to min(2 GiB, 80% of RAM).`);
|
|
340
|
+
}
|
|
341
|
+
return defaultBufferPoolSize();
|
|
342
|
+
};
|
|
289
343
|
/** Matches WAL corruption errors from the LadybugDB engine. */
|
|
290
344
|
const WAL_CORRUPTION_RE = /corrupt(ed)?\s+wal|invalid\s+wal\s+record|wal.*corrupt|checksum.*wal/i;
|
|
291
345
|
export const WAL_RECOVERY_SUGGESTION = 'WAL corruption detected. Run `gitnexus analyze --force` to rebuild the index.';
|
|
@@ -496,7 +550,7 @@ export const classifyDeleteAllError = (err) => {
|
|
|
496
550
|
};
|
|
497
551
|
export function createLbugDatabase(lbugModule, databasePath, options = {}) {
|
|
498
552
|
// .d.ts declares fewer args than the native constructor accepts.
|
|
499
|
-
return new lbugModule.Database(databasePath,
|
|
553
|
+
return new lbugModule.Database(databasePath, resolveBufferManagerSize(), // bufferManagerSize (#2557: default min(2 GiB, 80% RAM); GITNEXUS_LBUG_BUFFER_POOL_SIZE overrides; 0 restores the native 80%-of-RAM default)
|
|
500
554
|
false, // enableCompression (pinned for v0.16.0)
|
|
501
555
|
options.readOnly ?? false, LBUG_MAX_DB_SIZE, true, // autoCheckpoint (always on)
|
|
502
556
|
resolveCheckpointThreshold(), // checkpointThreshold (default 64 MiB; override with GITNEXUS_WAL_CHECKPOINT_THRESHOLD; -1 keeps Ladybug stock ~16 MiB)
|
package/package.json
CHANGED