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.
Files changed (188) hide show
  1. package/dist/_shared/graph/types.d.ts +1 -1
  2. package/dist/_shared/graph/types.d.ts.map +1 -1
  3. package/dist/_shared/language-detection.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.js +2 -0
  5. package/dist/_shared/language-detection.js.map +1 -1
  6. package/dist/_shared/languages.d.ts +1 -0
  7. package/dist/_shared/languages.d.ts.map +1 -1
  8. package/dist/_shared/languages.js +1 -0
  9. package/dist/_shared/languages.js.map +1 -1
  10. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  11. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  12. package/dist/_shared/lbug/schema-constants.js +2 -0
  13. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  14. package/dist/_shared/scope-resolution/language-classification.d.ts +2 -1
  15. package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
  16. package/dist/_shared/scope-resolution/language-classification.js +3 -1
  17. package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
  18. package/dist/_shared/scope-resolution/registries/class-registry.d.ts +3 -3
  19. package/dist/_shared/scope-resolution/registries/class-registry.js +3 -3
  20. package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
  21. package/dist/_shared/scope-resolution/registries/context.js +2 -0
  22. package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
  23. package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
  24. package/dist/_shared/scope-resolution/resolve-type-ref.js +2 -0
  25. package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
  26. package/dist/cli/analyze-config.d.ts +2 -2
  27. package/dist/cli/analyze-config.js +10 -33
  28. package/dist/cli/doctor.js +6 -4
  29. package/dist/cli/eval-server.js +13 -1
  30. package/dist/cli/i18n/en.d.ts +4 -2
  31. package/dist/cli/i18n/en.js +4 -2
  32. package/dist/cli/i18n/resources.d.ts +6 -2
  33. package/dist/cli/i18n/zh-CN.d.ts +2 -0
  34. package/dist/cli/i18n/zh-CN.js +4 -2
  35. package/dist/core/analysis-feature-registry.d.ts +1 -1
  36. package/dist/core/analysis-feature-registry.js +2 -0
  37. package/dist/core/auto-sync/starter.js +2 -2
  38. package/dist/core/embeddings/ast-utils.js +8 -3
  39. package/dist/core/embeddings/chunker.js +67 -10
  40. package/dist/core/embeddings/embedding-pipeline.d.ts +1 -1
  41. package/dist/core/embeddings/embedding-pipeline.js +1 -1
  42. package/dist/core/embeddings/structural-extractor.js +2 -2
  43. package/dist/core/embeddings/types.d.ts +4 -2
  44. package/dist/core/embeddings/types.js +18 -0
  45. package/dist/core/git-ref.d.ts +22 -0
  46. package/dist/core/git-ref.js +114 -0
  47. package/dist/core/git-staleness.js +14 -0
  48. package/dist/core/group/extractors/graphql-extractor.d.ts +1 -1
  49. package/dist/core/group/extractors/graphql-extractor.js +149 -13
  50. package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
  51. package/dist/core/group/extractors/manifest-extractor.js +2 -2
  52. package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +5 -8
  53. package/dist/core/ingestion/content-language-classification.d.ts +10 -0
  54. package/dist/core/ingestion/content-language-classification.js +30 -0
  55. package/dist/core/ingestion/filesystem-walker.d.ts +1 -0
  56. package/dist/core/ingestion/filesystem-walker.js +1 -1
  57. package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +3 -2
  58. package/dist/core/ingestion/language-config.d.ts +82 -13
  59. package/dist/core/ingestion/language-config.js +217 -13
  60. package/dist/core/ingestion/language-provider.d.ts +88 -1
  61. package/dist/core/ingestion/languages/index.d.ts +7 -0
  62. package/dist/core/ingestion/languages/index.js +30 -1
  63. package/dist/core/ingestion/languages/objective-c/analysis-features.d.ts +10 -0
  64. package/dist/core/ingestion/languages/objective-c/analysis-features.js +19 -0
  65. package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.d.ts +13 -0
  66. package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.js +124 -0
  67. package/dist/core/ingestion/languages/objective-c/facts.d.ts +148 -0
  68. package/dist/core/ingestion/languages/objective-c/facts.js +1150 -0
  69. package/dist/core/ingestion/languages/objective-c/import-target.d.ts +11 -0
  70. package/dist/core/ingestion/languages/objective-c/import-target.js +122 -0
  71. package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.d.ts +24 -0
  72. package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.js +261 -0
  73. package/dist/core/ingestion/languages/objective-c/resolution-config.d.ts +41 -0
  74. package/dist/core/ingestion/languages/objective-c/resolution-config.js +282 -0
  75. package/dist/core/ingestion/languages/objective-c/scope-resolver.d.ts +2 -0
  76. package/dist/core/ingestion/languages/objective-c/scope-resolver.js +503 -0
  77. package/dist/core/ingestion/languages/objective-c.d.ts +2 -0
  78. package/dist/core/ingestion/languages/objective-c.js +300 -0
  79. package/dist/core/ingestion/languages/typescript/query.d.ts +31 -0
  80. package/dist/core/ingestion/languages/typescript/query.js +7 -1
  81. package/dist/core/ingestion/languages/typescript/tsconfig.js +3 -2
  82. package/dist/core/ingestion/languages/typescript.js +1 -1
  83. package/dist/core/ingestion/languages/zig/query.d.ts +26 -0
  84. package/dist/core/ingestion/languages/zig/query.js +61 -1
  85. package/dist/core/ingestion/languages/zig/range-binding.d.ts +8 -0
  86. package/dist/core/ingestion/languages/zig/range-binding.js +24 -0
  87. package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +3 -2
  88. package/dist/core/ingestion/languages/zig/scope-resolver.js +18 -5
  89. package/dist/core/ingestion/languages/zig/this-alias-bindings.d.ts +58 -0
  90. package/dist/core/ingestion/languages/zig/this-alias-bindings.js +176 -0
  91. package/dist/core/ingestion/languages/zig/workspace-static-gating.d.ts +8 -0
  92. package/dist/core/ingestion/languages/zig/workspace-static-gating.js +82 -0
  93. package/dist/core/ingestion/model/registration-table.d.ts +3 -3
  94. package/dist/core/ingestion/model/registration-table.js +9 -5
  95. package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
  96. package/dist/core/ingestion/model/symbol-table.js +2 -0
  97. package/dist/core/ingestion/model/type-registry.d.ts +1 -1
  98. package/dist/core/ingestion/parsing-processor.d.ts +22 -6
  99. package/dist/core/ingestion/parsing-processor.js +39 -20
  100. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +9 -1
  101. package/dist/core/ingestion/pipeline-phases/parse-impl.js +307 -96
  102. package/dist/core/ingestion/pipeline-phases/parse-round-budget.d.ts +42 -0
  103. package/dist/core/ingestion/pipeline-phases/parse-round-budget.js +50 -0
  104. package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -1
  105. package/dist/core/ingestion/scope-extractor.js +4 -0
  106. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +13 -0
  107. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +2 -0
  108. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +2 -0
  109. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +78 -5
  110. package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +262 -7
  111. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +5 -2
  112. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  113. package/dist/core/ingestion/scope-resolution/pipeline/run.js +10 -1
  114. package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +39 -3
  115. package/dist/core/ingestion/scope-resolution/scope/walkers.js +82 -3
  116. package/dist/core/ingestion/scope-resolution/value-ref-edges.d.ts +14 -0
  117. package/dist/core/ingestion/scope-resolution/value-ref-edges.js +14 -0
  118. package/dist/core/ingestion/tree-sitter-queries.js +2 -0
  119. package/dist/core/ingestion/utils/symbol-labels.js +2 -0
  120. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -1
  121. package/dist/core/ingestion/workers/parse-worker.js +63 -3
  122. package/dist/core/ingestion/workers/worker-pool.d.ts +56 -2
  123. package/dist/core/ingestion/workers/worker-pool.js +117 -26
  124. package/dist/core/lbug/csv-generator.js +2 -0
  125. package/dist/core/lbug/schema.d.ts +3 -1
  126. package/dist/core/lbug/schema.js +27 -0
  127. package/dist/core/search/fts-indexes.js +13 -4
  128. package/dist/core/search/fts-schema.js +2 -0
  129. package/dist/core/tree-sitter/parser-loader.js +10 -0
  130. package/dist/core/tree-sitter/vendored-grammars.d.ts +1 -1
  131. package/dist/core/tree-sitter/vendored-grammars.js +2 -1
  132. package/dist/mcp/local/local-backend.d.ts +33 -0
  133. package/dist/mcp/local/local-backend.js +166 -5
  134. package/dist/mcp/tools.js +6 -4
  135. package/dist/server/analyze-job.d.ts +19 -1
  136. package/dist/server/analyze-job.js +14 -3
  137. package/dist/server/analyze-launch.d.ts +14 -0
  138. package/dist/server/analyze-launch.js +59 -14
  139. package/dist/server/analyze-worker-ipc.d.ts +6 -5
  140. package/dist/server/analyze-worker-ipc.js +4 -0
  141. package/dist/server/api.js +68 -21
  142. package/dist/server/git-clone.d.ts +38 -5
  143. package/dist/server/git-clone.js +216 -38
  144. package/dist/server/repo-projection.d.ts +88 -0
  145. package/dist/server/repo-projection.js +53 -0
  146. package/dist/storage/file-lock.js +5 -3
  147. package/dist/storage/parse-cache.js +32 -1
  148. package/dist/storage/parsedfile-store.js +53 -1
  149. package/dist/storage/v8-sidecar.d.ts +11 -0
  150. package/dist/storage/v8-sidecar.js +15 -7
  151. package/dist/utils/process-identity.d.ts +13 -0
  152. package/dist/utils/process-identity.js +18 -0
  153. package/package.json +1 -1
  154. package/scripts/build-tree-sitter-grammars.cjs +4 -3
  155. package/vendor/tree-sitter-objc/LICENSE +21 -0
  156. package/vendor/tree-sitter-objc/README.md +18 -0
  157. package/vendor/tree-sitter-objc/binding.gyp +35 -0
  158. package/vendor/tree-sitter-objc/bindings/node/binding.cc +19 -0
  159. package/vendor/tree-sitter-objc/bindings/node/binding_test.js +9 -0
  160. package/vendor/tree-sitter-objc/bindings/node/index.d.ts +27 -0
  161. package/vendor/tree-sitter-objc/bindings/node/index.js +11 -0
  162. package/vendor/tree-sitter-objc/grammar.js +1286 -0
  163. package/vendor/tree-sitter-objc/package.json +56 -0
  164. package/vendor/tree-sitter-objc/prebuilds/SHA256SUMS +6 -0
  165. package/vendor/tree-sitter-objc/prebuilds/darwin-arm64/tree-sitter-objc.node +0 -0
  166. package/vendor/tree-sitter-objc/prebuilds/darwin-x64/tree-sitter-objc.node +0 -0
  167. package/vendor/tree-sitter-objc/prebuilds/linux-arm64/tree-sitter-objc.node +0 -0
  168. package/vendor/tree-sitter-objc/prebuilds/linux-x64/tree-sitter-objc.node +0 -0
  169. package/vendor/tree-sitter-objc/prebuilds/win32-arm64/tree-sitter-objc.node +0 -0
  170. package/vendor/tree-sitter-objc/prebuilds/win32-x64/tree-sitter-objc.node +0 -0
  171. package/vendor/tree-sitter-objc/queries/folds.scm +20 -0
  172. package/vendor/tree-sitter-objc/queries/highlights.scm +216 -0
  173. package/vendor/tree-sitter-objc/queries/indents.scm +1 -0
  174. package/vendor/tree-sitter-objc/queries/injections.scm +10 -0
  175. package/vendor/tree-sitter-objc/queries/locals.scm +1 -0
  176. package/vendor/tree-sitter-objc/src/grammar.json +16292 -0
  177. package/vendor/tree-sitter-objc/src/node-types.json +7524 -0
  178. package/vendor/tree-sitter-objc/src/parser.c +684023 -0
  179. package/vendor/tree-sitter-objc/src/tree_sitter/alloc.h +54 -0
  180. package/vendor/tree-sitter-objc/src/tree_sitter/array.h +291 -0
  181. package/vendor/tree-sitter-objc/src/tree_sitter/parser.h +266 -0
  182. package/vendor/tree-sitter-objc/tree-sitter-objc.wasm +0 -0
  183. package/vendor/tree-sitter-objc/tree-sitter.json +38 -0
  184. package/web/assets/{agent-BMRqXorA.js → agent-C2n33ANd.js} +1 -1
  185. package/web/assets/{index-Dj3vQOHK.js → index-CHJoW_P1.js} +18 -18
  186. package/web/assets/src-jc8Ffy3T.js +1 -0
  187. package/web/index.html +2 -2
  188. package/web/assets/src-Df5C1Nz4.js +0 -1
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import type { PipelinePhase } from './types.js';
19
19
  import type { BindingAccumulator } from '../binding-accumulator.js';
20
- import type { ParsedFile } from '../../../_shared/index.js';
20
+ import type { ParsedFile, SupportedLanguages } from '../../../_shared/index.js';
21
21
  import type { ExtractedFetchCall, ExtractedRoute, ExtractedDecoratorRoute, ExtractedToolDef, ExtractedORMQuery, FetchWrapperDef } from '../workers/parse-worker.js';
22
22
  import type { MutableSemanticModel } from '../model/index.js';
23
23
  import type { ModuleConstants } from '../route-extractors/constant-resolver.js';
@@ -71,6 +71,12 @@ export interface ParseOutput {
71
71
  * costing ~58s on a 1000-file repo).
72
72
  */
73
73
  readonly parsedFiles: readonly ParsedFile[];
74
+ /**
75
+ * Content-derived language decisions for files such as `.h` whose extension
76
+ * is ambiguous. Scope resolution reuses this result rather than rereading
77
+ * source solely to classify it.
78
+ */
79
+ readonly contentLanguageByPath: ReadonlyMap<string, SupportedLanguages | null>;
74
80
  /**
75
81
  * Repo-wide string constants harvested by the providers that declare
76
82
  * `extractModuleConstants`, keyed by file path and already through each
@@ -625,6 +625,10 @@ function normalizeNodeLabel(kindStr) {
625
625
  switch (kindStr.toLowerCase()) {
626
626
  case 'class':
627
627
  return 'Class';
628
+ case 'protocol':
629
+ return 'Protocol';
630
+ case 'category':
631
+ return 'Category';
628
632
  case 'interface':
629
633
  return 'Interface';
630
634
  case 'enum':
@@ -615,6 +615,19 @@ export interface ScopeResolver {
615
615
  readonly populateWorkspaceOwners?: (parsedFiles: readonly ParsedFile[], ctx: {
616
616
  readonly fileContents: ReadonlyMap<string, string>;
617
617
  }) => void;
618
+ /**
619
+ * Optional workspace-wide enrichment of extracted reference sites. Runs
620
+ * after all files have been extracted and before reference finalization.
621
+ * Use this when a per-file capture needs conservative facts from an
622
+ * imported sibling (for example a compile-time branch constant).
623
+ */
624
+ readonly populateWorkspaceReferences?: (parsedFiles: ParsedFile[], ctx: {
625
+ readonly fileContents: ReadonlyMap<string, string>;
626
+ readonly treeCache?: {
627
+ get(filePath: string): unknown;
628
+ };
629
+ readonly resolutionConfig?: unknown;
630
+ }) => void;
618
631
  /**
619
632
  * Recognize a `super(...)`-style receiver text. Python returns
620
633
  * `/^super\s*\(/.test(t)`. Java returns `t === 'super'`. C++ may
@@ -56,6 +56,8 @@ export const CALLER_ANCHOR_LABELS = new Set([
56
56
  'Constructor',
57
57
  'Module',
58
58
  'Class',
59
+ 'Protocol',
60
+ 'Category',
59
61
  'Interface',
60
62
  'Struct',
61
63
  'Enum',
@@ -238,6 +238,8 @@ export const LINKABLE_LABELS = new Set([
238
238
  // targets and need the same def→graph bridge.
239
239
  'Module',
240
240
  'Class',
241
+ 'Protocol',
242
+ 'Category',
241
243
  'Interface',
242
244
  'Struct',
243
245
  'Enum',
@@ -17,9 +17,11 @@
17
17
  * registries only consult pre-finalize local bindings — imported names live
18
18
  * in finalized bindings (the same reason free calls need
19
19
  * `emitFreeCallFallback`) — so `resolveReferenceSites` skips `value-ref`
20
- * sites and this pass resolves them post-finalize via
21
- * `findCallableBindingInScope` (Function/Method/Constructor only — the
22
- * callable gate that keeps `{ port: DEFAULT_PORT }` from emitting anything).
20
+ * sites and this pass resolves them post-finalize (see
21
+ * `resolveValueRefTarget` — Function/Method/Constructor only, the callable
22
+ * gate that keeps `{ port: DEFAULT_PORT }` from emitting anything, and
23
+ * receiver-aware so a qualified reference binds the owner it was written
24
+ * with).
23
25
  *
24
26
  * Precision posture (mirrors `emitInterfaceDispatchFor`):
25
27
  * - reason `'property-dispatch'` keeps synthesized CALLS auditable;
@@ -35,11 +37,12 @@
35
37
  * Language-neutral: consumes only `value-ref` sites (any language that
36
38
  * emits the capture participates) and generic member-call sites.
37
39
  */
38
- import type { ParsedFile } from '../../../../_shared/index.js';
40
+ import type { ParsedFile, ReferenceSite, SymbolDefinition } from '../../../../_shared/index.js';
39
41
  import type { KnowledgeGraph } from '../../../graph/types.js';
40
42
  import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
41
43
  import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
42
44
  import type { CalleeIdSink } from '../graph-bridge/callee-id-sink.js';
45
+ import type { SemanticModel } from '../../model/semantic-model.js';
43
46
  /**
44
47
  * Keys registered by more than this many distinct functions are skipped —
45
48
  * dispatch through such a name says nothing about which function runs.
@@ -56,7 +59,77 @@ import type { CalleeIdSink } from '../graph-bridge/callee-id-sink.js';
56
59
  export declare const MAX_PROPERTY_DISPATCH_FANOUT: number;
57
60
  /** Below the 0.85 resolved baseline; same discount idea as interface-dispatch. */
58
61
  export declare const PROPERTY_DISPATCH_CONFIDENCE = 0.7;
59
- export declare function emitPropertyDispatchCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, calleeIdSink?: CalleeIdSink): {
62
+ /**
63
+ * Resolve a `value-ref` site to the callable it names.
64
+ *
65
+ * Two shapes, and the difference matters:
66
+ *
67
+ * - BARE (`{ handler: onClick }`, `register(onTick)`) — the name is resolved
68
+ * up the lexical chain, which is what an unqualified name means. Be exact
69
+ * about what that walk does, because it is not a plain lexical lookup:
70
+ * `findCallableBindingInScope` applies the callable predicate WHILE walking,
71
+ * so a scope binding the name to a parameter or a local contributes nothing
72
+ * and the walk continues outward. A nearer non-callable binding is stepped
73
+ * over — the same shape the qualified path guards against below, unguarded
74
+ * here. Pre-existing (#2437) and out of scope for #3399; reachable in JS/TS
75
+ * (`function outer(handler) { return { h: handler }; }` beside a top-level
76
+ * `function handler`), and narrow in Zig, which rejects a local shadowing a
77
+ * container declaration.
78
+ *
79
+ * - QUALIFIED (`bridge.accessor(Element.getNamespaceUri, …)`) — the source
80
+ * WROTE the owner, so the lexical chain is the wrong instrument. It gives
81
+ * local bindings precedence (`walkScopeChain`), so a nested container
82
+ * holding its own `getNamespaceUri` answers first and the registration is
83
+ * attached to a DIFFERENT function than the one written. That is a wrong
84
+ * edge, not a missing one — strictly worse for a tool whose value is that
85
+ * its edges can be trusted — and it is reachable in any language that
86
+ * allows a same-named callable in a nested container, Zig included.
87
+ *
88
+ * So a qualified site resolves through its receiver: name the owner, then take
89
+ * the member off that owner. An owner is either a MODULE or a CLASS-like
90
+ * container — `utils.compare` and `Element.getNamespaceUri` are the same shape
91
+ * written against the two kinds of namespace a language has, and the member-call
92
+ * path already resolves both (receiver-bound-calls Case 1 / Case 2). Both are
93
+ * tried here, MODULE FIRST: see `findNamespaceValueRefTarget` for why a module
94
+ * receiver cannot simply be declined, and the comment on the call below for why
95
+ * the container channel must not go first.
96
+ *
97
+ * If neither channel names the owner, or the owner is named but owns no such
98
+ * callable, this DECLINES rather than falling back to the lexical walk.
99
+ *
100
+ * Be precise about what declining costs, because it is more than one reference:
101
+ * a site that emits NO edge leaves no evidence for `impact`'s value-reference
102
+ * probe to read, so the target keeps `epistemic: "exact"` — silence, not a
103
+ * hedge. That is why the module channel above exists rather than being waved
104
+ * through as "just a decline". What remains declined is the case where the
105
+ * written receiver names nothing this index knows at all (a Zig `@This()` alias
106
+ * whose name differs from its container's, an owner from outside the workspace):
107
+ * there the alternative is not a hedge either, it is a confident edge to a
108
+ * lexically-nearer function that the source did not name, and a wrong edge is
109
+ * strictly worse than a missing one for a tool whose value is that its edges can
110
+ * be trusted.
111
+ *
112
+ * `CALL_TARGET_TYPES`, not a hand-rolled label set: `findOwnedMember` also
113
+ * answers with FIELDS, and a field named like the member would otherwise
114
+ * register as if it were the callable.
115
+ *
116
+ * Exported for `bench/value-ref-resolution/measure.mjs`, which gates both the
117
+ * resolved-target SET and the per-site cost of the four channels above across a
118
+ * 4x file-count step. It is the per-site half of the pass, so timing it in
119
+ * isolation is what makes a workspace-size dependency visible; timing
120
+ * `emitPropertyDispatchCalls` would fold that signal into edge emission, and
121
+ * re-implementing the channel order in the bench would pin the bench's idea of
122
+ * this function rather than this function.
123
+ */
124
+ export declare function resolveValueRefTarget(site: ReferenceSite, filePath: string, scopes: ScopeResolutionIndexes, model: SemanticModel, publishesImportedNames: boolean): SymbolDefinition | undefined;
125
+ export declare function emitPropertyDispatchCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, model: SemanticModel, calleeIdSink?: CalleeIdSink,
126
+ /**
127
+ * `ScopeResolver.namespaceExportsIncludeImportedNames`, forwarded rather than
128
+ * re-derived. The pass names no language; it asks the provider the same
129
+ * question `receiver-bound-calls` asks before resolving a namespace member,
130
+ * so the CALL and the REGISTRATION forms of `hub.fn` cannot disagree.
131
+ */
132
+ publishesImportedNames?: boolean): {
60
133
  usesEmitted: number;
61
134
  callsEmitted: number;
62
135
  skippedKeys: number;
@@ -17,9 +17,11 @@
17
17
  * registries only consult pre-finalize local bindings — imported names live
18
18
  * in finalized bindings (the same reason free calls need
19
19
  * `emitFreeCallFallback`) — so `resolveReferenceSites` skips `value-ref`
20
- * sites and this pass resolves them post-finalize via
21
- * `findCallableBindingInScope` (Function/Method/Constructor only — the
22
- * callable gate that keeps `{ port: DEFAULT_PORT }` from emitting anything).
20
+ * sites and this pass resolves them post-finalize (see
21
+ * `resolveValueRefTarget` — Function/Method/Constructor only, the callable
22
+ * gate that keeps `{ port: DEFAULT_PORT }` from emitting anything, and
23
+ * receiver-aware so a qualified reference binds the owner it was written
24
+ * with).
23
25
  *
24
26
  * Precision posture (mirrors `emitInterfaceDispatchFor`):
25
27
  * - reason `'property-dispatch'` keeps synthesized CALLS auditable;
@@ -36,7 +38,9 @@
36
38
  * emits the capture participates) and generic member-call sites.
37
39
  */
38
40
  import { tryEmitEdge } from '../graph-bridge/edges.js';
39
- import { findCallableBindingInScope } from '../scope/walkers.js';
41
+ import { findCallableBindingInScope, findClassBindingInScope, findOwnedMember, isNamespaceNameShadowed, isOwnerNameShadowedBySomethingElse, lookupBindingsAt, } from '../scope/walkers.js';
42
+ import { VALUE_REF_EDGE_REASON } from '../value-ref-edges.js';
43
+ import { CALL_TARGET_TYPES } from '../../model/symbol-table.js';
40
44
  /**
41
45
  * Keys registered by more than this many distinct functions are skipped —
42
46
  * dispatch through such a name says nothing about which function runs.
@@ -56,7 +60,258 @@ export const MAX_PROPERTY_DISPATCH_FANOUT = (() => {
56
60
  })();
57
61
  /** Below the 0.85 resolved baseline; same discount idea as interface-dispatch. */
58
62
  export const PROPERTY_DISPATCH_CONFIDENCE = 0.7;
59
- export function emitPropertyDispatchCalls(graph, scopes, parsedFiles, nodeLookup, calleeIdSink) {
63
+ /**
64
+ * Resolve a `value-ref` site to the callable it names.
65
+ *
66
+ * Two shapes, and the difference matters:
67
+ *
68
+ * - BARE (`{ handler: onClick }`, `register(onTick)`) — the name is resolved
69
+ * up the lexical chain, which is what an unqualified name means. Be exact
70
+ * about what that walk does, because it is not a plain lexical lookup:
71
+ * `findCallableBindingInScope` applies the callable predicate WHILE walking,
72
+ * so a scope binding the name to a parameter or a local contributes nothing
73
+ * and the walk continues outward. A nearer non-callable binding is stepped
74
+ * over — the same shape the qualified path guards against below, unguarded
75
+ * here. Pre-existing (#2437) and out of scope for #3399; reachable in JS/TS
76
+ * (`function outer(handler) { return { h: handler }; }` beside a top-level
77
+ * `function handler`), and narrow in Zig, which rejects a local shadowing a
78
+ * container declaration.
79
+ *
80
+ * - QUALIFIED (`bridge.accessor(Element.getNamespaceUri, …)`) — the source
81
+ * WROTE the owner, so the lexical chain is the wrong instrument. It gives
82
+ * local bindings precedence (`walkScopeChain`), so a nested container
83
+ * holding its own `getNamespaceUri` answers first and the registration is
84
+ * attached to a DIFFERENT function than the one written. That is a wrong
85
+ * edge, not a missing one — strictly worse for a tool whose value is that
86
+ * its edges can be trusted — and it is reachable in any language that
87
+ * allows a same-named callable in a nested container, Zig included.
88
+ *
89
+ * So a qualified site resolves through its receiver: name the owner, then take
90
+ * the member off that owner. An owner is either a MODULE or a CLASS-like
91
+ * container — `utils.compare` and `Element.getNamespaceUri` are the same shape
92
+ * written against the two kinds of namespace a language has, and the member-call
93
+ * path already resolves both (receiver-bound-calls Case 1 / Case 2). Both are
94
+ * tried here, MODULE FIRST: see `findNamespaceValueRefTarget` for why a module
95
+ * receiver cannot simply be declined, and the comment on the call below for why
96
+ * the container channel must not go first.
97
+ *
98
+ * If neither channel names the owner, or the owner is named but owns no such
99
+ * callable, this DECLINES rather than falling back to the lexical walk.
100
+ *
101
+ * Be precise about what declining costs, because it is more than one reference:
102
+ * a site that emits NO edge leaves no evidence for `impact`'s value-reference
103
+ * probe to read, so the target keeps `epistemic: "exact"` — silence, not a
104
+ * hedge. That is why the module channel above exists rather than being waved
105
+ * through as "just a decline". What remains declined is the case where the
106
+ * written receiver names nothing this index knows at all (a Zig `@This()` alias
107
+ * whose name differs from its container's, an owner from outside the workspace):
108
+ * there the alternative is not a hedge either, it is a confident edge to a
109
+ * lexically-nearer function that the source did not name, and a wrong edge is
110
+ * strictly worse than a missing one for a tool whose value is that its edges can
111
+ * be trusted.
112
+ *
113
+ * `CALL_TARGET_TYPES`, not a hand-rolled label set: `findOwnedMember` also
114
+ * answers with FIELDS, and a field named like the member would otherwise
115
+ * register as if it were the callable.
116
+ *
117
+ * Exported for `bench/value-ref-resolution/measure.mjs`, which gates both the
118
+ * resolved-target SET and the per-site cost of the four channels above across a
119
+ * 4x file-count step. It is the per-site half of the pass, so timing it in
120
+ * isolation is what makes a workspace-size dependency visible; timing
121
+ * `emitPropertyDispatchCalls` would fold that signal into edge emission, and
122
+ * re-implementing the channel order in the bench would pin the bench's idea of
123
+ * this function rather than this function.
124
+ */
125
+ export function resolveValueRefTarget(site, filePath, scopes, model, publishesImportedNames) {
126
+ const receiverName = site.explicitReceiver?.name;
127
+ if (receiverName === undefined) {
128
+ return findCallableBindingInScope(site.inScope, site.name, scopes);
129
+ }
130
+ // NAMESPACE FIRST, and the order is load-bearing. `findClassBindingInScope`
131
+ // does not stop at the scope chain: when its `isClassLike` walk misses — and a
132
+ // namespace handle binds a Module, so it always misses — it falls back to
133
+ // `scopes.qualifiedNames`, a WORKSPACE-wide index, and answers with the unique
134
+ // def of that name anywhere in the repo. Trying it first therefore lets a
135
+ // same-named container in a file this one never imported preempt the `@import`
136
+ // this file actually wrote:
137
+ //
138
+ // const dom_utils = @import("dom_utils.zig"); // namespace-only module
139
+ // … bridge.accessor(dom_utils.compare, …) // → decoy.zig's compare
140
+ //
141
+ // The shadow guard below cannot catch it: the import binds at MODULE scope,
142
+ // which the guard treats as the floor. An import written in this file is the
143
+ // strongest statement about what the name means here, so it outranks a global
144
+ // guess — and when the handle is not an import of this file, this answers
145
+ // nothing and the container channel runs exactly as before.
146
+ const viaNamespace = findNamespaceValueRefTarget(site, filePath, receiverName, scopes, publishesImportedNames);
147
+ if (viaNamespace !== undefined) {
148
+ // `'owned'` is NOT "no answer" — it is "this receiver is a namespace handle
149
+ // this file wrote, and it names no callable member". The two must not be
150
+ // conflated, because falling through from the second one reaches
151
+ // `findClassBindingInScope`, whose miss path answers from the WORKSPACE-wide
152
+ // qualified-name index: a same-named container in a file this one never
153
+ // imported then supplies the member the written module does not have. That
154
+ // is a confident edge into an unrelated file, and the owner-shadow guard
155
+ // below does not stop it — a plain `const utils = @import("utils.zig");`
156
+ // records a namespace IMPORT EDGE, not a module-scope binding, so the guard
157
+ // sees nothing bound under the name and reads the container as unshadowed.
158
+ // Verified with a fixture rather than argued: `dom_utils.onlyOnDecoy`, where
159
+ // `dom_utils.zig` has no such member and `decoy.zig` declares a same-named
160
+ // struct that does, minted `JsApi → onlyOnDecoy` before this line existed.
161
+ //
162
+ // The file said which module it meant. If that module does not expose the
163
+ // name as a callable, the honest answer is no edge.
164
+ return viaNamespace === 'owned' ? undefined : viaNamespace;
165
+ }
166
+ const owner = findClassBindingInScope(site.inScope, receiverName, scopes);
167
+ if (owner !== undefined) {
168
+ // The container lookup is a CLASS-ONLY walk: `walkScopeChain` filters by
169
+ // `isClassLike`, so it steps over a nearer binding that is a value and keeps
170
+ // climbing — and past the scope chain entirely, into a qualified-name
171
+ // fallback that answers with the unique workspace definition of the name.
172
+ // `fn f(Ticker: u8) { register(Ticker.fire) }` in a file that neither
173
+ // declares nor imports `Ticker` therefore resolves to some other file's
174
+ // `Ticker` container. That is the wrong-edge failure R1-2 exists to prevent,
175
+ // arriving through the class channel instead of the lexical one, and a
176
+ // registration pointing at a function the source never named is worse than
177
+ // no registration at all.
178
+ //
179
+ // So the name has to still MEAN that container at this site. The namespace
180
+ // channel below asks the same question through `isNamespaceNameShadowed`;
181
+ // a container needs the variant that exempts the container ITSELF, because
182
+ // `fn make() { const Local = struct {…}; register(Local.go); }` binds the
183
+ // name locally to the very def we resolved, and reading that as its own
184
+ // shadow would suppress the resolutions this path exists to make.
185
+ if (isOwnerNameShadowedBySomethingElse(receiverName, owner, site.inScope, scopes))
186
+ return undefined;
187
+ const member = findOwnedMember(owner.nodeId, site.name, model);
188
+ if (member === undefined || !CALL_TARGET_TYPES.has(member.type))
189
+ return undefined;
190
+ return member;
191
+ }
192
+ return undefined;
193
+ }
194
+ /**
195
+ * The second kind of owner: a namespace handle.
196
+ *
197
+ * `const utils = @import("utils.zig"); register(utils.compare);` — `utils` is a
198
+ * MODULE, not a class, so `findClassBindingInScope` answers nothing and the
199
+ * class path above declines. Declining here would be a silent hole rather than
200
+ * a conservative one: no USES edge is emitted, so
201
+ * `callableValueReferenceBoundaries` measures a real zero and `impact` on
202
+ * `compare` republishes `exact` — the very claim this feature exists to stop
203
+ * making. Nothing downstream can hedge on evidence that was never recorded.
204
+ *
205
+ * So resolve it, through the SAME channel the member-CALL path already trusts
206
+ * for `utils.compare()` (receiver-bound-calls Case 1): the file's namespace
207
+ * import edges name the target module, and the target module's own local
208
+ * module-scope bindings name its members. `utils.compare` and `utils.compare()`
209
+ * disagreeing about what `utils` is would be the anomaly.
210
+ *
211
+ * The same three guards Case 1 applies, for the same reasons:
212
+ * - a LOCAL declaration shadowing the handle suppresses the resolution
213
+ * (`isNamespaceNameShadowed`) — `fn f(utils: Decoy) { register(utils.compare) }`
214
+ * names the parameter's member, and resolving through the import would be a
215
+ * wrong edge rather than a missing one;
216
+ * - a locally declared member wins, and a name the target file merely IMPORTED
217
+ * counts only when the provider says its imports ARE its exports
218
+ * (`ScopeResolver.namespaceExportsIncludeImportedNames`). That opt-in is not
219
+ * a detail to skip: a Zig HUB — a file made only of re-exports, ghostty's
220
+ * `src/terminal/`, tigerbeetle's `stdx` — declares nothing, so requiring a
221
+ * local declaration declines every member reached through one. `hub.fn()`
222
+ * resolves and `register(hub.fn)` would not, and one name would mean two
223
+ * things depending on whether a `(` followed it. In languages that do not
224
+ * opt in, a module's imports are not its exports and this stays closed;
225
+ * - two distinct defs under one name resolve NOTHING. Never guess a namespace
226
+ * member — the whole point of reading the written receiver is precision.
227
+ *
228
+ * `CALL_TARGET_TYPES` gates the answer for the same reason the class path needs
229
+ * it: `utils.DEFAULT_PORT` is a module-scope binding too, and a registration
230
+ * table full of constants must keep emitting nothing.
231
+ */
232
+ function findNamespaceValueRefTarget(site, filePath, receiverName, scopes, publishesImportedNames) {
233
+ const moduleScopeId = scopes.moduleScopes.get(filePath);
234
+ if (moduleScopeId === undefined)
235
+ return undefined;
236
+ const targetFiles = [];
237
+ for (const edge of scopes.imports.get(moduleScopeId) ?? []) {
238
+ if (edge.kind !== 'namespace' || edge.localName !== receiverName)
239
+ continue;
240
+ if (edge.targetFile === null)
241
+ continue;
242
+ if (!targetFiles.includes(edge.targetFile))
243
+ targetFiles.push(edge.targetFile);
244
+ }
245
+ if (targetFiles.length === 0)
246
+ return undefined;
247
+ if (isNamespaceNameShadowed(receiverName, site.inScope, scopes))
248
+ return undefined;
249
+ /** The unique callable `select` finds across every target file, or nothing. */
250
+ const uniqueMember = (select) => {
251
+ let picked;
252
+ for (const targetFile of targetFiles) {
253
+ const targetScopeId = scopes.moduleScopes.get(targetFile);
254
+ if (targetScopeId === undefined)
255
+ continue;
256
+ for (const ref of select(targetScopeId)) {
257
+ if (!CALL_TARGET_TYPES.has(ref.def.type))
258
+ continue;
259
+ if (picked !== undefined && picked.nodeId !== ref.def.nodeId)
260
+ return 'ambiguous';
261
+ picked = ref.def;
262
+ }
263
+ }
264
+ return picked;
265
+ };
266
+ // A locally declared member first — same precedence `findExportedDef` states
267
+ // and `walkScopeChain` applies: what the target file DECLARED beats what it
268
+ // merely re-published.
269
+ const localRefs = (scope) => (scopes.bindings.get(scope)?.get(site.name) ?? []).filter((ref) => ref.origin === 'local');
270
+ const local = uniqueMember(localRefs);
271
+ if (local === 'ambiguous')
272
+ return 'owned';
273
+ if (local !== undefined)
274
+ return local;
275
+ if (!publishesImportedNames)
276
+ return 'owned';
277
+ // PRECEDENCE IS DECIDED BEFORE THE TYPE GATE, not by it. `uniqueMember`
278
+ // applies `CALL_TARGET_TYPES` while it selects, so a target file declaring a
279
+ // NON-callable under this name answers `undefined` above and would otherwise
280
+ // fall through to the published channel — publishing a re-exported callable
281
+ // under a name the module's own declaration owns. `findExportedDef` does not
282
+ // do that: it returns any local def it finds and lets its caller's type gate
283
+ // reject it, so `findExportedDefIncludingImportedNames` never reaches the
284
+ // imported names for a name the file declares. Same rule here, so `x.f` and
285
+ // `x.f()` cannot disagree about which module owns the name.
286
+ //
287
+ // Not reachable through valid Zig today — a container cannot declare a name
288
+ // twice, so one target file cannot hold both spellings, and Zig is the only
289
+ // provider that sets `namespaceExportsIncludeImportedNames`. It becomes
290
+ // reachable the moment a second provider opts in, or a receiver binds more
291
+ // than one target file; the guard is one `some` and the alternative failure
292
+ // is a confident edge into the wrong module.
293
+ const declaredLocally = targetFiles.some((targetFile) => {
294
+ const targetScopeId = scopes.moduleScopes.get(targetFile);
295
+ return targetScopeId !== undefined && localRefs(targetScopeId).length > 0;
296
+ });
297
+ if (declaredLocally)
298
+ return 'owned';
299
+ // Then a name the target file publishes but did not declare — the hub case.
300
+ // `lookupBindingsAt`, not `scopes.bindings`, because a hub's module scope owns
301
+ // no local binding for these names and the finalized/augmented channel is the
302
+ // only place they exist; the same read `findExportedDefIncludingImportedNames`
303
+ // does for the CALL form.
304
+ const published = uniqueMember((scope) => lookupBindingsAt(scope, site.name, scopes).filter((ref) => ref.origin === 'import' || ref.origin === 'namespace' || ref.origin === 'reexport'));
305
+ return published === 'ambiguous' || published === undefined ? 'owned' : published;
306
+ }
307
+ export function emitPropertyDispatchCalls(graph, scopes, parsedFiles, nodeLookup, model, calleeIdSink,
308
+ /**
309
+ * `ScopeResolver.namespaceExportsIncludeImportedNames`, forwarded rather than
310
+ * re-derived. The pass names no language; it asks the provider the same
311
+ * question `receiver-bound-calls` asks before resolving a namespace member,
312
+ * so the CALL and the REGISTRATION forms of `hub.fn` cannot disagree.
313
+ */
314
+ publishesImportedNames = false) {
60
315
  const seen = new Set();
61
316
  let usesEmitted = 0;
62
317
  // Sweep 1 — resolve every value-ref site: emit the USES registration edge
@@ -66,10 +321,10 @@ export function emitPropertyDispatchCalls(graph, scopes, parsedFiles, nodeLookup
66
321
  for (const site of parsed.referenceSites) {
67
322
  if (site.kind !== 'value-ref')
68
323
  continue;
69
- const def = findCallableBindingInScope(site.inScope, site.name, scopes);
324
+ const def = resolveValueRefTarget(site, parsed.filePath, scopes, model, publishesImportedNames);
70
325
  if (def === undefined)
71
326
  continue;
72
- const ok = tryEmitEdge(graph, scopes, nodeLookup, site, def, 'scope-resolution: value-ref', seen);
327
+ const ok = tryEmitEdge(graph, scopes, nodeLookup, site, def, VALUE_REF_EDGE_REASON, seen);
73
328
  if (ok)
74
329
  usesEmitted++;
75
330
  if (site.propertyKey === undefined)
@@ -55,6 +55,7 @@ const NOOP_OUTPUT = Object.freeze({
55
55
  /** Select source files that must be materialized for one resolver pass. */
56
56
  export function selectScopeSourcePathsToRead(provider, primaryFilePaths, preExtractedByPath) {
57
57
  const hasPostExtractHooks = provider.populateWorkspaceOwners !== undefined ||
58
+ provider.populateWorkspaceReferences !== undefined ||
58
59
  provider.populateNamespaceSiblings !== undefined ||
59
60
  provider.populateRangeBindings !== undefined ||
60
61
  provider.emitPostResolutionEdges !== undefined;
@@ -80,7 +81,7 @@ export const scopeResolutionPhase = {
80
81
  logHeapProbe('scopeResolution-enter');
81
82
  const { scannedFiles } = getPhaseOutput(deps, 'structure');
82
83
  const parseOutput = getPhaseOutput(deps, 'parse');
83
- const { model, parsedFiles: workerParsedFiles } = parseOutput;
84
+ const { model, parsedFiles: workerParsedFiles, contentLanguageByPath } = parseOutput;
84
85
  const scopeExtractionFailures = new Set(parseOutput.scopeExtractionFailures);
85
86
  // SemanticModel populated during `parse`: scope-resolution consumes
86
87
  // TypeRegistry / MethodRegistry / SymbolTable lookups instead of
@@ -146,7 +147,9 @@ export const scopeResolutionPhase = {
146
147
  // Language-agnostic: keyed by the provider-supplied SupportedLanguages value.
147
148
  const filesByLang = new Map();
148
149
  for (const f of scannedFiles) {
149
- const fileLang = getLanguageFromFilename(f.path);
150
+ const fileLang = contentLanguageByPath.has(f.path)
151
+ ? (contentLanguageByPath.get(f.path) ?? null)
152
+ : getLanguageFromFilename(f.path);
150
153
  if (fileLang === null)
151
154
  continue;
152
155
  // Tree-sitter providers require an available grammar. Standalone regex
@@ -26,6 +26,7 @@ import { swiftScopeResolver } from '../../languages/swift/scope-resolver.js';
26
26
  import { dartScopeResolver } from '../../languages/dart/scope-resolver.js';
27
27
  import { vueScopeResolver } from '../../languages/vue/scope-resolver.js';
28
28
  import { zigScopeResolver } from '../../languages/zig/scope-resolver.js';
29
+ import { objectiveCScopeResolver } from '../../languages/objective-c/scope-resolver.js';
29
30
  /** Map of `SupportedLanguages` → `ScopeResolver`. The scope-resolution phase
30
31
  * iterates this map directly — every registered resolver runs. This is the
31
32
  * single source of truth for which languages resolve via scope-resolution. */
@@ -47,4 +48,5 @@ export const SCOPE_RESOLVERS = new Map([
47
48
  [SupportedLanguages.Dart, dartScopeResolver],
48
49
  [SupportedLanguages.Vue, vueScopeResolver],
49
50
  [SupportedLanguages.Zig, zigScopeResolver],
51
+ [SupportedLanguages.ObjectiveC, objectiveCScopeResolver],
50
52
  ]);
@@ -312,6 +312,11 @@ export function runScopeResolution(input, provider) {
312
312
  }
313
313
  logHeapProbe('sr-extract-end', `lang=${provider.language} parsedFiles=${parsedFiles.length} preExtractedHits=${preExtractedHits} skipped=${filesSkipped}`);
314
314
  provider.populateWorkspaceOwners?.(parsedFiles, { fileContents: getFileContents() });
315
+ provider.populateWorkspaceReferences?.(parsedFiles, {
316
+ fileContents: getFileContents(),
317
+ treeCache,
318
+ resolutionConfig: input.resolutionConfig,
319
+ });
315
320
  // A callable-flow-only provider has no reason to build the whole-graph
316
321
  // lookup or finalize ordinary references when none of its files emitted a
317
322
  // callable fact. This keeps the opt-in path proportional to source scanning
@@ -760,7 +765,11 @@ export function runScopeResolution(input, provider) {
760
765
  // edges already emitted for a site retain ownership.
761
766
  const propertyDispatch = callableFlowOnly
762
767
  ? { usesEmitted: 0, callsEmitted: 0, skippedKeys: 0, skippedKeyNames: [] }
763
- : emitPropertyDispatchCalls(graph, indexes, emitParsedFiles, postHeritageNodeLookup, calleeIdAccumulator);
768
+ : emitPropertyDispatchCalls(graph, indexes, emitParsedFiles, postHeritageNodeLookup, readonlyModel, calleeIdAccumulator,
769
+ // Same provider hook the receiver-bound pass consults for a namespace
770
+ // member (Case 1). Without it a hub module's re-exported callable
771
+ // resolves when CALLED and declines when REGISTERED.
772
+ provider.namespaceExportsIncludeImportedNames === true);
764
773
  if (propertyDispatch.skippedKeys > 0) {
765
774
  // Never drop dispatch coverage silently: a hook table larger than the
766
775
  // fan-out cap means member calls through those keys get no synthesized
@@ -176,6 +176,41 @@ export declare function isReceiverOwnedButUnbound(startScope: ScopeId, receiverN
176
176
  * this walk wants.
177
177
  */
178
178
  export declare function isNamespaceNameShadowed(namespaceName: string, inScope: ScopeId, scopes: ScopeResolutionIndexes): boolean;
179
+ /**
180
+ * Does something between `inScope` and its module scope bind `name` to
181
+ * ANYTHING other than `def`?
182
+ *
183
+ * `isNamespaceNameShadowed` asks the same question for a namespace handle,
184
+ * where any local binding of the name is by definition not the import. A
185
+ * CONTAINER receiver needs the extra clause: the container may itself be the
186
+ * local declaration (`fn make() { const Local = struct {…}; … Local.go … }`),
187
+ * and reading that as its own shadow would suppress exactly the resolutions it
188
+ * is meant to permit — the #2723 mistake, one channel over.
189
+ *
190
+ * So a scope that binds the name answers immediately, and the answer is "not
191
+ * shadowed" only when one of that scope's bindings IS `def`. A name bound in a
192
+ * nearer scope to something else — a parameter, a local, a type binding — wins
193
+ * the lexical race, which is the whole point: `findClassBindingInScope` filters
194
+ * the chain by `isClassLike` and therefore cannot see that it lost it.
195
+ *
196
+ * The MODULE scope is inspected too, unlike `isNamespaceNameShadowed`, and the
197
+ * exemption is what makes that safe. That guard stops one rung short because a
198
+ * namespace import writes its own name into the module scope and would read as
199
+ * its own shadow (#2723); here the owner is compared by identity, so the binding
200
+ * that IS the owner exempts itself and only a binding to something ELSE answers
201
+ * `true`. Stopping short would leave the exact hole this walk exists to close:
202
+ * `findClassBindingInScope` steps over a module-scope binding that is not
203
+ * class-like and then answers from a WORKSPACE-wide qualified-name index, so
204
+ * `const Gauge = @import("other.zig").SOME_CONST;` in a file that never imports
205
+ * `Gauge.zig` would still resolve `Gauge.read` to that file's container.
206
+ * `lookupBindingsAt` is used at that scope and only there, because an imported
207
+ * alias lives in the finalized channel rather than in `scope.bindings`.
208
+ *
209
+ * Fail-closed like its sibling: a missing scope or a parent cycle answers
210
+ * `true`, because suppressing a resolution costs a missing edge while trusting a
211
+ * corrupt chain costs a wrong one.
212
+ */
213
+ export declare function isOwnerNameShadowedBySomethingElse(name: string, def: SymbolDefinition, inScope: ScopeId, scopes: ScopeResolutionIndexes): boolean;
179
214
  export declare function findReceiverTypeBinding(startScope: ScopeId, receiverName: string, scopes: ScopeResolutionIndexes): TypeRef | undefined;
180
215
  /**
181
216
  * Resolve a typeBinding for `name` from the per-namespace channel
@@ -195,9 +230,10 @@ export declare function moduleScopeIdOf(startScope: ScopeId, scopes: ScopeResolu
195
230
  /**
196
231
  * Look up a class-like binding by name in the given scope's chain.
197
232
  *
198
- * "Class-like" covers `Class | Interface | Struct | Record | Enum |
199
- * Trait` via the shared `isClassLike` predicate — every kind that
200
- * collapses to `@scope.class` in the scope-extractor query contract.
233
+ * "Class-like" covers `Class | Interface | Struct | Record | Enum | Trait |
234
+ * Protocol | Category` via the shared `isClassLike` predicate. Objective-C
235
+ * protocol and category definitions are graph-side containers rather than
236
+ * `@scope.class` captures.
201
237
  *
202
238
  * Walks the scope chain upward and consults TWO sources at each step:
203
239
  * 1. `scope.bindings` — populated during scope-extraction Pass 2 with