gitnexus 1.6.10-rc.166 → 1.6.10-rc.167
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/class-extractors/configs/go.js +10 -5
- package/dist/core/ingestion/languages/go/interface-impls.js +7 -5
- package/dist/core/ingestion/languages/go/method-owners.js +24 -12
- package/dist/core/ingestion/languages/go/package-clause.d.ts +46 -0
- package/dist/core/ingestion/languages/go/package-clause.js +75 -0
- package/dist/core/ingestion/languages/go/package-siblings.js +7 -11
- package/dist/core/ingestion/languages/go/query.js +37 -3
- package/dist/core/ingestion/languages/go.js +9 -0
- package/dist/core/ingestion/scope-resolution/unresolved-receivers.d.ts +20 -0
- package/dist/core/ingestion/scope-resolution/unresolved-receivers.js +105 -14
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +9 -2
- package/dist/core/ingestion/utils/ast-helpers.js +20 -14
- package/dist/core/run-analyze.js +4 -2
- package/dist/storage/parse-cache.js +20 -1
- package/package.json +1 -1
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
3
|
export const goClassConfig = {
|
|
4
4
|
language: SupportedLanguages.Go,
|
|
5
|
-
|
|
5
|
+
// `type_spec`, not the enclosing `type_declaration` (#2837): one node per
|
|
6
|
+
// DECLARED TYPE, so a grouped `type ( A struct{…}; B struct{…} )` yields one
|
|
7
|
+
// each instead of one for the whole block. Every Go capture that reaches this
|
|
8
|
+
// extractor is anchored the same way, so the wrapper is never handed over —
|
|
9
|
+
// and accepting it would be worse than rejecting it, because picking one spec
|
|
10
|
+
// out of several with no reference point silently returns the FIRST type's
|
|
11
|
+
// name. A `null` here is loud; the wrong name is not.
|
|
12
|
+
typeDeclarationNodes: ['type_spec'],
|
|
6
13
|
fileScopeNodeTypes: ['package_clause'],
|
|
7
14
|
extractName(node) {
|
|
8
|
-
|
|
9
|
-
return typeSpec?.childForFieldName('name')?.text;
|
|
15
|
+
return node.childForFieldName('name')?.text;
|
|
10
16
|
},
|
|
11
17
|
extractType(node) {
|
|
12
|
-
const
|
|
13
|
-
const typeNode = typeSpec?.childForFieldName('type');
|
|
18
|
+
const typeNode = node.childForFieldName('type');
|
|
14
19
|
if (typeNode?.type === 'struct_type')
|
|
15
20
|
return 'Struct';
|
|
16
21
|
if (typeNode?.type === 'interface_type')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { simpleQualifiedName } from '../../scope-resolution/graph-bridge/ids.js';
|
|
2
2
|
import { resolveInheritanceBaseInScope } from '../../scope-resolution/scope/walkers.js';
|
|
3
|
+
import { goPackageDir } from './package-clause.js';
|
|
3
4
|
export function detectGoInterfaceImplementations(parsedFiles, _indexes, _model) {
|
|
4
5
|
return detectGoInterfaceImplementationsFromIndexes(buildDetectionIndexes(parsedFiles, _indexes));
|
|
5
6
|
}
|
|
@@ -582,12 +583,13 @@ function signatureContextForFile(parsed, indexes) {
|
|
|
582
583
|
importQualifiers,
|
|
583
584
|
};
|
|
584
585
|
}
|
|
586
|
+
/** The package directory, or `undefined` for a repo-root file.
|
|
587
|
+
*
|
|
588
|
+
* Shares `goPackageDir` with the package-clause resolver rather than repeating
|
|
589
|
+
* its normalize-and-slice (#2837): the two disagree only on how they spell "no
|
|
590
|
+
* directory", so the difference stays here, at the one call site that cares. */
|
|
585
591
|
function packageQualifierForFile(filePath) {
|
|
586
|
-
const
|
|
587
|
-
const slash = normalized.lastIndexOf('/');
|
|
588
|
-
if (slash === -1)
|
|
589
|
-
return undefined;
|
|
590
|
-
const packageDir = normalized.slice(0, slash);
|
|
592
|
+
const packageDir = goPackageDir(filePath);
|
|
591
593
|
return packageDir.length === 0 ? undefined : packageDir;
|
|
592
594
|
}
|
|
593
595
|
const GO_BUILTIN_TYPES = new Set([
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { logger } from '../../../logger.js';
|
|
1
2
|
import { isClassLike, populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
|
3
|
+
import { goPackageDir, inferGoPackageName } from './package-clause.js';
|
|
4
|
+
/** Bound on the sample of no-package-clause paths named in the warning. */
|
|
5
|
+
const SKIPPED_SAMPLE_CAP = 5;
|
|
2
6
|
/**
|
|
3
7
|
* Populate `ownerId` on Go Method defs by matching receiver types
|
|
4
8
|
* extracted from `@type-binding.self` captures against struct defs in
|
|
@@ -20,15 +24,32 @@ export function populateGoOwners(parsed) {
|
|
|
20
24
|
}
|
|
21
25
|
export function populateGoWorkspaceOwners(parsedFiles, ctx) {
|
|
22
26
|
const filesByPackage = new Map();
|
|
27
|
+
// A file with no resolvable package clause is dropped from ownership
|
|
28
|
+
// resolution entirely — its methods never attach to a struct declared in a
|
|
29
|
+
// sibling file. That used to be a bare `continue` with no trace, which is the
|
|
30
|
+
// same false-safe silence #2813 was filed about. Report it once, bounded
|
|
31
|
+
// (mirrors the fan-out-cap warning in scope-resolution/pipeline/run.ts).
|
|
32
|
+
let skippedCount = 0;
|
|
33
|
+
const skippedSample = [];
|
|
23
34
|
for (const parsed of parsedFiles) {
|
|
24
|
-
const pkgName =
|
|
25
|
-
if (pkgName === null)
|
|
35
|
+
const pkgName = inferGoPackageName(ctx.fileContents.get(parsed.filePath) ?? '');
|
|
36
|
+
if (pkgName === null) {
|
|
37
|
+
// Count everything, retain only the sample — a misrouted vendored tree
|
|
38
|
+
// would otherwise accumulate one path reference per file to print five.
|
|
39
|
+
skippedCount += 1;
|
|
40
|
+
if (skippedSample.length < SKIPPED_SAMPLE_CAP)
|
|
41
|
+
skippedSample.push(parsed.filePath);
|
|
26
42
|
continue;
|
|
27
|
-
|
|
43
|
+
}
|
|
44
|
+
const key = `${goPackageDir(parsed.filePath)}\0${pkgName}`;
|
|
28
45
|
const bucket = filesByPackage.get(key) ?? [];
|
|
29
46
|
bucket.push(parsed);
|
|
30
47
|
filesByPackage.set(key, bucket);
|
|
31
48
|
}
|
|
49
|
+
if (skippedCount > 0) {
|
|
50
|
+
logger.warn({ skippedFiles: skippedCount, sample: skippedSample }, 'go: files with no resolvable package clause were excluded from method-owner ' +
|
|
51
|
+
'resolution (their methods cannot attach to structs declared in sibling files)');
|
|
52
|
+
}
|
|
32
53
|
for (const bucket of filesByPackage.values()) {
|
|
33
54
|
populateGoOwnersInPackage(bucket);
|
|
34
55
|
}
|
|
@@ -97,12 +118,3 @@ function populateGoOwnersInPackage(parsedFiles) {
|
|
|
97
118
|
}
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
|
-
function inferPackageName(sourceText) {
|
|
101
|
-
const match = sourceText.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_]*)/m);
|
|
102
|
-
return match?.[1] ?? null;
|
|
103
|
-
}
|
|
104
|
-
function packageDir(filePath) {
|
|
105
|
-
const normalized = filePath.replace(/\\/g, '/');
|
|
106
|
-
const idx = normalized.lastIndexOf('/');
|
|
107
|
-
return idx === -1 ? '' : normalized.slice(0, idx);
|
|
108
|
-
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go package-clause resolution — the single derivation of a file's package
|
|
3
|
+
* identity (#2837).
|
|
4
|
+
*
|
|
5
|
+
* Both Go passes that bucket files by package (`populateGoWorkspaceOwners` and
|
|
6
|
+
* `populateGoPackageSiblings`) previously carried their own byte-identical copy
|
|
7
|
+
* of this, spelled as one unanchored multiline regex:
|
|
8
|
+
*
|
|
9
|
+
* sourceText.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_]*)/m)
|
|
10
|
+
*
|
|
11
|
+
* With the `m` flag that matches the first line ANYWHERE in the file starting
|
|
12
|
+
* with `package <ident>` — comment bodies included. Measured against that exact
|
|
13
|
+
* expression: a header comment containing `package legacy_notes kept for
|
|
14
|
+
* history` yields `legacy_notes`, and an indented ` package helper old name`
|
|
15
|
+
* yields `helper`. A file that mis-infers its own package gets a bucket key no
|
|
16
|
+
* sibling shares, so it is isolated in BOTH passes: its methods never attach to
|
|
17
|
+
* structs declared in sibling files, and it exchanges no same-package bindings.
|
|
18
|
+
* Every field-receiver call in it then resolves to nothing, silently — the same
|
|
19
|
+
* per-file signature #2837 reported.
|
|
20
|
+
*
|
|
21
|
+
* The Go spec makes the correct rule exact rather than heuristic: a source
|
|
22
|
+
* file's first non-comment, non-blank token is `package`. So skip the leading
|
|
23
|
+
* run of whitespace and comments, then require the very next token to be the
|
|
24
|
+
* clause. Anything else is `null` — a truncated read, a misrouted non-Go file,
|
|
25
|
+
* an unparseable header — reported by the caller rather than guessed at.
|
|
26
|
+
*
|
|
27
|
+
* ONE rule governs every leniency below (the `\s+` separator, the shebang skip,
|
|
28
|
+
* CR-only line endings): a file this returns `null` for is dropped from BOTH
|
|
29
|
+
* passes, so refusing a shape the previous regex accepted is a silent
|
|
30
|
+
* regression, not a principled tightening. Be no stricter than the grammar.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The package name declared by this Go source text, or `null` when its first
|
|
34
|
+
* real token is not a package clause.
|
|
35
|
+
*
|
|
36
|
+
* Only the leading run before the clause is skipped — deliberately NOT a
|
|
37
|
+
* whole-file comment strip, which would be O(file) on every Go file and would
|
|
38
|
+
* also have to model string literals to stay correct.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inferGoPackageName(sourceText: string): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The directory half of a Go package key. Go package identity is
|
|
43
|
+
* directory-scoped, so repeated `package main` directories must not see each
|
|
44
|
+
* other's unqualified names.
|
|
45
|
+
*/
|
|
46
|
+
export declare function goPackageDir(filePath: string): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Go package-clause resolution — the single derivation of a file's package
|
|
3
|
+
* identity (#2837).
|
|
4
|
+
*
|
|
5
|
+
* Both Go passes that bucket files by package (`populateGoWorkspaceOwners` and
|
|
6
|
+
* `populateGoPackageSiblings`) previously carried their own byte-identical copy
|
|
7
|
+
* of this, spelled as one unanchored multiline regex:
|
|
8
|
+
*
|
|
9
|
+
* sourceText.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_]*)/m)
|
|
10
|
+
*
|
|
11
|
+
* With the `m` flag that matches the first line ANYWHERE in the file starting
|
|
12
|
+
* with `package <ident>` — comment bodies included. Measured against that exact
|
|
13
|
+
* expression: a header comment containing `package legacy_notes kept for
|
|
14
|
+
* history` yields `legacy_notes`, and an indented ` package helper old name`
|
|
15
|
+
* yields `helper`. A file that mis-infers its own package gets a bucket key no
|
|
16
|
+
* sibling shares, so it is isolated in BOTH passes: its methods never attach to
|
|
17
|
+
* structs declared in sibling files, and it exchanges no same-package bindings.
|
|
18
|
+
* Every field-receiver call in it then resolves to nothing, silently — the same
|
|
19
|
+
* per-file signature #2837 reported.
|
|
20
|
+
*
|
|
21
|
+
* The Go spec makes the correct rule exact rather than heuristic: a source
|
|
22
|
+
* file's first non-comment, non-blank token is `package`. So skip the leading
|
|
23
|
+
* run of whitespace and comments, then require the very next token to be the
|
|
24
|
+
* clause. Anything else is `null` — a truncated read, a misrouted non-Go file,
|
|
25
|
+
* an unparseable header — reported by the caller rather than guessed at.
|
|
26
|
+
*
|
|
27
|
+
* ONE rule governs every leniency below (the `\s+` separator, the shebang skip,
|
|
28
|
+
* CR-only line endings): a file this returns `null` for is dropped from BOTH
|
|
29
|
+
* passes, so refusing a shape the previous regex accepted is a silent
|
|
30
|
+
* regression, not a principled tightening. Be no stricter than the grammar.
|
|
31
|
+
*/
|
|
32
|
+
/** Leading trivia: whitespace, `//` lines, block comments. Sticky. */
|
|
33
|
+
const LEADING_TRIVIA = /(?:\s+|\/\/[^\n\r]*|\/\*[\s\S]*?\*\/)*/y;
|
|
34
|
+
/** The clause itself, anchored at the first non-trivia byte. `\s+` (not
|
|
35
|
+
* `[ \t]+`) because Go separates tokens by any whitespace: `package\nmain` is
|
|
36
|
+
* legal and tree-sitter parses it without error. */
|
|
37
|
+
const PACKAGE_CLAUSE = /package\s+([A-Za-z_][A-Za-z0-9_]*)/y;
|
|
38
|
+
/**
|
|
39
|
+
* The package name declared by this Go source text, or `null` when its first
|
|
40
|
+
* real token is not a package clause.
|
|
41
|
+
*
|
|
42
|
+
* Only the leading run before the clause is skipped — deliberately NOT a
|
|
43
|
+
* whole-file comment strip, which would be O(file) on every Go file and would
|
|
44
|
+
* also have to model string literals to stay correct.
|
|
45
|
+
*/
|
|
46
|
+
export function inferGoPackageName(sourceText) {
|
|
47
|
+
let i = 0;
|
|
48
|
+
// A leading `#!` line: `gorun`-style scripts carry one. Only a FIRST-line
|
|
49
|
+
// `#!` is skipped; a `#` anywhere else still ends the scan.
|
|
50
|
+
if (sourceText.startsWith('#!')) {
|
|
51
|
+
const eol = sourceText.search(/[\n\r]/);
|
|
52
|
+
if (eol === -1)
|
|
53
|
+
return null;
|
|
54
|
+
i = eol + 1;
|
|
55
|
+
}
|
|
56
|
+
// Whitespace (`\s` covers the BOM and every line ending), `//` lines and block
|
|
57
|
+
// comments — the run a Go file may carry before its clause. Sticky, so the
|
|
58
|
+
// header is skipped in place without slicing a copy of the file. An
|
|
59
|
+
// unterminated `/*` or a `//` running to EOF simply leaves `lastIndex` where
|
|
60
|
+
// the clause cannot match, so neither needs its own early return.
|
|
61
|
+
LEADING_TRIVIA.lastIndex = i;
|
|
62
|
+
LEADING_TRIVIA.exec(sourceText);
|
|
63
|
+
PACKAGE_CLAUSE.lastIndex = LEADING_TRIVIA.lastIndex;
|
|
64
|
+
return PACKAGE_CLAUSE.exec(sourceText)?.[1] ?? null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The directory half of a Go package key. Go package identity is
|
|
68
|
+
* directory-scoped, so repeated `package main` directories must not see each
|
|
69
|
+
* other's unqualified names.
|
|
70
|
+
*/
|
|
71
|
+
export function goPackageDir(filePath) {
|
|
72
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
73
|
+
const idx = normalized.lastIndexOf('/');
|
|
74
|
+
return idx === -1 ? '' : normalized.slice(0, idx);
|
|
75
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expandGoDotImports } from './expand-wildcards.js';
|
|
2
|
+
import { goPackageDir, inferGoPackageName } from './package-clause.js';
|
|
2
3
|
/**
|
|
3
4
|
* O(n²×d) where n = files per package, d = defs per file.
|
|
4
5
|
* Acceptable for V1 since Go packages are typically small (< 20 files).
|
|
@@ -17,9 +18,13 @@ export function populateGoPackageSiblings(parsedFiles, indexes, ctx) {
|
|
|
17
18
|
// must not see each other's unqualified names.
|
|
18
19
|
const packageByFile = new Map();
|
|
19
20
|
for (const parsed of nonTestFiles) {
|
|
20
|
-
|
|
21
|
+
// Same derivation as `populateGoWorkspaceOwners` — one shared resolver, so
|
|
22
|
+
// the two passes cannot disagree about a file's package (#2837). The
|
|
23
|
+
// no-clause case is reported there; warning twice for one fact would be
|
|
24
|
+
// noise.
|
|
25
|
+
const pkgName = inferGoPackageName(ctx.fileContents.get(parsed.filePath) ?? '');
|
|
21
26
|
if (pkgName !== null) {
|
|
22
|
-
packageByFile.set(parsed.filePath, `${
|
|
27
|
+
packageByFile.set(parsed.filePath, `${goPackageDir(parsed.filePath)}\0${pkgName}`);
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
const filesByPackage = new Map();
|
|
@@ -60,15 +65,6 @@ export function populateGoPackageSiblings(parsedFiles, indexes, ctx) {
|
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
|
-
function inferPackageName(sourceText) {
|
|
64
|
-
const match = sourceText.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_]*)/m);
|
|
65
|
-
return match?.[1] ?? null;
|
|
66
|
-
}
|
|
67
|
-
function packageDir(filePath) {
|
|
68
|
-
const normalized = filePath.replace(/\\/g, '/');
|
|
69
|
-
const idx = normalized.lastIndexOf('/');
|
|
70
|
-
return idx === -1 ? '' : normalized.slice(0, idx);
|
|
71
|
-
}
|
|
72
68
|
function getAugmentationBucket(augmentations, scopeId, name) {
|
|
73
69
|
let scopeBindings = augmentations.get(scopeId);
|
|
74
70
|
if (scopeBindings === undefined) {
|
|
@@ -3,9 +3,33 @@ import Go from 'tree-sitter-go';
|
|
|
3
3
|
const GO_SCOPE_QUERY = `
|
|
4
4
|
;; Scopes
|
|
5
5
|
(source_file) @scope.module
|
|
6
|
+
;; One Class scope per DECLARED TYPE, not per declaration (#2837).
|
|
7
|
+
;;
|
|
8
|
+
;; Capturing the type_declaration made a grouped declaration
|
|
9
|
+
;; type (
|
|
10
|
+
;; Decoy struct { ... }
|
|
11
|
+
;; PickService struct { ... }
|
|
12
|
+
;; )
|
|
13
|
+
;; a SINGLE Class scope owning every struct in the block. Downstream that scope
|
|
14
|
+
;; can name only one owner -- buildWorkspaceResolutionIndex keeps the first
|
|
15
|
+
;; class-like def it finds -- so the block's structs lost their field type
|
|
16
|
+
;; bindings, typeOfMemberOnClass found no scope, the compound-receiver fold
|
|
17
|
+
;; declined, and every s.field.Method() site in the file emitted nothing at
|
|
18
|
+
;; all. Silent, per-file, and independent of file size: exactly the split
|
|
19
|
+
;; reported in #2837 that #2829's global fixes could not explain. Measured on
|
|
20
|
+
;; the go-grouped-type-decl fixture: EVERY struct in a grouped block lost its
|
|
21
|
+
;; edges, the first one included, and grouped interface blocks produced no
|
|
22
|
+
;; IMPLEMENTS edges at all.
|
|
23
|
+
;;
|
|
24
|
+
;; type_spec is the node Go's own grammar gives one declared type, so one
|
|
25
|
+
;; capture per type_spec is the granularity the rest of the pipeline already
|
|
26
|
+
;; assumes. A plain single-type declaration is unaffected in count -- only its
|
|
27
|
+
;; scope range narrows, from the type keyword to the name.
|
|
28
|
+
;;
|
|
29
|
+
;; NOTE: this string is a JS template literal. Backticks are a syntax error.
|
|
6
30
|
(type_declaration
|
|
7
31
|
(type_spec
|
|
8
|
-
type: [(struct_type) (interface_type)])
|
|
32
|
+
type: [(struct_type) (interface_type)]) @scope.class)
|
|
9
33
|
(function_declaration) @scope.function
|
|
10
34
|
(method_declaration) @scope.function
|
|
11
35
|
(func_literal) @scope.function
|
|
@@ -21,14 +45,24 @@ const GO_SCOPE_QUERY = `
|
|
|
21
45
|
(communication_case) @scope.block
|
|
22
46
|
|
|
23
47
|
;; Declarations — struct
|
|
48
|
+
;;
|
|
49
|
+
;; Anchored on the type_spec, in lockstep with @scope.class above (#2837). Both
|
|
50
|
+
;; captures MUST name the same node: the def node and the class-scope node are
|
|
51
|
+
;; paired by range, so anchoring the def on the enclosing type_declaration while
|
|
52
|
+
;; the scope sits on the type_spec leaves the def strictly larger than its own
|
|
53
|
+
;; scope and NOTHING is owned -- measured as every Go field-receiver edge in the
|
|
54
|
+
;; fixture disappearing, plain declarations included. Keeping both on
|
|
55
|
+
;; type_declaration is the original bug: a grouped block gave both structs the
|
|
56
|
+
;; same capture node, so one silently displaced the other.
|
|
24
57
|
(type_declaration
|
|
25
58
|
(type_spec name: (type_identifier) @declaration.name
|
|
26
|
-
type: (struct_type))
|
|
59
|
+
type: (struct_type)) @declaration.struct)
|
|
27
60
|
|
|
28
61
|
;; Declarations — interface
|
|
62
|
+
;; Same lockstep requirement as @declaration.struct above (#2837).
|
|
29
63
|
(type_declaration
|
|
30
64
|
(type_spec name: (type_identifier) @declaration.name
|
|
31
|
-
type: (interface_type))
|
|
65
|
+
type: (interface_type)) @declaration.interface)
|
|
32
66
|
|
|
33
67
|
;; Declarations — function
|
|
34
68
|
(function_declaration
|
|
@@ -132,6 +132,15 @@ export const goProvider = defineLanguage({
|
|
|
132
132
|
descriptionExtractor: createLeadingDocDescriptionExtractor({
|
|
133
133
|
lineCommentPrefixes: ['//'],
|
|
134
134
|
lineDirectivePrefixes: ['//go:', '// +build', '//nolint', '//line'],
|
|
135
|
+
// #2837: Go type declarations anchor on the `type_spec`, whose
|
|
136
|
+
// `previousNamedSibling` is null for the ordinary `type T struct{…}` form —
|
|
137
|
+
// the godoc comment is a sibling of the enclosing `type_declaration`, one
|
|
138
|
+
// level up. Without this the doc silently vanished for EVERY Go struct and
|
|
139
|
+
// interface (measured: pre-#2837 `desc=YES`, post `none`), taking #2270's
|
|
140
|
+
// description out of both the embedding header and the FTS column.
|
|
141
|
+
// Functions/methods are unaffected — they anchor on their own declaration
|
|
142
|
+
// node, which does carry the comment as a previous sibling.
|
|
143
|
+
wrapperNodeTypes: ['type_declaration'],
|
|
135
144
|
}),
|
|
136
145
|
builtInNames: GO_BUILT_INS,
|
|
137
146
|
// ── RFC #909 Ring 3: scope-based resolution hooks ──────────
|
|
@@ -87,3 +87,23 @@ export declare function lookupUnresolvedCallCount(summary: UnresolvedReceiverSum
|
|
|
87
87
|
* friends would otherwise return a function.
|
|
88
88
|
*/
|
|
89
89
|
export declare function lookupExternalCallCount(summary: UnresolvedReceiverSummary | undefined, symName: string): number | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Report which FILES lost the most call sites to an untyped receiver (#2837).
|
|
92
|
+
*
|
|
93
|
+
* `summarizeUnresolvedReceivers` persists the same drops keyed by member NAME,
|
|
94
|
+
* capped at 500 distinct names, and discards `filePath` — deliberately, because
|
|
95
|
+
* its consumer (`impact()`'s exact-vs-lower-bound verdict) asks "is this
|
|
96
|
+
* symbol's caller count trustworthy", a question about names.
|
|
97
|
+
*
|
|
98
|
+
* That leaves "why does THIS file resolve nothing while its sibling resolves
|
|
99
|
+
* everything" unanswerable from any artifact, which is exactly what #2837 asked
|
|
100
|
+
* for and could not run: comparing the drop records of two files with identical
|
|
101
|
+
* declared shapes separates "receiver never typed" from "typed but no edge
|
|
102
|
+
* emitted", and narrows a per-file split in one run instead of a bisect.
|
|
103
|
+
*
|
|
104
|
+
* A log line rather than a persisted field because nothing queries it — a
|
|
105
|
+
* persisted `byFile` map would be an unread field carrying its own cap and
|
|
106
|
+
* truncation semantics. When a consumer appears, `UnresolvedReceiverSummary` is
|
|
107
|
+
* already a `RepoMeta` field and can carry it with no schema change.
|
|
108
|
+
*/
|
|
109
|
+
export declare function logUnresolvedReceiverFiles(outcomes: readonly ResolutionOutcome[]): void;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* report its caller count as a lower bound, while a query about an unrelated
|
|
13
13
|
* symbol stays exact.
|
|
14
14
|
*/
|
|
15
|
+
import { createLogger } from '../../logger.js';
|
|
16
|
+
import { compareCodeUnits } from '../../../lib/utils.js';
|
|
15
17
|
/** Cap on distinct member names persisted. Well above what a real repo
|
|
16
18
|
* produces (the whole point of the signal is that drops are the exception),
|
|
17
19
|
* but bounded so a pathological repo cannot grow the metadata file without
|
|
@@ -30,11 +32,42 @@ export const MAX_UNRESOLVED_RECEIVER_MEMBERS = 500;
|
|
|
30
32
|
* `omitted` is the number of distinct names past the cap, so the caller can
|
|
31
33
|
* report truncation rather than silently losing entries.
|
|
32
34
|
*/
|
|
33
|
-
function rankAndCap(counts) {
|
|
34
|
-
const ranked = [...counts.entries()].sort(
|
|
35
|
-
|
|
35
|
+
function rankAndCap(counts, cap = MAX_UNRESOLVED_RECEIVER_MEMBERS) {
|
|
36
|
+
const ranked = [...counts.entries()].sort(
|
|
37
|
+
// `compareCodeUnits`, not `localeCompare` (#2787). The tiebreak feeds the
|
|
38
|
+
// `.slice()` below, so locale-sensitive collation would decide WHICH
|
|
39
|
+
// entries survive the cap, not merely how they are listed — and ICU order
|
|
40
|
+
// varies by platform and ICU build, so two runs over one repo could persist
|
|
41
|
+
// different sets. Key-based lookup is unaffected either way.
|
|
42
|
+
([aName, aCount], [bName, bCount]) => bCount - aCount || compareCodeUnits(aName, bName));
|
|
43
|
+
const kept = ranked.slice(0, cap);
|
|
36
44
|
return { kept, omitted: ranked.length - kept.length };
|
|
37
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A `receiver-unresolved` drop at a CALL site.
|
|
48
|
+
*
|
|
49
|
+
* ONE predicate, shared by the persisted summary and the per-file diagnostic —
|
|
50
|
+
* the same contract `rankAndCap` states for its comparator. If the two ever
|
|
51
|
+
* disagreed, the diagnostic would rank a different population than the artifact
|
|
52
|
+
* it exists to explain.
|
|
53
|
+
*
|
|
54
|
+
* Call sites only: Case 0's recorder gates on the receiver's punctuation, not on
|
|
55
|
+
* what the reference IS, so property reads (`d.source.kind`) and writes
|
|
56
|
+
* (`x.argtypes = [...]`) arrive alongside lost method calls — measured at 25 of
|
|
57
|
+
* 124 drops on the fixture corpus. Counting them made the consumer's "N call
|
|
58
|
+
* sites invoking X were dropped" literally false. A missing `siteKind` counts as
|
|
59
|
+
* a call: the only emitter always sets it, and erring toward `lower-bound` is
|
|
60
|
+
* the safe direction for an epistemic signal.
|
|
61
|
+
*
|
|
62
|
+
* Receiver ORIGIN is deliberately NOT decided here — the summary routes external
|
|
63
|
+
* drops into their own bucket while the diagnostic excludes them, and collapsing
|
|
64
|
+
* that choice into this predicate would take it away from both callers.
|
|
65
|
+
*/
|
|
66
|
+
function isUnresolvedReceiverCall(outcome) {
|
|
67
|
+
return (outcome.kind === 'suppressed' &&
|
|
68
|
+
outcome.reason === 'receiver-unresolved' &&
|
|
69
|
+
(outcome.siteKind === undefined || outcome.siteKind === 'call'));
|
|
70
|
+
}
|
|
38
71
|
/**
|
|
39
72
|
* Build the summary, or `undefined` when nothing was dropped — an index with
|
|
40
73
|
* no unresolved receivers stores no key at all, so `epistemic` keeps its
|
|
@@ -47,20 +80,12 @@ export function summarizeUnresolvedReceivers(outcomes) {
|
|
|
47
80
|
let totalSites = 0;
|
|
48
81
|
let externalSites = 0;
|
|
49
82
|
for (const outcome of outcomes) {
|
|
50
|
-
|
|
83
|
+
// CALL sites only — see `isUnresolvedReceiverCall`, which the per-file
|
|
84
|
+
// diagnostic shares so the two can never rank different populations.
|
|
85
|
+
if (!isUnresolvedReceiverCall(outcome))
|
|
51
86
|
continue;
|
|
52
87
|
if (outcome.name.length === 0)
|
|
53
88
|
continue;
|
|
54
|
-
// CALL sites only. Case 0's recorder gates on the receiver's punctuation, not
|
|
55
|
-
// on what the reference IS, so property reads (`d.source.kind`) and writes
|
|
56
|
-
// (`x.argtypes = [...]`) are recorded alongside lost method calls — measured at
|
|
57
|
-
// 25 of 124 drops on the fixture corpus. Counting them made the consumer's
|
|
58
|
-
// "N call sites invoking X were dropped" literally false, and flagged symbols
|
|
59
|
-
// whose CALL count was never short. `siteKind` exists to make this separable.
|
|
60
|
-
// A missing `siteKind` counts as a call: the only emitter always sets it, and
|
|
61
|
-
// erring toward `lower-bound` is the safe direction for an epistemic signal.
|
|
62
|
-
if (outcome.siteKind !== undefined && outcome.siteKind !== 'call')
|
|
63
|
-
continue;
|
|
64
89
|
// Routed, not discarded. External-rooted drops (`console.log(...)`,
|
|
65
90
|
// `fetch(...)`) reach code this index does not contain, so there is no node
|
|
66
91
|
// an edge could have pointed at and nothing was lost — they must not hedge.
|
|
@@ -142,3 +167,69 @@ export function lookupExternalCallCount(summary, symName) {
|
|
|
142
167
|
return undefined;
|
|
143
168
|
return sites;
|
|
144
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Opt-in, following the repo's established diagnostic pattern
|
|
172
|
+
* (`createLogger(name, { debugEnvVar })`). Every repository in every language
|
|
173
|
+
* drops SOME receivers, so emitting this at `info` unconditionally would add a
|
|
174
|
+
* line to every analyze anyone ever runs — the noise ADV-6 flagged. #2837's
|
|
175
|
+
* reporter turns it on deliberately:
|
|
176
|
+
*
|
|
177
|
+
* GITNEXUS_DEBUG_RECEIVER_DROPS=1 gitnexus analyze
|
|
178
|
+
*/
|
|
179
|
+
// Built on first use, not at import. `createLogger` is eager where the `logger`
|
|
180
|
+
// singleton is a lazy Proxy — it resolves `pino-pretty`, constructs a SonicBoom
|
|
181
|
+
// destination and registers a `beforeExit` hook. This module is imported by
|
|
182
|
+
// `mcp/local/local-backend.ts` for its lookup helpers, so an eager call would
|
|
183
|
+
// put that on MCP server startup for a diagnostic that is off by default.
|
|
184
|
+
let receiverDropLog;
|
|
185
|
+
const dropLog = () => (receiverDropLog ??= createLogger('receiver-drops', {
|
|
186
|
+
debugEnvVar: 'GITNEXUS_DEBUG_RECEIVER_DROPS',
|
|
187
|
+
}));
|
|
188
|
+
/** Files named in the per-file receiver-drop line. Bounded: this is a signpost
|
|
189
|
+
* pointing at where to look, not an inventory. */
|
|
190
|
+
const UNRESOLVED_RECEIVER_FILE_SAMPLE = 10;
|
|
191
|
+
/**
|
|
192
|
+
* Report which FILES lost the most call sites to an untyped receiver (#2837).
|
|
193
|
+
*
|
|
194
|
+
* `summarizeUnresolvedReceivers` persists the same drops keyed by member NAME,
|
|
195
|
+
* capped at 500 distinct names, and discards `filePath` — deliberately, because
|
|
196
|
+
* its consumer (`impact()`'s exact-vs-lower-bound verdict) asks "is this
|
|
197
|
+
* symbol's caller count trustworthy", a question about names.
|
|
198
|
+
*
|
|
199
|
+
* That leaves "why does THIS file resolve nothing while its sibling resolves
|
|
200
|
+
* everything" unanswerable from any artifact, which is exactly what #2837 asked
|
|
201
|
+
* for and could not run: comparing the drop records of two files with identical
|
|
202
|
+
* declared shapes separates "receiver never typed" from "typed but no edge
|
|
203
|
+
* emitted", and narrows a per-file split in one run instead of a bisect.
|
|
204
|
+
*
|
|
205
|
+
* A log line rather than a persisted field because nothing queries it — a
|
|
206
|
+
* persisted `byFile` map would be an unread field carrying its own cap and
|
|
207
|
+
* truncation semantics. When a consumer appears, `UnresolvedReceiverSummary` is
|
|
208
|
+
* already a `RepoMeta` field and can carry it with no schema change.
|
|
209
|
+
*/
|
|
210
|
+
export function logUnresolvedReceiverFiles(outcomes) {
|
|
211
|
+
// Nothing below is observable unless the diagnostic is switched on, so skip
|
|
212
|
+
// the whole tally rather than building it and discarding it at the emit.
|
|
213
|
+
if (!dropLog().isLevelEnabled('debug'))
|
|
214
|
+
return;
|
|
215
|
+
const byFile = new Map();
|
|
216
|
+
let totalSites = 0;
|
|
217
|
+
for (const outcome of outcomes) {
|
|
218
|
+
// External-rooted drops (`fmt.Println`) reach code this index does not
|
|
219
|
+
// contain, so no node existed for an edge to point at and nothing was lost.
|
|
220
|
+
// Counting them here led the ranking with files that call `fmt` the most.
|
|
221
|
+
if (!isUnresolvedReceiverCall(outcome) || outcome.receiverOrigin === 'external')
|
|
222
|
+
continue;
|
|
223
|
+
totalSites += 1;
|
|
224
|
+
byFile.set(outcome.filePath, (byFile.get(outcome.filePath) ?? 0) + 1);
|
|
225
|
+
}
|
|
226
|
+
if (totalSites === 0)
|
|
227
|
+
return;
|
|
228
|
+
const { kept } = rankAndCap(byFile, UNRESOLVED_RECEIVER_FILE_SAMPLE);
|
|
229
|
+
dropLog().debug({
|
|
230
|
+
totalSites,
|
|
231
|
+
filesAffected: byFile.size,
|
|
232
|
+
topFiles: kept.map(([filePath, sites]) => ({ filePath, sites })),
|
|
233
|
+
}, 'receiver-unresolved call sites by file (top offenders; a file far above its ' +
|
|
234
|
+
'siblings usually means its receivers were never typed, not that it has more calls)');
|
|
235
|
+
}
|
|
@@ -3,7 +3,7 @@ export declare const JAVASCRIPT_QUERIES: string;
|
|
|
3
3
|
export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier) @name) @definition.class\n\n(function_definition\n name: (identifier) @name) @definition.function\n\n(import_statement\n name: (dotted_name) @import.source) @import\n\n; import numpy as np \u2192 aliased_import captures the module name so the\n; import path is resolved and named-binding extraction stores \"np\" \u2192 \"numpy\".\n(import_statement\n name: (aliased_import\n name: (dotted_name) @import.source)) @import\n\n(import_from_statement\n module_name: (dotted_name) @import.source) @import\n\n(import_from_statement\n module_name: (relative_import) @import.source) @import\n\n(call\n function: (identifier) @call.name) @call\n\n(call\n function: (attribute\n attribute: (identifier) @call.name)) @call\n\n; Class attribute type annotations \u2014 PEP 526: address: Address or address: Address = Address()\n; Both bare annotations (address: Address) and annotated assignments (name: str = \"test\")\n; are parsed as (assignment left: ... type: ...) in tree-sitter-python.\n(expression_statement\n (assignment\n left: (identifier) @name\n type: (type)) @definition.property)\n\n; Plain variable assignments without type annotation: x = 5, MAX_SIZE = 100\n; Overlap with @definition.property (typed) is handled by parse-worker dedup.\n(expression_statement\n (assignment\n left: (identifier) @name)) @definition.variable\n\n; Lambda bindings: `f = lambda x: x` binds a CALLABLE, so it emits Function\n; rather than Variable, matching what TS/JS already do for `const f = () => {}`.\n; This aligns the LABEL only \u2014 call resolution runs off the scope-resolution\n; query, which still models the binding as a value, so `f()` does not resolve\n; here yet. Overlap with the assignment pattern above is collapsed by the\n; parse-worker dedup (#2687).\n(expression_statement\n (assignment\n left: (identifier) @name\n right: (lambda))) @definition.function\n\n; Write access: obj.field = value\n(assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(augmented_assignment\n left: (attribute\n object: (_) @assignment.receiver\n attribute: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; Python HTTP clients: requests.get('/path'), httpx.post('/path'), session.get('/path')\n(call\n function: (attribute\n attribute: (identifier) @http_client.method)\n arguments: (argument_list\n (string (string_content) @http_client.url))) @http_client\n\n; Python decorators: @app.route, @router.get, etc.\n; The first positional argument is captured three ways (#2391): a string literal\n; path via @decorator.arg (quote-free, the fast path); a bare constant name or a\n; plus-concatenation via @decorator.arg_expr (resolved cross-file by the constant\n; resolver). The anchored optional alternation pins to the FIRST arg and stays\n; optional, so no-arg decorators (@app.tool(), etc.) and non-path first args still\n; match.\n(decorator\n (call\n function: (attribute\n object: (identifier) @decorator.receiver\n attribute: (identifier) @decorator.name)\n arguments: (argument_list\n .\n [\n (string (string_content)? @decorator.arg) @decorator.arg_str\n (identifier) @decorator.arg_expr\n (binary_operator) @decorator.arg_expr\n ]?))) @decorator\n";
|
|
4
4
|
export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Records, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Anonymous class bodies: new Runnable() { ... } \u2014 no @name capture; the\n; class extractor synthesizes the javac-style Worker$N name (#2550)\n(object_creation_expression (class_body)) @definition.class\n\n; Enum constant bodies: enum E { A { ... } } \u2014 javac's other anonymous\n; shape, synthesized as E$N by the same naming authority (#2555)\n(enum_constant body: (class_body)) @definition.class\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; \u2500\u2500 Closure bindings (#2693) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; A name bound to a closure literal IS a callable, so it emits Function rather\n; than a value label \u2014 matching TS/JS and the languages #2687 already covered.\n; The callable node is what callable-value-flow joins the binding to (by file,\n; line and name), which is what makes handler.apply(1) resolve. Overlap with the value\n; rules above is collapsed by the parse-worker dedup, which ranks callable\n; highest (#2687).\n; Anchored on field_declaration / local_variable_declaration \u2014 the SAME nodes\n; the value rules above use \u2014 so the parse-worker dedup (keyed by definition\n; node + name) actually collapses the pair. Anchoring on the inner\n; variable_declarator instead produced a Function AND a Property twin, the exact\n; double-indexing #2687 removed.\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name\n value: (lambda_expression))) @definition.function\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name\n value: (lambda_expression))) @definition.function\n";
|
|
5
5
|
export declare const C_QUERIES = "\n; Functions (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n\n; Functions returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Functions returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n\n; Structs, Unions, Enums, Typedefs\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(type_definition\n type: (struct_specifier\n body: (field_declaration_list))\n declarator: (type_identifier) @name) @definition.struct\n(union_specifier name: (type_identifier) @name) @definition.union\n(enum_specifier name: (type_identifier) @name) @definition.enum\n(type_definition\n type: (enum_specifier\n body: (enumerator_list))\n declarator: (type_identifier) @name) @definition.enum\n(enumerator name: (identifier) @name) @definition.const\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n\n; Variable declarations: int x = 5; or int x;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n";
|
|
6
|
-
export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaration name: (identifier) @name) @definition.function\n(method_declaration name: (field_identifier) @name) @definition.method\n(method_elem name: (field_identifier) @name) @definition.method\n\n; Types\n(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))
|
|
6
|
+
export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaration name: (identifier) @name) @definition.function\n(method_declaration name: (field_identifier) @name) @definition.method\n(method_elem name: (field_identifier) @name) @definition.method\n\n; Types\n;\n; Anchored on the type_spec, NOT the enclosing type_declaration (#2837) \u2014 a\n; grouped type ( A struct{}; B struct{} ) block otherwise gave every match the\n; same capture node, and goClassConfig.extractName resolved all of them to the\n; FIRST spec's name, collapsing the block to one node. Must stay in lockstep\n; with @scope.class / @declaration.struct in languages/go/query.ts, which\n; carries the full rationale. (No backticks here: this is a template literal.)\n(type_declaration (type_spec name: (type_identifier) @name type: (struct_type)) @definition.struct)\n(type_declaration (type_spec name: (type_identifier) @name type: (interface_type)) @definition.interface)\n\n; Imports\n(import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import\n(import_declaration (import_spec_list (import_spec path: (interpreted_string_literal) @import.source))) @import\n\n; Struct fields \u2014 named field declarations inside struct types\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (selector_expression field: (field_identifier) @call.name)) @call\n\n; Const/var declarations\n(const_declaration (const_spec (identifier) @name)) @definition.const\n(var_declaration (var_spec (identifier) @name)) @definition.variable\n(var_declaration (var_spec_list (var_spec (identifier) @name))) @definition.variable\n\n; Short variable declaration: x := 5\n(short_var_declaration left: (expression_list (identifier) @name)) @definition.variable\n\n; Closure bindings: `var f = func(){}` / `f := func(){}` bind a CALLABLE, so\n; they emit Function, not Variable \u2014 the same convention TS/JS already use for\n; `const f = () => {}`. This aligns the LABEL only \u2014 call resolution runs off\n; the scope-resolution query, which still models the binding as a value, so\n; `f()` does not resolve here yet. Overlap with the value patterns above is\n; collapsed by the parse-worker dedup (#2687).\n(var_declaration\n (var_spec\n name: (identifier) @name\n value: (expression_list (func_literal)))) @definition.function\n(var_declaration\n (var_spec_list\n (var_spec\n name: (identifier) @name\n value: (expression_list (func_literal))))) @definition.function\n(short_var_declaration\n left: (expression_list (identifier) @name)\n right: (expression_list (func_literal))) @definition.function\n\n; Struct literal construction: User{Name: \"Alice\"}\n(composite_literal type: (type_identifier) @call.name) @call\n\n; Write access: obj.field = value\n(assignment_statement\n left: (expression_list\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property))\n right: (_)) @assignment\n\n; Write access: obj.field++ / obj.field--\n(inc_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n(dec_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n";
|
|
7
7
|
export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.class\n; Out-of-line nested definition: class Outer::Inner { ... } / struct Outer::Inner { ... }.\n; Key the node by the full qualified_identifier text so the def materializes a\n; node that matches the HAS_METHOD owner id (also the full qualified text) and\n; stays distinct from a same-tail type in another scope (#1975, #1978).\n(class_specifier name: (qualified_identifier) @name) @definition.class\n(struct_specifier name: (qualified_identifier) @name) @definition.struct\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(struct_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition\n type: (struct_specifier\n body: (field_declaration_list))\n declarator: (type_identifier) @name) @definition.struct\n(type_definition\n type: (enum_specifier\n body: (enumerator_list))\n declarator: (type_identifier) @name) @definition.enum\n(enumerator name: (identifier) @name) @definition.const\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (operator_name) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (operator_name) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (operator_name) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (operator_name) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (operator_name) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (reference_declarator (function_declarator declarator: (operator_name) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier) (operator_name)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration\n (class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments))) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n(binary_expression operator: \"+\" @call.name) @call\n(binary_expression operator: \"<<\" @call.name) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Lambda bindings: `auto f = [](int x){ \u2026 };` binds a CALLABLE, so it emits\n; Function rather than Variable, matching TS/JS. This aligns the LABEL only \u2014\n; call resolution runs off the scope-resolution query, which still models the\n; binding as a value, so `f()` does not resolve here yet. Overlap with the\n; pattern above is collapsed by the parse-worker dedup (#2687).\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name\n value: (lambda_expression))) @definition.function\n\n; Structured bindings: auto [a, b] = makePair(); (one @name per bound identifier)\n(declaration\n declarator: (init_declarator\n declarator: (structured_binding_declarator\n (identifier) @name))) @definition.variable\n\n; Structured bindings, reference form: auto& [x, y] = tup;\n(declaration\n declarator: (init_declarator\n declarator: (reference_declarator\n (structured_binding_declarator\n (identifier) @name)))) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
|
|
8
8
|
export declare const CSHARP_QUERIES = "\n; Types\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(struct_declaration name: (identifier) @name) @definition.struct\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(delegate_declaration name: (identifier) @name) @definition.delegate\n\n; Namespaces (block form and C# 10+ file-scoped form)\n(namespace_declaration name: (identifier) @name) @definition.namespace\n(namespace_declaration name: (qualified_name) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (identifier) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (qualified_name) @name) @definition.namespace\n\n; Methods & Properties\n(method_declaration name: (identifier) @name) @definition.method\n(local_function_statement name: (identifier) @name) @definition.function\n(constructor_declaration name: (identifier) @name) @definition.constructor\n(property_declaration name: (identifier) @name) @definition.property\n\n; Primary constructors (C# 12): class User(string name, int age) { }\n(class_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n(record_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n\n; Using\n(using_directive (qualified_name) @import.source) @import\n(using_directive (identifier) @import.source) @import\n\n; Calls\n(invocation_expression function: (identifier) @call.name) @call\n(invocation_expression function: (member_access_expression name: (identifier) @call.name)) @call\n\n; Null-conditional method calls: user?.Save()\n; Parses as: invocation_expression \u2192 conditional_access_expression \u2192 member_binding_expression \u2192 identifier\n(invocation_expression\n function: (conditional_access_expression\n (member_binding_expression\n (identifier) @call.name))) @call\n\n; Constructor calls: new Foo() and new Foo { Props }\n(object_creation_expression type: (identifier) @call.name) @call\n\n; Target-typed new (C# 9): User u = new(\"x\", 5)\n(variable_declaration type: (identifier) @call.name (variable_declarator (implicit_object_creation_expression) @call))\n\n; Local variable declarations\n(local_declaration_statement\n (variable_declaration\n (variable_declarator\n (identifier) @name))) @definition.variable\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_access_expression\n expression: (_) @assignment.receiver\n name: (identifier) @assignment.property)\n right: (_)) @assignment\n\n; \u2500\u2500 Closure bindings (#2693) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; A name bound to a closure literal IS a callable, so it emits Function rather\n; than a value label \u2014 matching TS/JS and the languages #2687 already covered.\n; The callable node is what callable-value-flow joins the binding to (by file,\n; line and name), which is what makes handler(1) resolve. Overlap with the value\n; rules above is collapsed by the parse-worker dedup, which ranks callable\n; highest (#2687).\n(variable_declarator\n (identifier) @name\n (lambda_expression)) @definition.function\n";
|
|
9
9
|
export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name: (identifier) @name) @definition.function\n(function_signature_item name: (identifier) @name) @definition.function\n\n; Closure bound to a let: let handler = || target(1);\n; Emits the Function NODE. Without it a Rust closure binding had no graph node\n; at all, so it could be neither a call target nor a call source (#2699), which\n; made Rust the one exception to \"a closure bound to a name is a Function node\n; in every language\" (#2687).\n; Anchor note: this channel puts @definition.function on the OUTER\n; let_declaration, which is the OPPOSITE of the scope-resolution channel in\n; languages/rust/query.ts (inner closure_expression, to align with\n; @scope.function). Both match their own channel's convention -- compare the\n; (lexical_declaration (variable_declarator ... (arrow_function))) rule above.\n(let_declaration\n pattern: (identifier) @name\n value: (closure_expression)) @definition.function\n(struct_item name: (type_identifier) @name) @definition.struct\n; A union is materialized as a Struct node (same rationale as the\n; scope-resolution @declaration.struct in languages/rust/query.ts: every\n; registry-primary resolution gate includes Struct but excludes Union, so a\n; Union-labeled node would be an unresolvable orphan). #1934 F71.\n(union_item name: (type_identifier) @name) @definition.struct\n(enum_item name: (type_identifier) @name) @definition.enum\n(trait_item name: (type_identifier) @name) @definition.trait\n(impl_item type: (type_identifier) @name !trait) @definition.impl\n(impl_item type: (generic_type type: (type_identifier) @name) !trait) @definition.impl\n; Scoped inherent impl: impl path::Type { ... }. Key the Impl node by the full\n; scoped_type_identifier text so it matches the owner id (also full text) and\n; stays distinct from a same-tail type in another module (#1975).\n(impl_item type: (scoped_type_identifier) @name !trait) @definition.impl\n(mod_item name: (identifier) @name) @definition.module\n\n; Type aliases, const, static, macros\n(type_item name: (type_identifier) @name) @definition.type\n(const_item name: (identifier) @name) @definition.const\n(static_item name: (identifier) @name) @definition.static\n(macro_definition name: (identifier) @name) @definition.macro\n\n; Use statements\n(use_declaration argument: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call\n(call_expression function: (generic_function function: (identifier) @call.name)) @call\n\n; Struct literal construction: User { name: value }\n(struct_expression name: (type_identifier) @call.name) @call\n\n; Struct fields \u2014 named field declarations inside struct bodies\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(compound_assignment_expr\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n";
|
|
@@ -1114,8 +1114,15 @@ export const GO_QUERIES = `
|
|
|
1114
1114
|
(method_elem name: (field_identifier) @name) @definition.method
|
|
1115
1115
|
|
|
1116
1116
|
; Types
|
|
1117
|
-
|
|
1118
|
-
|
|
1117
|
+
;
|
|
1118
|
+
; Anchored on the type_spec, NOT the enclosing type_declaration (#2837) — a
|
|
1119
|
+
; grouped type ( A struct{}; B struct{} ) block otherwise gave every match the
|
|
1120
|
+
; same capture node, and goClassConfig.extractName resolved all of them to the
|
|
1121
|
+
; FIRST spec's name, collapsing the block to one node. Must stay in lockstep
|
|
1122
|
+
; with @scope.class / @declaration.struct in languages/go/query.ts, which
|
|
1123
|
+
; carries the full rationale. (No backticks here: this is a template literal.)
|
|
1124
|
+
(type_declaration (type_spec name: (type_identifier) @name type: (struct_type)) @definition.struct)
|
|
1125
|
+
(type_declaration (type_spec name: (type_identifier) @name type: (interface_type)) @definition.interface)
|
|
1119
1126
|
|
|
1120
1127
|
; Imports
|
|
1121
1128
|
(import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import
|
|
@@ -775,20 +775,26 @@ getQualifiedOwnerName) => {
|
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
|
-
// Go:
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
778
|
+
// Go: the `type_spec` IS the declared type (`type User struct { ... }`, and
|
|
779
|
+
// one per member of a grouped `type ( A struct{…}; B struct{…} )` block).
|
|
780
|
+
//
|
|
781
|
+
// Matched here rather than on the enclosing `type_declaration` (#2837): this
|
|
782
|
+
// walk climbs `node.parent`, so it passes THROUGH the containing spec on its
|
|
783
|
+
// way up from any member, and the structure it already has is the answer.
|
|
784
|
+
// Keying on the wrapper instead meant picking one spec out of several with
|
|
785
|
+
// no reference point — which filed every member of a grouped block under its
|
|
786
|
+
// FIRST struct, so two same-named fields minted one id and first-write-wins
|
|
787
|
+
// dropped the second.
|
|
788
|
+
if (current.type === 'type_spec') {
|
|
789
|
+
const typeBody = current.childForFieldName?.('type');
|
|
790
|
+
if (typeBody?.type === 'struct_type' || typeBody?.type === 'interface_type') {
|
|
791
|
+
const nameNode = current.childForFieldName?.('name');
|
|
792
|
+
if (nameNode) {
|
|
793
|
+
const label = typeBody.type === 'struct_type' ? 'Struct' : 'Interface';
|
|
794
|
+
return {
|
|
795
|
+
classId: generateId(label, `${filePath}:${nameNode.text}`),
|
|
796
|
+
className: nameNode.text,
|
|
797
|
+
};
|
|
792
798
|
}
|
|
793
799
|
}
|
|
794
800
|
}
|
package/dist/core/run-analyze.js
CHANGED
|
@@ -14,7 +14,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
14
14
|
import { retryRename } from '../storage/fs-atomic.js';
|
|
15
15
|
import { acquireIndexLock } from '../storage/index-lock.js';
|
|
16
16
|
import { runPipelineFromRepo } from './ingestion/pipeline.js';
|
|
17
|
-
import { summarizeUnresolvedReceivers } from './ingestion/scope-resolution/unresolved-receivers.js';
|
|
17
|
+
import { logUnresolvedReceiverFiles, summarizeUnresolvedReceivers, } from './ingestion/scope-resolution/unresolved-receivers.js';
|
|
18
18
|
import { resetDegradedParseCounter } from './tree-sitter/safe-parse.js';
|
|
19
19
|
import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, closeLbugBeforeExit, loadCachedEmbeddings, deleteNodesForFiles, ensureEmbeddingRowDmlSafe, deleteAllCommunitiesAndProcesses, deleteAllInterprocTaintPaths, deleteAllCallSummaries, deleteAllInjects, deleteAllAdvisedBy, deleteSpringAopEvidenceNodes, deleteSpringAutoConfigurationDeclarations, deleteSpringAutoConfigurationSyntheticClasses, queryImportersBatch, loadFTSExtension, wipeLbugDbFiles, LbugWipeError, DELETE_FILES_CHUNK_SIZE, } from './lbug/lbug-adapter.js';
|
|
20
20
|
import { estimateBufferPool, setBufferPoolSizeHint, resolveNativeSafeStorageDir, } from './lbug/lbug-config.js';
|
|
@@ -2218,6 +2218,8 @@ async function runFullAnalysisInner(repoPath, options, callbacks, writeTarget, r
|
|
|
2218
2218
|
const newFileHashesRecord = {};
|
|
2219
2219
|
for (const [k, v] of newFileHashes)
|
|
2220
2220
|
newFileHashesRecord[k] = v;
|
|
2221
|
+
const resolutionOutcomes = pipelineResult.resolutionOutcomes ?? [];
|
|
2222
|
+
logUnresolvedReceiverFiles(resolutionOutcomes);
|
|
2221
2223
|
// Annotated so the capabilities stamp below is compile-checked against
|
|
2222
2224
|
// RepoMeta's status unions (tri-review 4669518496 P1/U3) — an unannotated
|
|
2223
2225
|
// literal widens the vectorSearch.status ternary to `string` and the
|
|
@@ -2286,7 +2288,7 @@ async function runFullAnalysisInner(repoPath, options, callbacks, writeTarget, r
|
|
|
2286
2288
|
// Derived digest of the DDL this run created the tables from (#2798).
|
|
2287
2289
|
// Git-only: non-git repos never take the incremental path.
|
|
2288
2290
|
schemaFingerprint: hasGitDir(repoPath) ? SCHEMA_FINGERPRINT : undefined,
|
|
2289
|
-
unresolvedReceiverMembers: summarizeUnresolvedReceivers(
|
|
2291
|
+
unresolvedReceiverMembers: summarizeUnresolvedReceivers(resolutionOutcomes),
|
|
2290
2292
|
analysisFeatures: currentAnalysisFeatures,
|
|
2291
2293
|
// Always stamped with the live resolved mode (#2331/#2339) — unlike
|
|
2292
2294
|
// `pdg` below, 'none' is a meaningful value to compare, not an
|
|
@@ -225,7 +225,26 @@ import { fileURLToPath } from 'url';
|
|
|
225
225
|
// cache would serve entries that are missing those matches entirely — the
|
|
226
226
|
// exact failure a bump exists to prevent. Verified against origin/main at
|
|
227
227
|
// a857f4c5a, which is still on 43, so 44 is free. RE-CHECK BEFORE MERGE.
|
|
228
|
-
|
|
228
|
+
// 44 -> 45: #2837 re-anchors Go's struct/interface captures from the
|
|
229
|
+
// `type_declaration` onto the `type_spec` (`languages/go/query.ts`
|
|
230
|
+
// @scope.class/@declaration.struct/@declaration.interface, and GO_QUERIES
|
|
231
|
+
// @definition.struct/@definition.interface in `tree-sitter-queries.ts`). Every
|
|
232
|
+
// Go file declaring a type therefore emits DIFFERENT capture ranges — measured:
|
|
233
|
+
// 70 fixture digests moved with zero change in capture COUNT — and a grouped
|
|
234
|
+
// `type (...)` block emits nodes it previously did not emit at all. Parse-time,
|
|
235
|
+
// so a warm cache would replay pre-fix ParsedFiles and the fix would be a silent
|
|
236
|
+
// no-op on every incremental analyze while still passing every cold-run test.
|
|
237
|
+
//
|
|
238
|
+
// This branch originally took 44 and it COLLIDED: #2842 above merged first and
|
|
239
|
+
// claimed it. The ninth entry in this ledger, and the third EXACT clash. Worth
|
|
240
|
+
// recording HOW it was caught, because the pin test cannot catch it — both PRs
|
|
241
|
+
// asserted `toBe(44)`, which passes even when main is already 44, so the two
|
|
242
|
+
// capture schemas would have shared one PARSE_CACHE_VERSION and the durable
|
|
243
|
+
// ParsedFile store would have replayed pre-fix ParsedFiles verbatim for one of
|
|
244
|
+
// them. Only comparing against origin/main at MERGE time surfaces it.
|
|
245
|
+
// PR #2840 (Objective-C, draft) still claims 44 as well — it must move too.
|
|
246
|
+
// RE-CHECK AGAINST origin/main IMMEDIATELY BEFORE MERGING.
|
|
247
|
+
const SCHEMA_BUMP = 45;
|
|
229
248
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
230
249
|
try {
|
|
231
250
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitnexus",
|
|
3
|
-
"version": "1.6.10-rc.
|
|
3
|
+
"version": "1.6.10-rc.167",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|