gitnexus 1.6.12-rc.2 → 1.6.12-rc.21
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/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +2 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +2 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +3 -1
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +3 -3
- package/dist/_shared/scope-resolution/registries/class-registry.js +3 -3
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js +2 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +2 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/cli/analyze-config.d.ts +2 -2
- package/dist/cli/analyze-config.js +10 -33
- package/dist/cli/doctor.js +6 -4
- package/dist/cli/eval-server.js +13 -1
- package/dist/cli/i18n/en.d.ts +4 -2
- package/dist/cli/i18n/en.js +4 -2
- package/dist/cli/i18n/resources.d.ts +6 -2
- package/dist/cli/i18n/zh-CN.d.ts +2 -0
- package/dist/cli/i18n/zh-CN.js +4 -2
- package/dist/core/analysis-feature-registry.d.ts +1 -1
- package/dist/core/analysis-feature-registry.js +2 -0
- package/dist/core/auto-sync/starter.js +2 -2
- package/dist/core/embeddings/ast-utils.js +8 -3
- package/dist/core/embeddings/chunker.js +67 -10
- package/dist/core/embeddings/embedding-pipeline.d.ts +1 -1
- package/dist/core/embeddings/embedding-pipeline.js +1 -1
- package/dist/core/embeddings/structural-extractor.js +2 -2
- package/dist/core/embeddings/types.d.ts +4 -2
- package/dist/core/embeddings/types.js +18 -0
- package/dist/core/git-ref.d.ts +22 -0
- package/dist/core/git-ref.js +114 -0
- package/dist/core/git-staleness.js +14 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +1 -1
- package/dist/core/group/extractors/graphql-extractor.js +149 -13
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +2 -2
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +5 -8
- package/dist/core/ingestion/content-language-classification.d.ts +10 -0
- package/dist/core/ingestion/content-language-classification.js +30 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +1 -0
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +3 -2
- package/dist/core/ingestion/language-config.d.ts +82 -13
- package/dist/core/ingestion/language-config.js +217 -13
- package/dist/core/ingestion/language-provider.d.ts +88 -1
- package/dist/core/ingestion/languages/index.d.ts +7 -0
- package/dist/core/ingestion/languages/index.js +30 -1
- package/dist/core/ingestion/languages/objective-c/analysis-features.d.ts +10 -0
- package/dist/core/ingestion/languages/objective-c/analysis-features.js +19 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.d.ts +13 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.js +124 -0
- package/dist/core/ingestion/languages/objective-c/facts.d.ts +148 -0
- package/dist/core/ingestion/languages/objective-c/facts.js +1150 -0
- package/dist/core/ingestion/languages/objective-c/import-target.d.ts +11 -0
- package/dist/core/ingestion/languages/objective-c/import-target.js +122 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.d.ts +24 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.js +261 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.d.ts +41 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.js +282 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.js +503 -0
- package/dist/core/ingestion/languages/objective-c.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c.js +300 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +31 -0
- package/dist/core/ingestion/languages/typescript/query.js +7 -1
- package/dist/core/ingestion/languages/typescript/tsconfig.js +3 -2
- package/dist/core/ingestion/languages/typescript.js +1 -1
- package/dist/core/ingestion/languages/zig/query.d.ts +26 -0
- package/dist/core/ingestion/languages/zig/query.js +61 -1
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +24 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +3 -2
- package/dist/core/ingestion/languages/zig/scope-resolver.js +18 -5
- package/dist/core/ingestion/languages/zig/this-alias-bindings.d.ts +58 -0
- package/dist/core/ingestion/languages/zig/this-alias-bindings.js +176 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.js +82 -0
- package/dist/core/ingestion/model/registration-table.d.ts +3 -3
- package/dist/core/ingestion/model/registration-table.js +9 -5
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/model/symbol-table.js +2 -0
- package/dist/core/ingestion/model/type-registry.d.ts +1 -1
- package/dist/core/ingestion/parsing-processor.d.ts +22 -6
- package/dist/core/ingestion/parsing-processor.js +39 -20
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +9 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +307 -96
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.d.ts +42 -0
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.js +50 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -1
- package/dist/core/ingestion/scope-extractor.js +4 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +2 -0
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +78 -5
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +262 -7
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +5 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +10 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +39 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +82 -3
- package/dist/core/ingestion/scope-resolution/value-ref-edges.d.ts +14 -0
- package/dist/core/ingestion/scope-resolution/value-ref-edges.js +14 -0
- package/dist/core/ingestion/tree-sitter-queries.js +2 -0
- package/dist/core/ingestion/utils/symbol-labels.js +2 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -1
- package/dist/core/ingestion/workers/parse-worker.js +63 -3
- package/dist/core/ingestion/workers/worker-pool.d.ts +56 -2
- package/dist/core/ingestion/workers/worker-pool.js +117 -26
- package/dist/core/lbug/csv-generator.js +2 -0
- package/dist/core/lbug/schema.d.ts +3 -1
- package/dist/core/lbug/schema.js +27 -0
- package/dist/core/search/fts-indexes.js +13 -4
- package/dist/core/search/fts-schema.js +2 -0
- package/dist/core/tree-sitter/parser-loader.js +10 -0
- package/dist/core/tree-sitter/vendored-grammars.d.ts +1 -1
- package/dist/core/tree-sitter/vendored-grammars.js +2 -1
- package/dist/mcp/local/local-backend.d.ts +33 -0
- package/dist/mcp/local/local-backend.js +166 -5
- package/dist/mcp/tools.js +6 -4
- package/dist/server/analyze-job.d.ts +19 -1
- package/dist/server/analyze-job.js +14 -3
- package/dist/server/analyze-launch.d.ts +14 -0
- package/dist/server/analyze-launch.js +59 -14
- package/dist/server/analyze-worker-ipc.d.ts +6 -5
- package/dist/server/analyze-worker-ipc.js +4 -0
- package/dist/server/api.js +68 -21
- package/dist/server/git-clone.d.ts +38 -5
- package/dist/server/git-clone.js +216 -38
- package/dist/server/repo-projection.d.ts +88 -0
- package/dist/server/repo-projection.js +53 -0
- package/dist/storage/file-lock.js +5 -3
- package/dist/storage/parse-cache.js +32 -1
- package/dist/storage/parsedfile-store.js +53 -1
- package/dist/storage/v8-sidecar.d.ts +11 -0
- package/dist/storage/v8-sidecar.js +15 -7
- package/dist/utils/process-identity.d.ts +13 -0
- package/dist/utils/process-identity.js +18 -0
- package/package.json +1 -1
- package/scripts/build-tree-sitter-grammars.cjs +4 -3
- package/vendor/tree-sitter-objc/LICENSE +21 -0
- package/vendor/tree-sitter-objc/README.md +18 -0
- package/vendor/tree-sitter-objc/binding.gyp +35 -0
- package/vendor/tree-sitter-objc/bindings/node/binding.cc +19 -0
- package/vendor/tree-sitter-objc/bindings/node/binding_test.js +9 -0
- package/vendor/tree-sitter-objc/bindings/node/index.d.ts +27 -0
- package/vendor/tree-sitter-objc/bindings/node/index.js +11 -0
- package/vendor/tree-sitter-objc/grammar.js +1286 -0
- package/vendor/tree-sitter-objc/package.json +56 -0
- package/vendor/tree-sitter-objc/prebuilds/SHA256SUMS +6 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/queries/folds.scm +20 -0
- package/vendor/tree-sitter-objc/queries/highlights.scm +216 -0
- package/vendor/tree-sitter-objc/queries/indents.scm +1 -0
- package/vendor/tree-sitter-objc/queries/injections.scm +10 -0
- package/vendor/tree-sitter-objc/queries/locals.scm +1 -0
- package/vendor/tree-sitter-objc/src/grammar.json +16292 -0
- package/vendor/tree-sitter-objc/src/node-types.json +7524 -0
- package/vendor/tree-sitter-objc/src/parser.c +684023 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/parser.h +266 -0
- package/vendor/tree-sitter-objc/tree-sitter-objc.wasm +0 -0
- package/vendor/tree-sitter-objc/tree-sitter.json +38 -0
- package/web/assets/{agent-BMRqXorA.js → agent-C2n33ANd.js} +1 -1
- package/web/assets/{index-Dj3vQOHK.js → index-CHJoW_P1.js} +18 -18
- package/web/assets/src-jc8Ffy3T.js +1 -0
- package/web/index.html +2 -2
- package/web/assets/src-Df5C1Nz4.js +0 -1
package/dist/core/lbug/schema.js
CHANGED
|
@@ -60,6 +60,28 @@ CREATE NODE TABLE Class (
|
|
|
60
60
|
frameworkAnnotations STRING[],
|
|
61
61
|
PRIMARY KEY (id)
|
|
62
62
|
)`;
|
|
63
|
+
export const PROTOCOL_SCHEMA = `
|
|
64
|
+
CREATE NODE TABLE Protocol (
|
|
65
|
+
id STRING,
|
|
66
|
+
name STRING,
|
|
67
|
+
filePath STRING,
|
|
68
|
+
startLine INT64,
|
|
69
|
+
endLine INT64,
|
|
70
|
+
content STRING,
|
|
71
|
+
description STRING,
|
|
72
|
+
PRIMARY KEY (id)
|
|
73
|
+
)`;
|
|
74
|
+
export const CATEGORY_SCHEMA = `
|
|
75
|
+
CREATE NODE TABLE Category (
|
|
76
|
+
id STRING,
|
|
77
|
+
name STRING,
|
|
78
|
+
filePath STRING,
|
|
79
|
+
startLine INT64,
|
|
80
|
+
endLine INT64,
|
|
81
|
+
content STRING,
|
|
82
|
+
description STRING,
|
|
83
|
+
PRIMARY KEY (id)
|
|
84
|
+
)`;
|
|
63
85
|
export const INTERFACE_SCHEMA = `
|
|
64
86
|
CREATE NODE TABLE Interface (
|
|
65
87
|
id STRING,
|
|
@@ -550,6 +572,9 @@ export const STRUCTURAL_PAIR_DDL = ` FROM File TO Folder,
|
|
|
550
572
|
FROM \`Module\` TO \`Namespace\`,
|
|
551
573
|
FROM \`Namespace\` TO Function,
|
|
552
574
|
FROM CodeElement TO CodeElement,
|
|
575
|
+
FROM CodeElement TO Class,
|
|
576
|
+
FROM CodeElement TO Category,
|
|
577
|
+
FROM CodeElement TO Method,
|
|
553
578
|
FROM CodeElement TO \`Module\`,
|
|
554
579
|
FROM CodeElement TO \`Property\`,
|
|
555
580
|
FROM Section TO Section,
|
|
@@ -674,6 +699,8 @@ export const NODE_SCHEMA_QUERIES = [
|
|
|
674
699
|
FOLDER_SCHEMA,
|
|
675
700
|
FUNCTION_SCHEMA,
|
|
676
701
|
CLASS_SCHEMA,
|
|
702
|
+
PROTOCOL_SCHEMA,
|
|
703
|
+
CATEGORY_SCHEMA,
|
|
677
704
|
INTERFACE_SCHEMA,
|
|
678
705
|
METHOD_SCHEMA,
|
|
679
706
|
CODE_ELEMENT_SCHEMA,
|
|
@@ -267,10 +267,19 @@ export async function createSearchFTSIndexes(options) {
|
|
|
267
267
|
// the old name+content index would silently persist. `dropFTSIndex` no-ops
|
|
268
268
|
// when the index is absent (first-ever analyze) and clears the per-connection
|
|
269
269
|
// memo so the create below actually runs.
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// this
|
|
270
|
+
// The cost DID show up in analyze profiles — 7.5s of a 31.7s edit loop on a
|
|
271
|
+
// 5350-file repo, `bench/analyze-phase-breakdown.md` — and a "skip when the
|
|
272
|
+
// index is already present" gate is NOT the answer, so don't reach for it.
|
|
273
|
+
// Every caller that reaches this loop has already dropped the indexes it
|
|
274
|
+
// passes in `tables`: the incremental writeback drops them because Ladybug
|
|
275
|
+
// cannot DML a table with a live FTS index (#2589), and a full rebuild
|
|
276
|
+
// builds into a fresh staging DB that never had one. A presence gate would
|
|
277
|
+
// therefore never fire. The cost is inherent — Ladybug's FTS is not
|
|
278
|
+
// incremental, so one changed row means re-tokenizing the whole table, and
|
|
279
|
+
// `File` alone is ~33MB of file content at ~10MB/s. The measured floor and
|
|
280
|
+
// the four exits that were tried and closed (narrow further, build
|
|
281
|
+
// concurrently, raise the connection thread count, drop `content`) are in
|
|
282
|
+
// that document.
|
|
274
283
|
try {
|
|
275
284
|
await dropFTSIndex(table, indexName);
|
|
276
285
|
await createFTSIndex(table, indexName, [...properties], stemmer);
|
|
@@ -18,6 +18,8 @@ export const FTS_INDEXES = [
|
|
|
18
18
|
// Original 5 (minus File) gain `description`.
|
|
19
19
|
{ table: 'Function', indexName: 'function_fts', properties: FTS_PROPERTIES },
|
|
20
20
|
{ table: 'Class', indexName: 'class_fts', properties: FTS_PROPERTIES },
|
|
21
|
+
{ table: 'Protocol', indexName: 'protocol_fts', properties: FTS_PROPERTIES },
|
|
22
|
+
{ table: 'Category', indexName: 'category_fts', properties: FTS_PROPERTIES },
|
|
21
23
|
{ table: 'Method', indexName: 'method_fts', properties: FTS_PROPERTIES },
|
|
22
24
|
{ table: 'Interface', indexName: 'interface_fts', properties: FTS_PROPERTIES },
|
|
23
25
|
// Remaining EMBEDDABLE_LABELS symbol tables — all CODE_ELEMENT_BASE-shaped
|
|
@@ -41,6 +41,16 @@ const SOURCES = {
|
|
|
41
41
|
load: () => _require('tree-sitter-cpp'),
|
|
42
42
|
unavailableNote: 'C++ parsing requires `tree-sitter-cpp`. Check the install and native binding.',
|
|
43
43
|
},
|
|
44
|
+
[SupportedLanguages.ObjectiveC]: {
|
|
45
|
+
load: () => requireVendoredGrammar('tree-sitter-objc'),
|
|
46
|
+
optional: true,
|
|
47
|
+
severity: 'error',
|
|
48
|
+
unavailableNote: 'Objective-C parsing disabled: vendored `tree-sitter-objc` (under ' +
|
|
49
|
+
'`gitnexus/vendor/tree-sitter-objc`) could not be loaded. GitNexus ships ' +
|
|
50
|
+
'prebuilt binaries for supported macOS/Linux runner architectures; this usually ' +
|
|
51
|
+
'indicates a corrupted install or native ABI mismatch with the bundled ' +
|
|
52
|
+
'tree-sitter@0.21.1 runtime.',
|
|
53
|
+
},
|
|
44
54
|
[SupportedLanguages.Go]: {
|
|
45
55
|
load: () => _require('tree-sitter-go'),
|
|
46
56
|
unavailableNote: 'Go parsing requires `tree-sitter-go`. Check the install and native binding.',
|
|
@@ -20,7 +20,7 @@ export declare const vendoredGrammarDir: (packageName: string) => string;
|
|
|
20
20
|
/**
|
|
21
21
|
* Load a vendored tree-sitter grammar by its absolute path under `vendor/`.
|
|
22
22
|
*
|
|
23
|
-
* GitNexus vendors
|
|
23
|
+
* GitNexus vendors seven grammars (c/dart/proto/swift/kotlin/objc/zig) inside its own
|
|
24
24
|
* package under `vendor/`, each shipping committed per-platform prebuilds. They
|
|
25
25
|
* are deliberately NOT npm dependencies and must NEVER be copied into
|
|
26
26
|
* `node_modules`: an undeclared package under `node_modules` is "extraneous" to
|
|
@@ -24,6 +24,7 @@ export const VENDORED_GRAMMAR_PACKAGES = new Set([
|
|
|
24
24
|
'tree-sitter-proto',
|
|
25
25
|
'tree-sitter-swift',
|
|
26
26
|
'tree-sitter-kotlin',
|
|
27
|
+
'tree-sitter-objc',
|
|
27
28
|
'tree-sitter-zig',
|
|
28
29
|
]);
|
|
29
30
|
/** Absolute directory of a vendored grammar package under `vendor/`. */
|
|
@@ -31,7 +32,7 @@ export const vendoredGrammarDir = (packageName) => path.join(VENDOR_ROOT, packag
|
|
|
31
32
|
/**
|
|
32
33
|
* Load a vendored tree-sitter grammar by its absolute path under `vendor/`.
|
|
33
34
|
*
|
|
34
|
-
* GitNexus vendors
|
|
35
|
+
* GitNexus vendors seven grammars (c/dart/proto/swift/kotlin/objc/zig) inside its own
|
|
35
36
|
* package under `vendor/`, each shipping committed per-platform prebuilds. They
|
|
36
37
|
* are deliberately NOT npm dependencies and must NEVER be copied into
|
|
37
38
|
* `node_modules`: an undeclared package under `node_modules` is "extraneous" to
|
|
@@ -142,6 +142,39 @@ export interface EpistemicCauses {
|
|
|
142
142
|
* "nothing was undecided", and a re-index is what tells the two apart.
|
|
143
143
|
*/
|
|
144
144
|
readonly undecidedSatisfaction: number;
|
|
145
|
+
/**
|
|
146
|
+
* Symbols that name this callable in VALUE position rather than calling it
|
|
147
|
+
* (#3399) — a registration table (`bridge.accessor(Element.getNamespaceUri,
|
|
148
|
+
* …)`), a callback argument, a function pointer stored in a field.
|
|
149
|
+
*
|
|
150
|
+
* Unit: SYMBOLS — distinct referrers, the same unit and the same reason as
|
|
151
|
+
* `dispatchBoundary`: the reference edge is per-site but the walk's question
|
|
152
|
+
* is "who else might reach this", and a referrer that names the callable
|
|
153
|
+
* twice is still one place the value escapes from.
|
|
154
|
+
*
|
|
155
|
+
* Kept separate from `dispatchBoundary` even though both describe dispatch
|
|
156
|
+
* the walk cannot follow. That slot counts implementations and
|
|
157
|
+
* interface-level consumers found by the heritage probe; these are neither,
|
|
158
|
+
* and folding them in would tell a consumer branching on the numbers that an
|
|
159
|
+
* interface boundary exists where there is none. The distinction is also the
|
|
160
|
+
* actionable one: a dispatch boundary is irreducible, whereas a callable
|
|
161
|
+
* value CAN often be followed once the language models the store/load that
|
|
162
|
+
* carries it.
|
|
163
|
+
*
|
|
164
|
+
* The reference itself IS modelled — that is what makes it countable. What is
|
|
165
|
+
* missing is the invocation through the value: it happens later, through a
|
|
166
|
+
* struct field, a registry lookup, or comptime reflection, and no CALLS edge
|
|
167
|
+
* connects the eventual call site back to this symbol.
|
|
168
|
+
*
|
|
169
|
+
* Zero when the property-dispatch pass DID synthesize that invocation
|
|
170
|
+
* (`x.<key>()` through a registered object-literal key): the walk followed
|
|
171
|
+
* the registration, so nothing was missed and the result stays `exact`.
|
|
172
|
+
*
|
|
173
|
+
* Also zero — WITH a boundary note — when the probe itself could not run.
|
|
174
|
+
* The note is the signal there; the count is not, which is why a reader must
|
|
175
|
+
* branch on `epistemic` first and read the causes as explanation.
|
|
176
|
+
*/
|
|
177
|
+
readonly callableValueReferences: number;
|
|
145
178
|
}
|
|
146
179
|
interface RepoHandle {
|
|
147
180
|
id: string;
|
|
@@ -61,6 +61,7 @@ import { compareCodeUnits } from '../../lib/utils.js';
|
|
|
61
61
|
import { lookupExternalCallCount, lookupUnresolvedCallCount, } from '../../core/ingestion/scope-resolution/unresolved-receivers.js';
|
|
62
62
|
import { scopeExtractionFailureTotal } from '../../core/ingestion/scope-resolution/scope-extraction-failures.js';
|
|
63
63
|
import { lookupCount } from '../../core/ingestion/scope-resolution/summary-maps.js';
|
|
64
|
+
import { VALUE_REF_EDGE_REASON } from '../../core/ingestion/scope-resolution/value-ref-edges.js';
|
|
64
65
|
import { DEFERRED_IMPORT_REASON_SUFFIX, TYPE_ONLY_IMPORT_REASON_SUFFIX, } from '../../core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js';
|
|
65
66
|
import { fnLineOf, isPdgDegradedLayerStatus, makePdgImpactErrorResult, makePdgLayerDegradedResult, pdgLayerStatus, pdgStampForMode, runImpactPDG, validateImpactMode, pdgBridgeEvidenceForImpact, betterBridgeEvidence, composeUnifiedPdgImpactResult, splitCalleeIds, } from './pdg-impact.js';
|
|
66
67
|
/**
|
|
@@ -164,7 +165,13 @@ function normalizeToolParams(method, params) {
|
|
|
164
165
|
// Internal CLI callers materialize omitted optional flags as undefined.
|
|
165
166
|
if (value === undefined)
|
|
166
167
|
continue;
|
|
167
|
-
|
|
168
|
+
// Strict OpenAI/Anthropic adapters also materialize omitted optional
|
|
169
|
+
// string aliases as "". Treat those exactly like undefined. Required
|
|
170
|
+
// canonical values are still rejected by the method-specific check
|
|
171
|
+
// below, and non-string aliases remain invalid.
|
|
172
|
+
if (typeof value === 'string' && !value.trim())
|
|
173
|
+
continue;
|
|
174
|
+
if (typeof value !== 'string') {
|
|
168
175
|
return { error: `MCP parameter ${method}.${key} must be a non-empty string.` };
|
|
169
176
|
}
|
|
170
177
|
supplied.push({ key, value: value.trim() });
|
|
@@ -177,8 +184,8 @@ function normalizeToolParams(method, params) {
|
|
|
177
184
|
.join(', ')} must agree.`,
|
|
178
185
|
};
|
|
179
186
|
}
|
|
180
|
-
for (const
|
|
181
|
-
delete normalized[
|
|
187
|
+
for (const key of keys)
|
|
188
|
+
delete normalized[key];
|
|
182
189
|
if (supplied.length > 0)
|
|
183
190
|
normalized[canonical] = supplied[0].value;
|
|
184
191
|
}
|
|
@@ -199,6 +206,8 @@ export const VALID_NODE_LABELS = new Set([
|
|
|
199
206
|
'Folder',
|
|
200
207
|
'Function',
|
|
201
208
|
'Class',
|
|
209
|
+
'Protocol',
|
|
210
|
+
'Category',
|
|
202
211
|
'Interface',
|
|
203
212
|
'Method',
|
|
204
213
|
'CodeElement',
|
|
@@ -460,6 +469,7 @@ function epistemicFrom(dropped) {
|
|
|
460
469
|
dispatchBoundary: dropped.dispatch,
|
|
461
470
|
externalBoundary: dropped.external,
|
|
462
471
|
undecidedSatisfaction: 0,
|
|
472
|
+
callableValueReferences: dropped.callableValueReferences,
|
|
463
473
|
},
|
|
464
474
|
}
|
|
465
475
|
: { epistemic: 'exact' }
|
|
@@ -476,6 +486,7 @@ function epistemicFrom(dropped) {
|
|
|
476
486
|
dispatchBoundary: dropped.dispatch,
|
|
477
487
|
externalBoundary: dropped.external,
|
|
478
488
|
undecidedSatisfaction: dropped.undecided,
|
|
489
|
+
callableValueReferences: dropped.callableValueReferences,
|
|
479
490
|
},
|
|
480
491
|
};
|
|
481
492
|
}
|
|
@@ -561,6 +572,138 @@ function undecidedSatisfactionBoundaries(summary, names) {
|
|
|
561
572
|
}
|
|
562
573
|
return { notes, undecided };
|
|
563
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Boundary evidence for callables named in VALUE position (#3399).
|
|
577
|
+
*
|
|
578
|
+
* `bridge.accessor(Element.getNamespaceUri, null, .{})`, `{ onClick: handler }`,
|
|
579
|
+
* `qsort(xs, n, sz, compareItems)` — each REGISTERS a function somewhere
|
|
580
|
+
* instead of calling it. The registration is modelled (`value-ref` → a USES
|
|
581
|
+
* edge, Kythe `ref` / Joern `METHOD_REF`); the invocation through the stored
|
|
582
|
+
* value is not, because it happens later through a struct field, a registry
|
|
583
|
+
* lookup or comptime reflection.
|
|
584
|
+
*
|
|
585
|
+
* That gap is precisely the first half of `tools.ts`'s definition of
|
|
586
|
+
* `lower-bound` — the walk provably missed callers — and it was previously
|
|
587
|
+
* reported as `exact`. (The second half, a probe that could not run, is what
|
|
588
|
+
* the failure branch below publishes; the contract states both because this
|
|
589
|
+
* function can produce either.) A
|
|
590
|
+
* public DOM accessor bound into a JS bridge table came back LOW/exact with two
|
|
591
|
+
* internal callers, which is worse than no answer: `lower-bound` invites the
|
|
592
|
+
* reader to look further, `exact` tells them not to bother.
|
|
593
|
+
*
|
|
594
|
+
* Counted as DISTINCT REFERRERS rather than sites: the question the count
|
|
595
|
+
* serves is "how many places does this value escape from", and a table that
|
|
596
|
+
* registers the same callable twice is still one table.
|
|
597
|
+
*
|
|
598
|
+
* NOT every value reference is a gap. Where the property-dispatch pass
|
|
599
|
+
* synthesized the invocation side, the walk followed it and the answer stays
|
|
600
|
+
* `exact` — see the second probe below.
|
|
601
|
+
*
|
|
602
|
+
* Three failure modes, three different answers, none of them silence:
|
|
603
|
+
* - the query cannot run → hedge, count 0 (a probe that did not answer
|
|
604
|
+
* is not evidence of completeness);
|
|
605
|
+
* - the query returns nothing → no hedge (a real, measured zero);
|
|
606
|
+
* - the reference was followed → no hedge (nothing was missed).
|
|
607
|
+
*
|
|
608
|
+
* The probe reads the edge's `reason`, which is why writer and reader share
|
|
609
|
+
* {@link VALUE_REF_EDGE_REASON}. Language-neutral by construction — every
|
|
610
|
+
* provider that emits a `value-ref` capture participates, and one that emits
|
|
611
|
+
* none simply gets no rows.
|
|
612
|
+
*/
|
|
613
|
+
async function callableValueReferenceBoundaries(lbugPath, symId) {
|
|
614
|
+
// `COUNT(DISTINCT …)`, not a capped row list. A `LIMIT n` here would make the
|
|
615
|
+
// published cause silently understate a target with more than n
|
|
616
|
+
// registrations — and this number is documented as "how many symbols", so a
|
|
617
|
+
// reader comparing its magnitude against `receiverTyping` would be comparing
|
|
618
|
+
// a truth to a ceiling. Aggregating in the database keeps the work bounded
|
|
619
|
+
// without capping the answer; scalar `sym.id` equality plus an implicit
|
|
620
|
+
// group-by is the shape `countByType` below already relies on.
|
|
621
|
+
//
|
|
622
|
+
// `null`, not `[]`, on failure: see below — an empty result set and an
|
|
623
|
+
// unanswerable query must not be the same value.
|
|
624
|
+
const rows = await executeParameterized(lbugPath, `MATCH (other)-[r:CodeRelation]->(sym)
|
|
625
|
+
WHERE sym.id = $symId AND r.type = 'USES' AND r.reason = $reason
|
|
626
|
+
RETURN COUNT(DISTINCT other.id) AS cnt`, { symId, reason: VALUE_REF_EDGE_REASON }).catch(() => null);
|
|
627
|
+
// A probe that could not run must never read as certainty — the same rule the
|
|
628
|
+
// `loadMeta` read above states, and the whole reason this function exists.
|
|
629
|
+
// Returning zero here would publish `exact` on the strength of a query that
|
|
630
|
+
// never answered.
|
|
631
|
+
if (rows === null) {
|
|
632
|
+
return {
|
|
633
|
+
referrers: 0,
|
|
634
|
+
notes: [
|
|
635
|
+
'The callable-value-reference probe could not be run against this index, so whether ' +
|
|
636
|
+
'this symbol is registered somewhere as a value is unknown. Treat the caller list as ' +
|
|
637
|
+
'incomplete until it can be re-checked.',
|
|
638
|
+
],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const referrers = rows.length > 0 ? Number(rows[0].cnt ?? rows[0][0] ?? 0) : 0;
|
|
642
|
+
if (!Number.isFinite(referrers) || referrers <= 0)
|
|
643
|
+
return { notes: [], referrers: 0 };
|
|
644
|
+
// Registrations whose invocation side the analyzer ALREADY synthesized are
|
|
645
|
+
// not a gap. `emitPropertyDispatchCalls` sweep 2 connects `x.<key>()` member
|
|
646
|
+
// calls to every function registered under `<key>` and stamps those edges
|
|
647
|
+
// `property-dispatch`; where that happened, the walk did not "provably miss"
|
|
648
|
+
// the caller and `lower-bound` would be noise sprayed over an answer the
|
|
649
|
+
// analyzer actually computed. Zig — the case this was built for — never sets
|
|
650
|
+
// a property key (no object-literal key to dispatch through), so it is never
|
|
651
|
+
// excluded here; the exclusion exists to keep TypeScript/JavaScript hook
|
|
652
|
+
// tables that ARE followed from being downgraded.
|
|
653
|
+
//
|
|
654
|
+
// SYMBOL-LEVEL, NOT PER-EDGE, and that is only sound because of an invariant
|
|
655
|
+
// that lives nowhere near this line. The graph does not record which
|
|
656
|
+
// registration produced which synthesized call, so if one symbol could carry
|
|
657
|
+
// both a followed and an unfollowed registration, this would zero the note
|
|
658
|
+
// over a gap the analyzer provably did not close — #3399 returning through a
|
|
659
|
+
// side door. Today no symbol can:
|
|
660
|
+
//
|
|
661
|
+
// - sweep 2 synthesizes CALLS only for a registration whose site carried a
|
|
662
|
+
// `propertyKey` (sweep 1 skips the index when it is undefined);
|
|
663
|
+
// - every JS/TS `@reference.value-ref` rule also captures
|
|
664
|
+
// `@reference.property-key` — both are object-literal shapes;
|
|
665
|
+
// - no Zig `@reference.value-ref` rule captures one.
|
|
666
|
+
//
|
|
667
|
+
// So a dispatchable registration is always a JS/TS one, an undispatchable
|
|
668
|
+
// registration is always a Zig one, and the two never meet on one symbol.
|
|
669
|
+
// `test/unit/scope-resolution/value-ref-dispatchability.test.ts` FAILS the day
|
|
670
|
+
// that stops holding — a JS/TS rule for a bare callback argument
|
|
671
|
+
// (`register(handler)`), a Zig rule that grows a key. When it does, the
|
|
672
|
+
// choice to make here is between (a) splitting the edge `reason` into
|
|
673
|
+
// dispatchable / undispatchable so this probe can count them apart, and
|
|
674
|
+
// (b) hedging any symbol with an undispatchable registration regardless of
|
|
675
|
+
// dispatch. (a) is precise and costs a graph-content change; (b) is cheap and
|
|
676
|
+
// over-hedges. What is NOT acceptable is leaving this as-is, because a signal
|
|
677
|
+
// that quietly stops firing is the defect this whole feature removes.
|
|
678
|
+
//
|
|
679
|
+
// Given the invariant, the residual today is only the coarseness of the
|
|
680
|
+
// exclusion within JS/TS, and hedging every property-value registration in
|
|
681
|
+
// every JS/TS codebase is worse: a signal that fires on everything stops
|
|
682
|
+
// carrying information, and the fan-out cap warning still sits behind it.
|
|
683
|
+
const dispatched = await executeParameterized(lbugPath, `MATCH (other)-[r:CodeRelation]->(sym)
|
|
684
|
+
WHERE sym.id = $symId AND r.type = 'CALLS' AND r.reason = 'property-dispatch'
|
|
685
|
+
RETURN COUNT(r) AS cnt`, { symId }).catch(() => null);
|
|
686
|
+
// Failure here is NOT a reason to skip the hedge: we already know a value
|
|
687
|
+
// reference exists, and being unable to prove it was followed leaves the
|
|
688
|
+
// conservative answer standing.
|
|
689
|
+
const dispatchedCount = dispatched === null || dispatched.length === 0
|
|
690
|
+
? 0
|
|
691
|
+
: Number(dispatched[0].cnt ?? dispatched[0][0] ?? 0);
|
|
692
|
+
if (Number.isFinite(dispatchedCount) && dispatchedCount > 0) {
|
|
693
|
+
return { notes: [], referrers: 0 };
|
|
694
|
+
}
|
|
695
|
+
const one = referrers === 1;
|
|
696
|
+
return {
|
|
697
|
+
referrers,
|
|
698
|
+
notes: [
|
|
699
|
+
`${referrers} ${one ? 'symbol references' : 'symbols reference'} this callable as a VALUE ` +
|
|
700
|
+
`rather than calling it (a registration table, a callback argument, a stored function ` +
|
|
701
|
+
`pointer). The reference is recorded, but the call made THROUGH that value is not: it is ` +
|
|
702
|
+
`dispatched later from wherever the value is stored. Callers reached that way are absent ` +
|
|
703
|
+
`from this result — actual impact may be higher.`,
|
|
704
|
+
],
|
|
705
|
+
};
|
|
706
|
+
}
|
|
564
707
|
/** Resolve symlinks for path comparison; falls back to path.resolve on error.
|
|
565
708
|
* Uses `realpathSync.native` (not the pure-JS `realpathSync`) so that Windows
|
|
566
709
|
* 8.3 short names (e.g. RUNNER~1 → runneradmin) are expanded to long form,
|
|
@@ -3338,7 +3481,8 @@ export class LocalBackend {
|
|
|
3338
3481
|
const [incomingRows, incomingAdvisedRows] = await Promise.all([
|
|
3339
3482
|
executeParameterized(repo.lbugPath, `
|
|
3340
3483
|
MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
|
|
3341
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
3484
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'MEMBER_OF', 'USES', 'DECLARES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
3485
|
+
AND (r.type <> 'MEMBER_OF' OR labels(n) <> 'Community')
|
|
3342
3486
|
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
3343
3487
|
ORDER BY uid, relType
|
|
3344
3488
|
LIMIT 30
|
|
@@ -3461,7 +3605,8 @@ export class LocalBackend {
|
|
|
3461
3605
|
const [outgoingRows, outgoingAdvisedRows] = await Promise.all([
|
|
3462
3606
|
executeParameterized(repo.lbugPath, `
|
|
3463
3607
|
MATCH (n {id: $symId})-[r:CodeRelation]->(target)
|
|
3464
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
3608
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'MEMBER_OF', 'USES', 'DECLARES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
3609
|
+
AND (r.type <> 'MEMBER_OF' OR labels(target) <> 'Community')
|
|
3465
3610
|
RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
|
|
3466
3611
|
ORDER BY uid, relType
|
|
3467
3612
|
LIMIT 30
|
|
@@ -5685,6 +5830,18 @@ export class LocalBackend {
|
|
|
5685
5830
|
const convexDispatchPromise = direction === 'downstream'
|
|
5686
5831
|
? Promise.resolve(undefined)
|
|
5687
5832
|
: queryConvexDispatchMetadata(repo.lbugPath, symId, symName, symType);
|
|
5833
|
+
// #3399 — callables named in value position. Upstream only: the question
|
|
5834
|
+
// "who can reach this symbol" is the one a registration makes unanswerable.
|
|
5835
|
+
// A downstream walk asks what THIS symbol reaches, which a reference INTO
|
|
5836
|
+
// it does not affect.
|
|
5837
|
+
//
|
|
5838
|
+
// Issued alongside the heritage probe rather than after it, and read into
|
|
5839
|
+
// `droppedBoundaries` below, so it hedges even when that probe finds
|
|
5840
|
+
// nothing AND when it throws — a value reference is an independent reason
|
|
5841
|
+
// a count is short, exactly as the receiver drops above are.
|
|
5842
|
+
const valueRefPromise = direction === 'downstream'
|
|
5843
|
+
? Promise.resolve({ notes: [], referrers: 0 })
|
|
5844
|
+
: callableValueReferenceBoundaries(repo.lbugPath, symId);
|
|
5688
5845
|
const interfaceRowsPromise = executeParameterized(repo.lbugPath, `MATCH (x)-[r:CodeRelation]->(iface)
|
|
5689
5846
|
WHERE x.id = $symId AND r.type IN $heritage
|
|
5690
5847
|
RETURN DISTINCT iface.id AS id, iface.name AS name, labels(iface)[0] AS label
|
|
@@ -5700,12 +5857,14 @@ export class LocalBackend {
|
|
|
5700
5857
|
: []),
|
|
5701
5858
|
]);
|
|
5702
5859
|
const convexDispatch = await convexDispatchPromise;
|
|
5860
|
+
const valueRefDrops = await valueRefPromise;
|
|
5703
5861
|
const droppedBoundaries = {
|
|
5704
5862
|
...receiverDrops,
|
|
5705
5863
|
notes: [
|
|
5706
5864
|
...receiverDrops.notes,
|
|
5707
5865
|
...scopeExtractionDrops.notes,
|
|
5708
5866
|
...undecidedDrops.notes,
|
|
5867
|
+
...valueRefDrops.notes,
|
|
5709
5868
|
...(convexDispatch === undefined ? [] : [convexDispatch.boundary]),
|
|
5710
5869
|
],
|
|
5711
5870
|
undecided: undecidedDrops.undecided,
|
|
@@ -5714,6 +5873,7 @@ export class LocalBackend {
|
|
|
5714
5873
|
// inventing one from the presence of a note.
|
|
5715
5874
|
dispatch: 0,
|
|
5716
5875
|
scopeExtraction: scopeExtractionDrops.files,
|
|
5876
|
+
callableValueReferences: valueRefDrops.referrers,
|
|
5717
5877
|
};
|
|
5718
5878
|
try {
|
|
5719
5879
|
// Discover the interface / abstract supertypes on the target's boundary.
|
|
@@ -5794,6 +5954,7 @@ export class LocalBackend {
|
|
|
5794
5954
|
dispatchBoundary: droppedBoundaries.dispatch + dispatchBoundarySymbols,
|
|
5795
5955
|
externalBoundary: droppedBoundaries.external,
|
|
5796
5956
|
undecidedSatisfaction: droppedBoundaries.undecided,
|
|
5957
|
+
callableValueReferences: droppedBoundaries.callableValueReferences,
|
|
5797
5958
|
},
|
|
5798
5959
|
};
|
|
5799
5960
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -250,14 +250,15 @@ Handles disambiguation: if multiple symbols share the same name, returns ranked
|
|
|
250
250
|
NOTE: ACCESSES edges (field read/write tracking) are included in context results with reason 'read' or 'write'. CALLS edges resolve through field access chains and method-call chains (e.g., user.address.getCity().save() produces CALLS edges at each step).
|
|
251
251
|
|
|
252
252
|
COMPLETENESS OF incoming: alongside symbol/incoming/outgoing the result carries the same epistemic envelope impact() returns:
|
|
253
|
-
- epistemic: 'exact' | 'lower-bound' — 'lower-bound' means callers
|
|
253
|
+
- epistemic: 'exact' | 'lower-bound' — 'lower-bound' means incoming is a FLOOR: either the walk provably missed callers, or a probe that would have established completeness could not run. Do not read it as proof that an omitted caller exists — read boundaries for which of the two it is.
|
|
254
254
|
- boundaries: string[] — one plain-language sentence per reason. Prose for humans; branch on causes instead.
|
|
255
|
-
- causes: { scopeExtractionFiles, receiverTyping, dispatchBoundary, externalBoundary, undecidedSatisfaction } — machine-readable WHY. Every field counts MISSING THINGS, never sentences:
|
|
255
|
+
- causes: { scopeExtractionFiles, receiverTyping, dispatchBoundary, externalBoundary, undecidedSatisfaction, callableValueReferences } — machine-readable WHY. Every field counts MISSING THINGS, never sentences:
|
|
256
256
|
- causes.scopeExtractionFiles (unit: files) > 0 — scope extraction still failed after the fallback pass, so scope-resolution edges from those files are absent. A value of 0 does not prove completeness when epistemic is 'lower-bound' because an older or unverified index has no measured file count. Re-run \`gitnexus analyze --force\`; if the reason persists, inspect the extraction warnings.
|
|
257
257
|
- causes.receiverTyping (unit: call sites) > 0 — RESOLVER GAP: the analyzer dropped that many call sites on this name because it could not type the receiver, so they are missing from incoming. Do not read an absent caller as proof none exists.
|
|
258
258
|
- causes.externalBoundary (unit: call sites) > 0 — the calls left the indexed program (System.out.println, fetch(...)). NOT a defect: no in-graph node could have been reached. An epistemic:'exact' result can carry this.
|
|
259
259
|
- causes.dispatchBoundary (unit: symbols) > 0 — DI or interface dispatch: that many symbols sit on or beyond a boundary static analysis cannot cross. Irreducible. A symbol count, not a site count — per-site multiplicity is not retained for these edges — so compare its magnitude with receiverTyping, not its exact value. A framework runtime-proxy boundary can make epistemic lower-bound while this value remains 0 because endpoint metadata proves the gap but cannot count omitted symbols.
|
|
260
260
|
- causes.undecidedSatisfaction (unit: unjudged interface/type pairs) > 0 — the analyzer could not decide whether a type satisfies an interface, so no IMPLEMENTS edge exists and no dispatch boundary was left for the walk to notice. Usually fixable by making the missing dependency available to analysis.
|
|
261
|
+
- causes.callableValueReferences (unit: symbols) > 0 — that many symbols name this callable as a VALUE instead of calling it (a Zig registration table or const initialiser, a JS/TS object-literal property value). A bare callback argument in JS/TS is not captured today and is not counted, so a 0 does not rule that shape out; nor does it, on an index built before the language emitted these captures — re-analyze first. The reference is in the graph as a USES edge; the call made THROUGH the value is not, because it is dispatched later from wherever the value was stored. incoming.calls is therefore a floor. Follow the USES edges to find the registration, then the code that reads it. It is 0 when the analyzer DID synthesize the dispatch through a registered property key. That exclusion is per SYMBOL, not per registration: a target with BOTH a followed registration and an unfollowed escape reads 0 here, so a 0 means 'no unfollowed registration was proven', not 'this symbol escapes nowhere'. A 0 alongside epistemic 'lower-bound' can also mean the probe itself could not run — read boundaries for which.
|
|
261
262
|
|
|
262
263
|
REQUIRES RE-INDEX: causes.scopeExtractionFiles, causes.receiverTyping, causes.externalBoundary, causes.undecidedSatisfaction, and framework runtime-proxy boundary detection depend on index-time metadata that only a current analyzer writes. Against an older index the metadata can be absent, which is indistinguishable from "nothing was dropped" unless the schema probe detects the stale index — re-run \`gitnexus analyze\` before trusting a zero or an apparently exact result.
|
|
263
264
|
|
|
@@ -443,15 +444,16 @@ Output includes:
|
|
|
443
444
|
- affected_processes: which execution flows break and at which step
|
|
444
445
|
- affected_modules: which functional areas are hit (direct vs indirect; classification-unavailable when that secondary query fails)
|
|
445
446
|
- byDepth: affected symbols grouped by traversal depth (paginated by limit/offset; omitted when summaryOnly:true — use byDepthCounts for totals per depth, pagination object when truncated). Each item includes a processes:[{id,label,processType,step}] field listing the execution flows that symbol participates in. Empty when the symbol has no process membership. Can ALSO be empty when partial:true is set — either the process-aggregation pass hit its cap before detecting affected processes, or per-symbol enrichment was capped on a very large page. When partial:true, do NOT treat processes:[] as proof of no participation; cross-check the top-level affected_processes list. An item carries staticGated:true only when the edge that reached it is provably unreachable at compile time from the indexed source (today: Zig calls inside an 'if (CONST_FALSE)' body or the else of 'if (CONST_TRUE)'); the field is absent when the edge is live or the language does not model it. Traversal and risk do NOT filter or rank on it: it is metadata for the caller to weigh.
|
|
446
|
-
- epistemic: 'exact' | 'lower-bound' — whether impactedCount is the whole story. 'lower-bound' means the walk provably missed callers, so
|
|
447
|
+
- epistemic: 'exact' | 'lower-bound' — whether impactedCount is the whole story. 'lower-bound' means the count is a FLOOR: either the walk provably missed callers, or a probe that would have established completeness could not run (a failed callable-value-reference query says so in boundaries). It is not itself proof that an omitted caller exists — branch on causes and read boundaries. Absent only on skipped probes (ambiguous-candidate lists, group fan-out).
|
|
447
448
|
- boundaries: string[] — one plain-language sentence per reason the count is short. Prose for humans; branch on causes instead.
|
|
448
|
-
- causes: { scopeExtractionFiles, receiverTyping, dispatchBoundary, externalBoundary, undecidedSatisfaction } — the machine-readable split of WHY, so an agent gating its own edits can tell a fixable analyzer gap from an irreducible one. Every field counts MISSING THINGS, never sentences:
|
|
449
|
+
- causes: { scopeExtractionFiles, receiverTyping, dispatchBoundary, externalBoundary, undecidedSatisfaction, callableValueReferences } — the machine-readable split of WHY, so an agent gating its own edits can tell a fixable analyzer gap from an irreducible one. Every field counts MISSING THINGS, never sentences:
|
|
449
450
|
- causes.scopeExtractionFiles (unit: files) > 0 — scope extraction still failed after the fallback pass, so scope-resolution edges from those files are absent. A value of 0 does not prove completeness when epistemic is 'lower-bound' because an older or unverified index has no measured file count. Re-run \`gitnexus analyze --force\`; if the reason persists, inspect the extraction warnings.
|
|
450
451
|
- causes.receiverTyping (unit: call sites) > 0 — the RESOLVER GAP signal: the analyzer dropped that many call sites because it could not establish the receiver's type (unresolved constructor, factory, chained expression). Those callers are absent from byDepth. Treat the result as incomplete: grep the symbol name before deleting or renaming.
|
|
451
452
|
- causes.externalBoundary (unit: call sites) > 0 — those calls left the indexed program (System.out.println, fetch(...), os.environ.*). NOT a defect and NOT a reason the count is short: there is no in-graph node any edge could have reached. An epistemic:'exact' result can carry this.
|
|
452
453
|
- causes.dispatchBoundary (unit: symbols) > 0 — DI or interface dispatch: that many symbols sit on or beyond a boundary a static walk cannot cross. Irreducible. A symbol count, not a site count — per-site multiplicity is not retained for these edges — so compare its magnitude with receiverTyping, not its exact value. A framework runtime-proxy boundary can make epistemic lower-bound while this value remains 0 because endpoint metadata proves the gap but cannot count omitted symbols.
|
|
453
454
|
|
|
454
455
|
- causes.undecidedSatisfaction (unit: unjudged interface/type pairs) > 0 — the analyzer could not DECIDE whether a type satisfies an interface (a type in a required signature named a package it could not resolve), so no IMPLEMENTS edge exists and no dispatch boundary was left for the walk to notice. Distinct from every cause above, which count decided facts that could not be attributed; this one counts questions never answered. It is the only cause that shortens a result WITHOUT leaving a trace in the graph, so an unhedged zero on a symbol reached only through such an interface would otherwise read as 'nobody calls this'. Usually fixable: it most often means a dependency is missing from the analyzed tree.
|
|
456
|
+
- causes.callableValueReferences (unit: symbols) > 0 — that many symbols name this callable as a VALUE rather than calling it: 'bridge.accessor(Element.getNamespaceUri, ...)' and 'pub const h = onReset;' in Zig, '{ onClick: handler }' in JS/TS. Those are the shapes actually captured today — a bare callback argument in JS/TS ('qsort'-style, 'setTimeout(tick)') is NOT one of them and is not counted, so a 0 here does not rule that shape out. The registration IS modelled (a USES edge); the invocation through the stored value is NOT, because it happens later via a struct field, a registry lookup or comptime reflection. So impactedCount is a floor and a LOW risk verdict on such a symbol is a floor too. Unlike dispatchBoundary this is often reducible — it usually means the language provider does not yet follow that store/load — but until it is, do NOT read an empty or small caller set as 'safe to change'. It is an exact count, not a capped sample. It is 0 when the analyzer DID synthesize the dispatch through a registered property key, and epistemic stays 'exact' on that account. That exclusion is symbol-level, not edge-level — the graph does not record which registration produced which synthesized call — so a symbol with a mix of followed and unfollowed registrations also reads 0: treat a 0 as 'no unfollowed registration was proven', not as proof the value escapes nowhere. A 0 alongside epistemic 'lower-bound' can instead mean the probe could not run at all, so read boundaries to tell those apart. Read from the graph, so it needs no index-time metadata BEYOND the edges being there: an index built by an analyzer that did not yet emit this language's value-ref captures has none, and reports 0. Re-analyze before reading a 0 as measured.
|
|
455
457
|
|
|
456
458
|
REQUIRES RE-INDEX: causes.scopeExtractionFiles, causes.receiverTyping, causes.externalBoundary, causes.undecidedSatisfaction, and framework runtime-proxy boundary detection depend on index-time metadata that only a current analyzer writes. Against an older index the metadata can be absent, which is indistinguishable from "nothing was dropped" unless the schema probe detects the stale index — re-run \`gitnexus analyze\` before trusting a zero or an apparently exact result.
|
|
457
459
|
|
|
@@ -53,6 +53,13 @@ export interface AnalyzeJob {
|
|
|
53
53
|
repoUrl?: string;
|
|
54
54
|
repoPath?: string;
|
|
55
55
|
repoName?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Index-branch selector this job was started with, part of the job's dedup
|
|
58
|
+
* identity. A repo is not "the same repo" for reuse purposes when a different
|
|
59
|
+
* branch was asked for — reusing across branches would hand the caller a 202
|
|
60
|
+
* for a job indexing something else.
|
|
61
|
+
*/
|
|
62
|
+
branch?: string;
|
|
56
63
|
progress: AnalyzeJobProgress;
|
|
57
64
|
error?: string;
|
|
58
65
|
/** Set only when a terminal `failed` job still persisted usable work. */
|
|
@@ -70,10 +77,21 @@ export declare class JobManager {
|
|
|
70
77
|
private emitter;
|
|
71
78
|
private cleanupTimer;
|
|
72
79
|
constructor();
|
|
73
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Create a new job, or return the existing active job for the same repo AND
|
|
82
|
+
* the same branch.
|
|
83
|
+
*
|
|
84
|
+
* Branch is part of the identity deliberately. Deduping on repo alone would
|
|
85
|
+
* return the in-flight job for branch A to a caller that asked for branch B,
|
|
86
|
+
* and that caller would read the resulting 202/`complete` as "B is indexed"
|
|
87
|
+
* — the same silent wrong-branch outcome that made `branch` worth honoring in
|
|
88
|
+
* the first place. Falling through instead lets the single-slot guard below
|
|
89
|
+
* reject the request outright, which is a truthful answer.
|
|
90
|
+
*/
|
|
74
91
|
createJob(params: {
|
|
75
92
|
repoUrl?: string;
|
|
76
93
|
repoPath?: string;
|
|
94
|
+
branch?: string;
|
|
77
95
|
}): AnalyzeJob;
|
|
78
96
|
getJob(id: string): AnalyzeJob | undefined;
|
|
79
97
|
/** Return a snapshot of all tracked jobs for inspection. */
|
|
@@ -34,14 +34,24 @@ export class JobManager {
|
|
|
34
34
|
constructor() {
|
|
35
35
|
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Create a new job, or return the existing active job for the same repo AND
|
|
39
|
+
* the same branch.
|
|
40
|
+
*
|
|
41
|
+
* Branch is part of the identity deliberately. Deduping on repo alone would
|
|
42
|
+
* return the in-flight job for branch A to a caller that asked for branch B,
|
|
43
|
+
* and that caller would read the resulting 202/`complete` as "B is indexed"
|
|
44
|
+
* — the same silent wrong-branch outcome that made `branch` worth honoring in
|
|
45
|
+
* the first place. Falling through instead lets the single-slot guard below
|
|
46
|
+
* reject the request outright, which is a truthful answer.
|
|
47
|
+
*/
|
|
38
48
|
createJob(params) {
|
|
39
|
-
// Dedup: return existing active job for the same repo (by URL or path)
|
|
49
|
+
// Dedup: return existing active job for the same repo (by URL or path) and branch
|
|
40
50
|
for (const job of this.jobs.values()) {
|
|
41
51
|
if (!this.isTerminal(job.status)) {
|
|
42
52
|
const isSameRepo = (params.repoUrl && job.repoUrl === params.repoUrl) ||
|
|
43
53
|
(params.repoPath && job.repoPath === params.repoPath);
|
|
44
|
-
if (isSameRepo) {
|
|
54
|
+
if (isSameRepo && job.branch === params.branch) {
|
|
45
55
|
return job;
|
|
46
56
|
}
|
|
47
57
|
}
|
|
@@ -57,6 +67,7 @@ export class JobManager {
|
|
|
57
67
|
status: 'queued',
|
|
58
68
|
repoUrl: params.repoUrl,
|
|
59
69
|
repoPath: params.repoPath,
|
|
70
|
+
branch: params.branch,
|
|
60
71
|
progress: { phase: 'queued', percent: 0, message: 'Waiting to start...' },
|
|
61
72
|
startedAt: Date.now(),
|
|
62
73
|
retryCount: 0,
|
|
@@ -31,6 +31,20 @@ export interface LaunchOptions {
|
|
|
31
31
|
springActuatorPath?: string;
|
|
32
32
|
asyncApiSpecPath?: string;
|
|
33
33
|
registryName?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Index-branch selector, forwarded to `AnalyzeOptions.branch`.
|
|
36
|
+
*
|
|
37
|
+
* Setting it does not by itself mean a `branches/<slug>/` sub-directory:
|
|
38
|
+
* `resolveBranchPlacement` (storage/branch-index.ts) keeps the run on the flat
|
|
39
|
+
* slot when that slot has no recorded owner, or when its owner already IS this
|
|
40
|
+
* label. Only a label that differs from the flat slot's owner gets its own
|
|
41
|
+
* sub-directory.
|
|
42
|
+
*
|
|
43
|
+
* The caller is responsible for having the branch checked out —
|
|
44
|
+
* `resolveWriteTarget` in core refuses a label that disagrees with the working
|
|
45
|
+
* tree, which is what keeps one branch's content out of another's slot (#2106).
|
|
46
|
+
*/
|
|
47
|
+
branch?: string;
|
|
34
48
|
}
|
|
35
49
|
export declare function createLaunchAnalysisWorker(deps: LaunchDeps): (job: {
|
|
36
50
|
id: string;
|