@backbencher/brain 0.0.0-stage → 0.1.1
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/CHANGELOG.md +298 -0
- package/EMBEDDINGS.md +355 -0
- package/LICENSE +21 -0
- package/README.md +415 -2
- package/bin/bb +13 -0
- package/dist/analysis-exclusions-7ZJDXOVZ.js +35 -0
- package/dist/brain-index-health-Q5APTJQL.js +17 -0
- package/dist/brain-relationship-enrichment-MYR2AM4F.js +558 -0
- package/dist/brain.sql-EIM4WS4D.js +29 -0
- package/dist/chunk-22V37YTK.js +1758 -0
- package/dist/chunk-2BSKD7O3.js +40 -0
- package/dist/chunk-2DE27DVC.js +1314 -0
- package/dist/chunk-2RRBMNBQ.js +322 -0
- package/dist/chunk-2WXYNLBA.js +29 -0
- package/dist/chunk-3CTFIJJB.js +17 -0
- package/dist/chunk-3ZTOHHSW.js +2104 -0
- package/dist/chunk-4H22TLOT.js +543 -0
- package/dist/chunk-5BVAH26D.js +340 -0
- package/dist/chunk-62DHLYW6.js +3427 -0
- package/dist/chunk-7AN7U5IZ.js +371 -0
- package/dist/chunk-7DRPNVIN.js +782 -0
- package/dist/chunk-7MBYT777.js +174 -0
- package/dist/chunk-AHDWGM7Q.js +236 -0
- package/dist/chunk-B5GEAAYF.js +818 -0
- package/dist/chunk-BEIZZ73B.js +1479 -0
- package/dist/chunk-BQ3OVQ3P.js +125 -0
- package/dist/chunk-D2Q4Y47A.js +53 -0
- package/dist/chunk-DIYAGQJL.js +34 -0
- package/dist/chunk-DWKXZ6IF.js +336 -0
- package/dist/chunk-DYM4FQEE.js +90 -0
- package/dist/chunk-ENW4BTBA.js +930 -0
- package/dist/chunk-EWMWK7WN.js +75 -0
- package/dist/chunk-EY5ZZUUA.js +626 -0
- package/dist/chunk-GBI7V7KY.js +69 -0
- package/dist/chunk-GEFFO6O2.js +152 -0
- package/dist/chunk-HJEWO5EV.js +247 -0
- package/dist/chunk-HQSPJDHI.js +613 -0
- package/dist/chunk-KAET47ZL.js +125 -0
- package/dist/chunk-KCDECRJY.js +440 -0
- package/dist/chunk-L5TDYKYK.js +76 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-NO3U3FJQ.js +201 -0
- package/dist/chunk-OWISBAOF.js +61 -0
- package/dist/chunk-QKTRECYS.js +1119 -0
- package/dist/chunk-QLK6ED4Q.js +235 -0
- package/dist/chunk-R2GW4F6R.js +19 -0
- package/dist/chunk-RCMTFSEF.js +87 -0
- package/dist/chunk-RM3PY6TG.js +376 -0
- package/dist/chunk-ROMM6KMC.js +433 -0
- package/dist/chunk-SGGCSHAX.js +12050 -0
- package/dist/chunk-SK6YVHFZ.js +211 -0
- package/dist/chunk-SM7RFQRO.js +214 -0
- package/dist/chunk-UGR4QIGS.js +67 -0
- package/dist/chunk-UQNQZXHY.js +711 -0
- package/dist/chunk-UUNHHX4M.js +13 -0
- package/dist/chunk-VL52PL2T.js +71 -0
- package/dist/chunk-WIG36E67.js +196 -0
- package/dist/chunk-XHK3WWGB.js +7436 -0
- package/dist/chunk-XTQM2IP3.js +2863 -0
- package/dist/chunk-ZE2LG76D.js +1296 -0
- package/dist/chunk-ZETTGPAW.js +6950 -0
- package/dist/cli/index.js +37 -0
- package/dist/config-HZAAYDP3.js +10 -0
- package/dist/content-search-7XIP555Q.js +593 -0
- package/dist/database-76RP44JG.js +9 -0
- package/dist/doctor-ANGETYMN.js +395 -0
- package/dist/edit-engine-CG72LDZD.js +51 -0
- package/dist/engine-BKEOGN7N.js +11 -0
- package/dist/engine-VIW6NSKO.js +18 -0
- package/dist/fingerprint-OBLIMZLP.js +33 -0
- package/dist/framework-intelligence-3LORTPOX.js +860 -0
- package/dist/freshness-3K6VU5OT.js +174 -0
- package/dist/graph-engine-HBSQNPTB.js +21 -0
- package/dist/graph_request-Q6NKB6N2.js +350 -0
- package/dist/impact_request-LYP672WE.js +68 -0
- package/dist/index-space-IUUN5WYV.js +15 -0
- package/dist/index-status-7OVGYSGW.js +34 -0
- package/dist/index.js +159 -0
- package/dist/instance-FO6QZRLH.js +19 -0
- package/dist/language-detection-DFCAU5DZ.js +11 -0
- package/dist/lsp/server.js +4301 -0
- package/dist/mcp/server.js +132 -0
- package/dist/onboard-DIQZV2NW.js +161 -0
- package/dist/override-companion-pass-XR7MHTP7.js +117 -0
- package/dist/parallel-indexer-FK2XKZXY.js +88 -0
- package/dist/pool-worker.js +1153 -0
- package/dist/project-health-6XPENVAA.js +94 -0
- package/dist/project-index-5SDPHITE.js +57 -0
- package/dist/project-registry-ZJ3DVUGO.js +355 -0
- package/dist/real-bash-analyzer-EJAU5LW5.js +2201 -0
- package/dist/real-c-analyzer-HBD3FNNH.js +5942 -0
- package/dist/real-clean-analyzer-4EEZLPV5.js +3470 -0
- package/dist/real-clojure-analyzer-Q55EBOO2.js +2818 -0
- package/dist/real-cpp-analyzer-IVVOD25R.js +4101 -0
- package/dist/real-csharp-analyzer-ZBNLNJZJ.js +3298 -0
- package/dist/real-css-analyzer-6XRQLUN7.js +15 -0
- package/dist/real-dart-analyzer-OLR72EFW.js +3944 -0
- package/dist/real-elixir-analyzer-MQWGAH2I.js +2192 -0
- package/dist/real-erlang-analyzer-7BVPQEHO.js +1731 -0
- package/dist/real-fsharp-analyzer-C2UPFNOA.js +2390 -0
- package/dist/real-go-analyzer-UKGI43YS.js +4126 -0
- package/dist/real-graphql-analyzer-VE2EEFII.js +1442 -0
- package/dist/real-groovy-analyzer-W26VACFW.js +2285 -0
- package/dist/real-haskell-analyzer-5CVVAJRX.js +2533 -0
- package/dist/real-hcl-analyzer-5ZT4U43N.js +725 -0
- package/dist/real-html-analyzer-JWNX3C6I.js +1759 -0
- package/dist/real-java-analyzer-GU42VBGQ.js +4677 -0
- package/dist/real-javascript-analyzer-QA4QOJ6W.js +14 -0
- package/dist/real-json-analyzer-MPA6PRXL.js +1640 -0
- package/dist/real-julia-analyzer-22CWYQMN.js +2085 -0
- package/dist/real-kotlin-analyzer-QDZJLXXU.js +3956 -0
- package/dist/real-lua-analyzer-G7NHLYKS.js +1957 -0
- package/dist/real-makefile-analyzer-OCY3GT5N.js +920 -0
- package/dist/real-markdown-analyzer-TEJK2JXJ.js +1215 -0
- package/dist/real-matlab-analyzer-Q5SWLN7Z.js +480 -0
- package/dist/real-nim-analyzer-YLXSK563.js +3603 -0
- package/dist/real-objective-c-analyzer-D23ACC2R.js +1518 -0
- package/dist/real-perl-analyzer-WWTRHYDT.js +1865 -0
- package/dist/real-php-analyzer-JNKJHTCL.js +9368 -0
- package/dist/real-powershell-analyzer-XRSNQERV.js +2915 -0
- package/dist/real-protobuf-analyzer-3OKUCZ7T.js +867 -0
- package/dist/real-python-analyzer-VXAFWI72.js +4880 -0
- package/dist/real-r-analyzer-ZGAIDVI4.js +2596 -0
- package/dist/real-ruby-analyzer-X2KTLVBH.js +2429 -0
- package/dist/real-rust-analyzer-OFFHPL2A.js +3359 -0
- package/dist/real-scala-analyzer-TSTW27J6.js +3293 -0
- package/dist/real-scss-analyzer-AVWYPSOQ.js +1515 -0
- package/dist/real-solidity-analyzer-UCI2KWUB.js +2608 -0
- package/dist/real-sql-analyzer-F77ZMQXR.js +3840 -0
- package/dist/real-svelte-analyzer-SF7NRUO5.js +2532 -0
- package/dist/real-swift-analyzer-MN5QT3QN.js +2311 -0
- package/dist/real-toml-analyzer-66L7BDCQ.js +929 -0
- package/dist/real-typescript-analyzer-6KEMVDWQ.js +14 -0
- package/dist/real-vb-analyzer-VICVPOP7.js +3145 -0
- package/dist/real-vba-analyzer-HXLTTHKM.js +466 -0
- package/dist/real-vue-analyzer-QXR56CFV.js +2680 -0
- package/dist/real-xml-analyzer-YL465ZJW.js +2405 -0
- package/dist/real-yaml-analyzer-SUBCBNSI.js +3528 -0
- package/dist/real-zig-analyzer-HIQLZMGY.js +2430 -0
- package/dist/regex-search-NJKHMZNO.js +239 -0
- package/dist/rules-ACHPB7QU.js +152 -0
- package/dist/scoped-path-MJYWJZQX.js +11 -0
- package/dist/setup-show-NT5ZHYNE.js +15 -0
- package/dist/setup-wizard-G5YYR5T6.js +24 -0
- package/dist/symbol-search-GZJIM2J2.js +16 -0
- package/dist/taint-KXUHHPIE.js +202 -0
- package/dist/taint-analyzer-N7YLZU3N.js +163 -0
- package/dist/tool-handlers-OJDC35DI.js +72 -0
- package/dist/tool-registry-BPEJNSZG.js +17 -0
- package/dist/tool-validation-Q57YMMBX.js +32 -0
- package/dist/type-inference-QMT2RMLV.js +13 -0
- package/dist/vector-binary-LMY4QGAN.js +19 -0
- package/dist/version-ABP2TUL2.js +9 -0
- package/dist/wasm-NMWO3WXY.js +16 -0
- package/docs/EMBEDDINGS.md +132 -0
- package/docs/README.md +178 -0
- package/docs/data/m10-baseline.json +602 -0
- package/docs/data/m10-benchmark-baseline.json +57 -0
- package/docs/data/m10-benchmark-last-run.json +127 -0
- package/docs/data/m10-final-audit.json +215 -0
- package/docs/data/m12-final-audit.json +138 -0
- package/docs/data/m12-surface-matrix.json +50 -0
- package/docs/data/public-tools.json +1902 -0
- package/docs/indexing/DEAD-ASSETS.md +88 -0
- package/docs/indexing/INDEXER-REVIEW.md +634 -0
- package/docs/indexing/PERFORMANCE-FINDINGS.md +106 -0
- package/docs/indexing/README.md +427 -0
- package/docs/indexing/discovery-matrix.md +41 -0
- package/docs/indexing/embeddings.md +325 -0
- package/docs/indexing/environment-variables.md +77 -0
- package/docs/indexing/performance.md +323 -0
- package/docs/indexing/resume.md +345 -0
- package/docs/indexing/troubleshooting.md +391 -0
- package/docs/man/bb.1 +190 -0
- package/docs/tools/bb_analyze.md +147 -0
- package/docs/tools/bb_annotate.md +110 -0
- package/docs/tools/bb_annotations.md +106 -0
- package/docs/tools/bb_ask.md +101 -0
- package/docs/tools/bb_cross_language.md +103 -0
- package/docs/tools/bb_doc.md +169 -0
- package/docs/tools/bb_edit.md +185 -0
- package/docs/tools/bb_graph.md +169 -0
- package/docs/tools/bb_health.md +156 -0
- package/docs/tools/bb_impact.md +117 -0
- package/docs/tools/bb_index.md +108 -0
- package/docs/tools/bb_migrate.md +135 -0
- package/docs/tools/bb_patterns.md +140 -0
- package/docs/tools/bb_search.md +150 -0
- package/docs/tools/bb_security.md +100 -0
- package/docs/tools/bb_visualize.md +184 -0
- package/grammars/README.md +57 -0
- package/grammars/THIRD-PARTY-NOTICES.md +50 -0
- package/grammars/tree-sitter-c/LICENSE +21 -0
- package/grammars/tree-sitter-clojure/LICENSE.md +21 -0
- package/grammars/tree-sitter-cpp/LICENSE +21 -0
- package/grammars/tree-sitter-csharp/LICENSE +21 -0
- package/grammars/tree-sitter-css/LICENSE +21 -0
- package/grammars/tree-sitter-css/README.md +21 -0
- package/grammars/tree-sitter-dart/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/LICENSE +20 -0
- package/grammars/tree-sitter-dart-upstream/README.md +18 -0
- package/grammars/tree-sitter-elixir/LICENSE +176 -0
- package/grammars/tree-sitter-elixir/README.md +11 -0
- package/grammars/tree-sitter-fsharp/LICENSE +21 -0
- package/grammars/tree-sitter-fsharp/README.md +45 -0
- package/grammars/tree-sitter-graphql/LICENSE +21 -0
- package/grammars/tree-sitter-graphql/README.md +106 -0
- package/grammars/tree-sitter-groovy/LICENSE +21 -0
- package/grammars/tree-sitter-groovy/README.md +4 -0
- package/grammars/tree-sitter-haskell/LICENSE +21 -0
- package/grammars/tree-sitter-haskell/README.md +394 -0
- package/grammars/tree-sitter-hcl/CHANGELOG.md +102 -0
- package/grammars/tree-sitter-hcl/LICENSE +201 -0
- package/grammars/tree-sitter-hcl/README.md +39 -0
- package/grammars/tree-sitter-html/LICENSE +21 -0
- package/grammars/tree-sitter-html/README.md +21 -0
- package/grammars/tree-sitter-julia/LICENSE +21 -0
- package/grammars/tree-sitter-julia/README.md +24 -0
- package/grammars/tree-sitter-kotlin/LICENSE +9 -0
- package/grammars/tree-sitter-lua/LICENSE.md +21 -0
- package/grammars/tree-sitter-lua/README.md +29 -0
- package/grammars/tree-sitter-make/LICENSE +21 -0
- package/grammars/tree-sitter-matlab/LICENSE +19 -0
- package/grammars/tree-sitter-matlab/README.md +99 -0
- package/grammars/tree-sitter-nim/LICENSE +373 -0
- package/grammars/tree-sitter-nim/README.md +46 -0
- package/grammars/tree-sitter-objc/LICENSE +21 -0
- package/grammars/tree-sitter-objc/README.md +20 -0
- package/grammars/tree-sitter-perl/LICENSE +7 -0
- package/grammars/tree-sitter-perl/README.md +100 -0
- package/grammars/tree-sitter-powershell/LICENSE +21 -0
- package/grammars/tree-sitter-powershell/README.md +7 -0
- package/grammars/tree-sitter-proto/LICENSE +21 -0
- package/grammars/tree-sitter-proto/README.md +105 -0
- package/grammars/tree-sitter-r/LICENSE +9 -0
- package/grammars/tree-sitter-r/README.md +66 -0
- package/grammars/tree-sitter-ruby/LICENSE +22 -0
- package/grammars/tree-sitter-scss/LICENSE +21 -0
- package/grammars/tree-sitter-scss/README.md +17 -0
- package/grammars/tree-sitter-solidity/LICENSE +7 -0
- package/grammars/tree-sitter-sql/LICENSE +21 -0
- package/grammars/tree-sitter-typescript/LICENSE +21 -0
- package/grammars/tree-sitter-vba/LICENSE +21 -0
- package/grammars/tree-sitter-vba/README.md +738 -0
- package/grammars/tree-sitter-vbnet/LICENSE +21 -0
- package/grammars/tree-sitter-vbnet/README.md +322 -0
- package/grammars/tree-sitter-xml/LICENSE +19 -0
- package/grammars/tree-sitter-yaml/LICENSE +20 -0
- package/grammars/tree-sitter-yaml/README.md +21 -0
- package/package.json +184 -4
- package/wasm/dart.wasm +0 -0
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-clean.wasm +0 -0
- package/wasm/tree-sitter-clojure.wasm +0 -0
- package/wasm/tree-sitter-core_schema.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-css.wasm +0 -0
- package/wasm/tree-sitter-dart.wasm +0 -0
- package/wasm/tree-sitter-elisp.wasm +0 -0
- package/wasm/tree-sitter-elixir.wasm +0 -0
- package/wasm/tree-sitter-elm.wasm +0 -0
- package/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/wasm/tree-sitter-erlang.wasm +0 -0
- package/wasm/tree-sitter-fsharp.wasm +0 -0
- package/wasm/tree-sitter-fsharp_signature.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-graphql.wasm +0 -0
- package/wasm/tree-sitter-groovy.wasm +0 -0
- package/wasm/tree-sitter-haskell.wasm +0 -0
- package/wasm/tree-sitter-hcl.wasm +0 -0
- package/wasm/tree-sitter-html.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-json.wasm +0 -0
- package/wasm/tree-sitter-json_schema.wasm +0 -0
- package/wasm/tree-sitter-julia.wasm +0 -0
- package/wasm/tree-sitter-kotlin.wasm +0 -0
- package/wasm/tree-sitter-lua.wasm +0 -0
- package/wasm/tree-sitter-make.wasm +0 -0
- package/wasm/tree-sitter-markdown.wasm +0 -0
- package/wasm/tree-sitter-markdown_inline.wasm +0 -0
- package/wasm/tree-sitter-matlab.wasm +0 -0
- package/wasm/tree-sitter-nim.wasm +0 -0
- package/wasm/tree-sitter-objc.wasm +0 -0
- package/wasm/tree-sitter-ocaml.wasm +0 -0
- package/wasm/tree-sitter-perl.wasm +0 -0
- package/wasm/tree-sitter-php.wasm +0 -0
- package/wasm/tree-sitter-php_only.wasm +0 -0
- package/wasm/tree-sitter-powershell.wasm +0 -0
- package/wasm/tree-sitter-protobuf.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ql.wasm +0 -0
- package/wasm/tree-sitter-r.wasm +0 -0
- package/wasm/tree-sitter-rescript.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-scala.wasm +0 -0
- package/wasm/tree-sitter-scss.wasm +0 -0
- package/wasm/tree-sitter-solidity.wasm +0 -0
- package/wasm/tree-sitter-sql.wasm +0 -0
- package/wasm/tree-sitter-svelte.wasm +0 -0
- package/wasm/tree-sitter-swift.wasm +0 -0
- package/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/wasm/tree-sitter-terraform.wasm +0 -0
- package/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/wasm/tree-sitter-toml.wasm +0 -0
- package/wasm/tree-sitter-tsx.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter-vba.wasm +0 -0
- package/wasm/tree-sitter-vbnet.wasm +0 -0
- package/wasm/tree-sitter-vue.wasm +0 -0
- package/wasm/tree-sitter-xml.wasm +0 -0
- package/wasm/tree-sitter-yaml.wasm +0 -0
- package/wasm/tree-sitter-zig.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
- package/wasm/tsx.wasm +0 -0
- package/wasm/typescript.wasm +0 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
import {
|
|
2
|
+
makeProvenance
|
|
3
|
+
} from "./chunk-UUNHHX4M.js";
|
|
4
|
+
import {
|
|
5
|
+
BrainDatabase
|
|
6
|
+
} from "./chunk-QKTRECYS.js";
|
|
7
|
+
import {
|
|
8
|
+
analysis_exclusions_exports
|
|
9
|
+
} from "./chunk-SM7RFQRO.js";
|
|
10
|
+
|
|
11
|
+
// src/intelligence/graph/line-address.ts
|
|
12
|
+
var STORED_LINE_COLUMN = "r.line_number";
|
|
13
|
+
var STORED_LINE_ALIAS = "bb_stored_call_line";
|
|
14
|
+
var STORED_LINE_SELECT = `${STORED_LINE_COLUMN} AS ${STORED_LINE_ALIAS}`;
|
|
15
|
+
var STORED_LINE_IS_EXACT_SQL = `typeof(${STORED_LINE_COLUMN}) = 'integer' AND ${STORED_LINE_COLUMN} > 0`;
|
|
16
|
+
var STORED_LINE_IS_ABSENT_SQL = `(${STORED_LINE_COLUMN} IS NULL OR (typeof(${STORED_LINE_COLUMN}) = 'integer' AND ${STORED_LINE_COLUMN} = 0))`;
|
|
17
|
+
function storedLineFromRow(row) {
|
|
18
|
+
if (row === null || row === void 0) return void 0;
|
|
19
|
+
return row[STORED_LINE_ALIAS];
|
|
20
|
+
}
|
|
21
|
+
var ADDRESS_NOTES = {
|
|
22
|
+
legacy: "call site not stored (legacy index: line_number is 0)",
|
|
23
|
+
notStored: "call site not stored (no line_number in the index)",
|
|
24
|
+
unusable: "call site not stored (unusable line_number in the index)",
|
|
25
|
+
substitute: "this is the calling symbol's declaration, not the call site",
|
|
26
|
+
/** L3's FOURTH reason, and it is not a store state: the store may well carry
|
|
27
|
+
* the line — this surface could not read the ROW that carries it. The
|
|
28
|
+
* distinct wording is load-bearing. A reader told "not stored" checks the
|
|
29
|
+
* index; a reader told "could not be read" checks the walk that quoted the
|
|
30
|
+
* evidence, and those are different investigations. Collapsing the two is
|
|
31
|
+
* how a declared absence starts reading as a store fact. */
|
|
32
|
+
unattributed: "call site not read (no relationship row at this depth could be attributed)"
|
|
33
|
+
};
|
|
34
|
+
function classifyStoredLine(raw) {
|
|
35
|
+
if (raw === null || raw === void 0) return "not-stored";
|
|
36
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || !Number.isInteger(raw)) return "unusable";
|
|
37
|
+
if (raw > 0) return "real";
|
|
38
|
+
if (raw === 0) return "legacy-placeholder";
|
|
39
|
+
return "unusable";
|
|
40
|
+
}
|
|
41
|
+
function deriveCallSiteAddress(input) {
|
|
42
|
+
const file = typeof input.file === "string" ? input.file : "";
|
|
43
|
+
const state = classifyStoredLine(input.storedLine);
|
|
44
|
+
if (state === "real") {
|
|
45
|
+
return { kind: "call-site", file, line: input.storedLine };
|
|
46
|
+
}
|
|
47
|
+
const callerLine = input.callerDeclaration?.line;
|
|
48
|
+
if (state !== "unusable" && classifyStoredLine(callerLine) === "real") {
|
|
49
|
+
return { kind: "declaration", file, line: callerLine, note: ADDRESS_NOTES.substitute };
|
|
50
|
+
}
|
|
51
|
+
const note = state === "legacy-placeholder" ? ADDRESS_NOTES.legacy : state === "unusable" ? ADDRESS_NOTES.unusable : ADDRESS_NOTES.notStored;
|
|
52
|
+
return {
|
|
53
|
+
kind: "unknown",
|
|
54
|
+
file,
|
|
55
|
+
line: null,
|
|
56
|
+
note
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function addressLine0(a) {
|
|
60
|
+
return a.kind === "call-site" ? a.line - 1 : null;
|
|
61
|
+
}
|
|
62
|
+
function isExactAddress(a) {
|
|
63
|
+
return a.kind === "call-site";
|
|
64
|
+
}
|
|
65
|
+
function unattributedAddress(file) {
|
|
66
|
+
return { kind: "unknown", file: typeof file === "string" ? file : "", line: null, note: ADDRESS_NOTES.unattributed };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/intelligence/graph/graph-engine.ts
|
|
70
|
+
var DEFAULT_REL_TYPES = [
|
|
71
|
+
"calls",
|
|
72
|
+
"extends",
|
|
73
|
+
"implements",
|
|
74
|
+
"uses",
|
|
75
|
+
"instantiates",
|
|
76
|
+
"overrides",
|
|
77
|
+
"contains",
|
|
78
|
+
"references",
|
|
79
|
+
"imports",
|
|
80
|
+
"exports",
|
|
81
|
+
// EXTENDED 2026-10-18, and they MUST be appended here in the same change that makes analyzers emit
|
|
82
|
+
// them: this array is the default filter, so an unlisted kind is dropped BEFORE the query runs.
|
|
83
|
+
// The measured size of that mistake is in the block above (22.2% of every emitted relationship on
|
|
84
|
+
// a 1,200-file ruby sample) and it would be exactly 100% for the four new kinds, because they have
|
|
85
|
+
// no other route into a default traversal.
|
|
86
|
+
"decorates",
|
|
87
|
+
"renders",
|
|
88
|
+
"mixes_in",
|
|
89
|
+
"part_of"
|
|
90
|
+
];
|
|
91
|
+
function relationshipFilterReason(relTypes) {
|
|
92
|
+
return `relationshipTypes:${[...relTypes].sort().join("|")}`;
|
|
93
|
+
}
|
|
94
|
+
var DEFAULT_MAX_DEPTH = 5;
|
|
95
|
+
var DEFAULT_BUDGET = 50;
|
|
96
|
+
var DEFAULT_WALK_TIME_BUDGET_MS = 0;
|
|
97
|
+
var EDGE_KIND_PRIORITY = {
|
|
98
|
+
// RENUMBERED 2026-10-18 to make room for the four extended kinds. The RELATIVE order of the ten is
|
|
99
|
+
// unchanged (calls < instantiates < extends < implements < overrides < uses < references < imports
|
|
100
|
+
// < exports < contains) — only the gaps are new, so no existing answer's ordering moves except
|
|
101
|
+
// where an extended kind is now in front of an old one (which is the point). The new kinds carry a
|
|
102
|
+
// dependency meaning and sit with the dependencies: `mixes_in` and `renders` compose a supertype /
|
|
103
|
+
// component (dependency), `decorates` applies a decorator to a declaration (dependency on the
|
|
104
|
+
// decorator), `part_of` is a compilation-unit inclusion (module-level, weakest for a traversal).
|
|
105
|
+
calls: 0,
|
|
106
|
+
instantiates: 2,
|
|
107
|
+
extends: 4,
|
|
108
|
+
implements: 6,
|
|
109
|
+
mixes_in: 7,
|
|
110
|
+
renders: 8,
|
|
111
|
+
overrides: 10,
|
|
112
|
+
decorates: 11,
|
|
113
|
+
uses: 12,
|
|
114
|
+
references: 14,
|
|
115
|
+
imports: 16,
|
|
116
|
+
exports: 18,
|
|
117
|
+
contains: 20,
|
|
118
|
+
part_of: 22
|
|
119
|
+
};
|
|
120
|
+
var UNRANKED_KIND_ORDER = 100;
|
|
121
|
+
var KIND_RANK_SQL = (
|
|
122
|
+
// `r.relationship_type`, not `relType`: the CASE is evaluated against the
|
|
123
|
+
// FROM tables, and an output alias is not in scope there. Inside a compound
|
|
124
|
+
// select that is a hard error — "no such column: relType" — which the corpus
|
|
125
|
+
// suite found before any reasoning about it would have.
|
|
126
|
+
"CASE r.relationship_type " + Object.entries(EDGE_KIND_PRIORITY).map(([kind, rank]) => `WHEN '${kind}' THEN ${rank}`).join(" ") + ` ELSE ${UNRANKED_KIND_ORDER} END AS kindRank `
|
|
127
|
+
);
|
|
128
|
+
var DEFAULT_EDGE_QUERY_LIMIT = 200;
|
|
129
|
+
var MAX_EDGE_QUERY_LIMIT = 5e3;
|
|
130
|
+
var DEFAULT_MAX_TOTAL_EDGES = 1e3;
|
|
131
|
+
var MAX_TOTAL_EDGES = 5e3;
|
|
132
|
+
var MAX_TOTAL_COUNT_SCAN = 5e4;
|
|
133
|
+
var STATEMENT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
function prepared(db, sql) {
|
|
135
|
+
let cache = STATEMENT_CACHE.get(db);
|
|
136
|
+
if (!cache) {
|
|
137
|
+
cache = /* @__PURE__ */ new Map();
|
|
138
|
+
STATEMENT_CACHE.set(db, cache);
|
|
139
|
+
}
|
|
140
|
+
let statement = cache.get(sql);
|
|
141
|
+
if (!statement) {
|
|
142
|
+
statement = db.prepare(sql);
|
|
143
|
+
cache.set(sql, statement);
|
|
144
|
+
}
|
|
145
|
+
return statement;
|
|
146
|
+
}
|
|
147
|
+
var EXTERNAL_ARM_SOURCE_INDEX = "brain_rel_proj_source_idx";
|
|
148
|
+
var OWNER_RANGE_INDEXES = ["brain_symbol_project_owner_idx", "brain_symbol_owner_idx"];
|
|
149
|
+
var SYMBOL_NAME_INDEX = "brain_sym_proj_name_idx";
|
|
150
|
+
var indexPresence = /* @__PURE__ */ new WeakMap();
|
|
151
|
+
function indexedBy(raw, indexName) {
|
|
152
|
+
if (raw === null || typeof raw !== "object") return "";
|
|
153
|
+
const db = raw;
|
|
154
|
+
if (typeof db.prepare !== "function") return "";
|
|
155
|
+
let presence = indexPresence.get(db);
|
|
156
|
+
if (presence === void 0) {
|
|
157
|
+
presence = /* @__PURE__ */ new Map();
|
|
158
|
+
indexPresence.set(db, presence);
|
|
159
|
+
}
|
|
160
|
+
let present = presence.get(indexName);
|
|
161
|
+
if (present === void 0) {
|
|
162
|
+
present = false;
|
|
163
|
+
try {
|
|
164
|
+
const rows = db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'index' AND name = ?").all(indexName);
|
|
165
|
+
present = rows.length > 0;
|
|
166
|
+
} catch {
|
|
167
|
+
present = false;
|
|
168
|
+
}
|
|
169
|
+
presence.set(indexName, present);
|
|
170
|
+
}
|
|
171
|
+
return present ? ` INDEXED BY ${indexName}` : "";
|
|
172
|
+
}
|
|
173
|
+
function externalArmIndexClause(raw) {
|
|
174
|
+
return indexedBy(raw, EXTERNAL_ARM_SOURCE_INDEX);
|
|
175
|
+
}
|
|
176
|
+
function cascadeBounds(symbolId) {
|
|
177
|
+
const prefix = symbolId.includes(":") ? symbolId.slice(0, symbolId.lastIndexOf(":") + 1) : "";
|
|
178
|
+
return { prefix, upper: prefix + ";" };
|
|
179
|
+
}
|
|
180
|
+
var CASCADE_INLINE_MAX = 400;
|
|
181
|
+
function ownerRangeSeek(db) {
|
|
182
|
+
for (const index of OWNER_RANGE_INDEXES) {
|
|
183
|
+
const hint = indexedBy(db, index);
|
|
184
|
+
if (hint) return hint;
|
|
185
|
+
}
|
|
186
|
+
return "";
|
|
187
|
+
}
|
|
188
|
+
function cascadeIds(db, projectId, symbolId) {
|
|
189
|
+
try {
|
|
190
|
+
const { prefix, upper } = cascadeBounds(symbolId);
|
|
191
|
+
const ownerSeek = ownerRangeSeek(db);
|
|
192
|
+
const rows = prepared(
|
|
193
|
+
db,
|
|
194
|
+
`SELECT ? AS id UNION ALL SELECT m.id FROM brain_symbol m${ownerSeek} WHERE m.owner_symbol_id >= ? AND m.owner_symbol_id < ? AND m.project_id = ? LIMIT ?`
|
|
195
|
+
).all(symbolId, prefix, upper, projectId, CASCADE_INLINE_MAX + 1);
|
|
196
|
+
if (rows.length > CASCADE_INLINE_MAX) return null;
|
|
197
|
+
return rows.map((row) => row.id);
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function cascadeInlineJoin(ids, column) {
|
|
203
|
+
const arms = ids.map(() => "SELECT ? AS id").join(" UNION ALL ");
|
|
204
|
+
return { sql: `JOIN (${arms}) casc ON casc.id = r.${column}`, params: ids };
|
|
205
|
+
}
|
|
206
|
+
function cascadeJoinFor(db, projectId, symbolId, column, cache) {
|
|
207
|
+
if (!cache.has(symbolId)) cache.set(symbolId, cascadeIds(db, projectId, symbolId));
|
|
208
|
+
const ids = cache.get(symbolId);
|
|
209
|
+
if (ids !== null && ids !== void 0) return cascadeInlineJoin(ids, column);
|
|
210
|
+
const { prefix, upper } = cascadeBounds(symbolId);
|
|
211
|
+
return {
|
|
212
|
+
sql: `JOIN (SELECT ? AS id UNION ALL SELECT m.id FROM brain_symbol m WHERE m.owner_symbol_id >= ? AND m.owner_symbol_id < ? AND m.project_id = ?) casc ON casc.id = r.${column}`,
|
|
213
|
+
params: [symbolId, prefix, upper, projectId]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function countUnattributedRows(db, projectId, rootId, relTypes, cascadeCache) {
|
|
217
|
+
try {
|
|
218
|
+
const relPlaceholders = relTypes.map(() => "?").join(",");
|
|
219
|
+
const callersJoin = cascadeJoinFor(db, projectId, rootId, "target_symbol_id", cascadeCache);
|
|
220
|
+
const callersRow = prepared(
|
|
221
|
+
db,
|
|
222
|
+
`SELECT COUNT(*) AS c FROM brain_relationship r
|
|
223
|
+
${callersJoin.sql}
|
|
224
|
+
WHERE r.project_id = ? AND r.relationship_type IN (${relPlaceholders})
|
|
225
|
+
AND r.source_symbol_id IS NULL`
|
|
226
|
+
).all(...callersJoin.params, projectId, ...relTypes)[0];
|
|
227
|
+
const calleesJoin = cascadeJoinFor(db, projectId, rootId, "source_symbol_id", cascadeCache);
|
|
228
|
+
const calleesRow = prepared(
|
|
229
|
+
db,
|
|
230
|
+
`SELECT COUNT(*) AS c FROM brain_relationship r
|
|
231
|
+
${calleesJoin.sql}
|
|
232
|
+
WHERE r.project_id = ? AND r.relationship_type IN (${relPlaceholders})
|
|
233
|
+
AND r.source_symbol_id IS NOT NULL AND r.target_symbol_id IS NULL AND r.target_external IS NULL`
|
|
234
|
+
).all(...calleesJoin.params, projectId, ...relTypes)[0];
|
|
235
|
+
return { callers: Number(callersRow?.c ?? 0), callees: Number(calleesRow?.c ?? 0) };
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function edgeMetadata(meta) {
|
|
241
|
+
if (!meta) return void 0;
|
|
242
|
+
try {
|
|
243
|
+
const parsed = JSON.parse(meta);
|
|
244
|
+
const out = {};
|
|
245
|
+
if (typeof parsed["evidenceClass"] === "string") out.evidenceClass = parsed["evidenceClass"];
|
|
246
|
+
if (typeof parsed["inferredAt"] === "number") {
|
|
247
|
+
out.enrichment = { inferredAt: parsed["inferredAt"] };
|
|
248
|
+
for (const key of ["role", "pattern", "layerFlow", "criticality", "frequency"]) {
|
|
249
|
+
if (typeof parsed[key] === "string") out.enrichment[key] = parsed[key];
|
|
250
|
+
}
|
|
251
|
+
for (const key of ["isTestCode", "isGeneratedCode", "isFrameworkContract", "isDependencyInjection"]) {
|
|
252
|
+
if (typeof parsed[key] === "boolean") out.enrichment[key] = parsed[key];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
256
|
+
} catch {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function cascadeMatch(row, tracedId) {
|
|
261
|
+
return row.matchedId == null || row.matchedId === tracedId ? "exact" : "owner-cascade";
|
|
262
|
+
}
|
|
263
|
+
function matchedMember(row, tracedId) {
|
|
264
|
+
if (row.matchedId == null || row.matchedId === tracedId) return void 0;
|
|
265
|
+
if (!row.matchedName) return void 0;
|
|
266
|
+
return { name: row.matchedName, file: row.matchedFile || "" };
|
|
267
|
+
}
|
|
268
|
+
function nodeFromEdge(row) {
|
|
269
|
+
const external = !row.symId;
|
|
270
|
+
return {
|
|
271
|
+
id: row.symId || `ext:${row.extName || "unknown"}`,
|
|
272
|
+
name: row.symName || row.extName || "unknown",
|
|
273
|
+
symbolType: row.symType || (external ? "external" : "symbol"),
|
|
274
|
+
file: row.fileRel || "",
|
|
275
|
+
filePath: row.filePath || "",
|
|
276
|
+
line: row.line || 0,
|
|
277
|
+
lineEnd: row.lineEnd || 0,
|
|
278
|
+
language: row.fileLang || "unknown",
|
|
279
|
+
complexity: row.complexity || 0,
|
|
280
|
+
isExternal: external
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async function traverseGraph(projectId, root, options = {}) {
|
|
284
|
+
const raw = await BrainDatabase.getRawInstance(projectId, { readonly: true });
|
|
285
|
+
const maxDepth = Math.max(1, Math.min(Math.floor(options.maxDepth || DEFAULT_MAX_DEPTH), 10));
|
|
286
|
+
const relTypes = options.relationshipTypes?.length ? options.relationshipTypes : DEFAULT_REL_TYPES;
|
|
287
|
+
const callerFiltered = !!options.relationshipTypes?.length;
|
|
288
|
+
const relPlaceholders = relTypes.map(() => "?").join(",");
|
|
289
|
+
const edgeLimit = Math.max(1, Math.min(Math.floor(options.edgeLimit ?? DEFAULT_EDGE_QUERY_LIMIT), MAX_EDGE_QUERY_LIMIT));
|
|
290
|
+
const maxEdges = Math.max(1, Math.min(Math.floor(options.maxEdges ?? DEFAULT_MAX_TOTAL_EDGES), MAX_TOTAL_EDGES));
|
|
291
|
+
let totalLimitReached = false;
|
|
292
|
+
const CALLER_ORDER = "kindRank, fileRel, lineNumber, rowId";
|
|
293
|
+
const CALLEE_ORDER = "kindRank, fileRel, lineNumber, rowId";
|
|
294
|
+
const nodes = {};
|
|
295
|
+
let truncated = false;
|
|
296
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
297
|
+
const noteReason = (reason) => {
|
|
298
|
+
reasons.add(reason);
|
|
299
|
+
truncated = true;
|
|
300
|
+
};
|
|
301
|
+
const timeBudgetMs = Math.max(0, Math.floor(options.timeBudgetMs ?? DEFAULT_WALK_TIME_BUDGET_MS));
|
|
302
|
+
const deadline = Date.now() + timeBudgetMs;
|
|
303
|
+
let timeBudgetExhausted = false;
|
|
304
|
+
const outOfTime = () => timeBudgetMs > 0 && Date.now() >= deadline;
|
|
305
|
+
const filterReason = callerFiltered ? relationshipFilterReason(relTypes) : null;
|
|
306
|
+
let maxDepthReached = 0;
|
|
307
|
+
let cappedQueries = 0;
|
|
308
|
+
let totalCallersIfKnown = null;
|
|
309
|
+
let totalCalleesIfKnown = null;
|
|
310
|
+
let totalsAreLowerBounds = false;
|
|
311
|
+
let noiseFilteredCallers = 0;
|
|
312
|
+
let noiseFilteredCallees = 0;
|
|
313
|
+
let crossLangExcludedCallers = 0;
|
|
314
|
+
let crossLangExcludedCallees = 0;
|
|
315
|
+
const survivesFilters = (row, name, includeCrossLanguage2) => {
|
|
316
|
+
if (!name || analysis_exclusions_exports.isNoiseSymbolName(name)) return false;
|
|
317
|
+
if (row.fileRel && analysis_exclusions_exports.isHardNoisePath(row.fileRel)) return false;
|
|
318
|
+
if (includeCrossLanguage2 === false && row.isCrossLang) return false;
|
|
319
|
+
return true;
|
|
320
|
+
};
|
|
321
|
+
const includeCrossLanguage = options.includeCrossLanguage !== false;
|
|
322
|
+
const countSurviving = (sqlWithoutLimit, params, nameOf) => {
|
|
323
|
+
let total = 0;
|
|
324
|
+
const scanned = prepared(raw, sqlWithoutLimit + " LIMIT ?").all(...params, MAX_TOTAL_COUNT_SCAN);
|
|
325
|
+
for (const row of scanned) {
|
|
326
|
+
if (survivesFilters(row, nameOf(row), includeCrossLanguage)) total++;
|
|
327
|
+
}
|
|
328
|
+
if (total >= MAX_TOTAL_COUNT_SCAN) totalsAreLowerBounds = true;
|
|
329
|
+
return total;
|
|
330
|
+
};
|
|
331
|
+
const matchedIdentities = /* @__PURE__ */ new Map();
|
|
332
|
+
const cascadeCache = /* @__PURE__ */ new Map();
|
|
333
|
+
const attachMatchedIdentities = (rows, tracedId) => {
|
|
334
|
+
const wanted = [];
|
|
335
|
+
for (const row of rows) {
|
|
336
|
+
const id = row.matchedId;
|
|
337
|
+
if (id == null || id === tracedId || matchedIdentities.has(id)) continue;
|
|
338
|
+
if (!wanted.includes(id)) wanted.push(id);
|
|
339
|
+
}
|
|
340
|
+
if (wanted.length === 0) return;
|
|
341
|
+
for (let i = 0; i < wanted.length; i += 400) {
|
|
342
|
+
const chunk = wanted.slice(i, i + 400);
|
|
343
|
+
const got = prepared(
|
|
344
|
+
raw,
|
|
345
|
+
"SELECT s.id AS id, s.name AS name, f.relative_path AS file FROM brain_symbol s LEFT JOIN brain_file f ON f.id = s.file_id WHERE s.id IN (" + chunk.map(() => "?").join(",") + ")"
|
|
346
|
+
).all(...chunk);
|
|
347
|
+
for (const g of got) matchedIdentities.set(g.id, { name: g.name, file: g.file || "" });
|
|
348
|
+
}
|
|
349
|
+
for (const row of rows) {
|
|
350
|
+
const id = row.matchedId;
|
|
351
|
+
if (id == null) continue;
|
|
352
|
+
const hit = matchedIdentities.get(id);
|
|
353
|
+
if (hit) {
|
|
354
|
+
row.matchedName = hit.name;
|
|
355
|
+
row.matchedFile = hit.file;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
const registerNode = (node) => {
|
|
360
|
+
if (!nodes[node.id]) nodes[node.id] = node;
|
|
361
|
+
};
|
|
362
|
+
registerNode({
|
|
363
|
+
id: root.id,
|
|
364
|
+
name: root.name,
|
|
365
|
+
symbolType: root.symbolType,
|
|
366
|
+
file: root.relativePath,
|
|
367
|
+
filePath: root.filePath,
|
|
368
|
+
line: root.line,
|
|
369
|
+
lineEnd: root.lineEnd,
|
|
370
|
+
language: root.language,
|
|
371
|
+
complexity: root.complexity,
|
|
372
|
+
isExternal: false
|
|
373
|
+
});
|
|
374
|
+
const emittedCallerRows = /* @__PURE__ */ new Set();
|
|
375
|
+
const emittedCallerSites = /* @__PURE__ */ new Set();
|
|
376
|
+
const emittedCalleeRows = /* @__PURE__ */ new Set();
|
|
377
|
+
const emittedCalleeSites = /* @__PURE__ */ new Set();
|
|
378
|
+
let collapsedCascadeRepeats = 0;
|
|
379
|
+
let collapsedSpellingVariants = 0;
|
|
380
|
+
let exactCallers = 0;
|
|
381
|
+
let cascadedCallers = 0;
|
|
382
|
+
let exactCallees = 0;
|
|
383
|
+
let cascadedCallees = 0;
|
|
384
|
+
let deeperCallers = 0;
|
|
385
|
+
let deeperCallees = 0;
|
|
386
|
+
let callerBudgetExhausted = false;
|
|
387
|
+
let calleeBudgetExhausted = false;
|
|
388
|
+
let edgesInBothDirections = 0;
|
|
389
|
+
const callers = [];
|
|
390
|
+
const visitedCallers = /* @__PURE__ */ new Set();
|
|
391
|
+
let callerBudget = options.callerBudget ?? DEFAULT_BUDGET;
|
|
392
|
+
const traceCallers = (symbolId, depth) => {
|
|
393
|
+
if (depth >= maxDepth || visitedCallers.has(symbolId)) return;
|
|
394
|
+
if (outOfTime()) {
|
|
395
|
+
timeBudgetExhausted = true;
|
|
396
|
+
noteReason("walk-time-budget-exhausted");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
visitedCallers.add(symbolId);
|
|
400
|
+
const callerCascade = cascadeJoinFor(raw, projectId, symbolId, "target_symbol_id", cascadeCache);
|
|
401
|
+
const callerSql = "SELECT r.id AS rowId, r.line_number AS lineNumber, r.relationship_type AS relType, r.confidence AS confidence, r.is_cross_language AS isCrossLang, r.bridge_mechanism AS bridge, r.metadata AS meta, s.id AS symId, s.name AS symName, s.line_start AS line, s.line_end AS lineEnd, s.complexity AS complexity, s.symbol_type AS symType, f.relative_path AS fileRel, f.path AS filePath, f.language AS fileLang, f.relative_path AS srcFileRel, NULL AS extName, r.target_basis AS targetBasis, casc.id AS matchedId, " + KIND_RANK_SQL + "FROM brain_relationship r JOIN brain_symbol s ON s.id = r.source_symbol_id AND s.project_id = r.project_id JOIN brain_file f ON f.id = s.file_id " + // A JOIN, not `target_symbol_id IN (cascade)`. The IN form filters
|
|
402
|
+
// correctly but discards WHICH id matched, and that is the only thing
|
|
403
|
+
// separating "someone calls Command" from "someone calls
|
|
404
|
+
// Command.Execute, attributed to its owner". The join is over the
|
|
405
|
+
// cascade's own ID LIST (see cascadeJoinFor), so `casc.id` is still the
|
|
406
|
+
// id that matched while the statement no longer re-materialises the
|
|
407
|
+
// owner range.
|
|
408
|
+
callerCascade.sql + " WHERE r.project_id = ? AND r.relationship_type IN (" + relPlaceholders + ")";
|
|
409
|
+
const callerParams = [...callerCascade.params, projectId, ...relTypes];
|
|
410
|
+
const fetched = prepared(raw, callerSql + " ORDER BY " + CALLER_ORDER + " LIMIT ?").all(
|
|
411
|
+
...callerParams,
|
|
412
|
+
edgeLimit + 1
|
|
413
|
+
);
|
|
414
|
+
const capped = fetched.length > edgeLimit;
|
|
415
|
+
const rows = capped ? fetched.slice(0, edgeLimit) : fetched;
|
|
416
|
+
attachMatchedIdentities(rows, symbolId);
|
|
417
|
+
if (capped) {
|
|
418
|
+
cappedQueries++;
|
|
419
|
+
noteReason(depth === 0 ? "caller-site-limit" : "caller-site-limit-at-depth");
|
|
420
|
+
if (depth === 0 && totalCallersIfKnown === null && !outOfTime()) {
|
|
421
|
+
totalCallersIfKnown = countSurviving(callerSql + " ORDER BY " + CALLER_ORDER, callerParams, (r) => r.symName);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const children = [];
|
|
425
|
+
for (const row of rows) {
|
|
426
|
+
if (!row.symName || analysis_exclusions_exports.isNoiseSymbolName(row.symName)) {
|
|
427
|
+
noiseFilteredCallers++;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (row.fileRel && analysis_exclusions_exports.isHardNoisePath(row.fileRel)) {
|
|
431
|
+
noiseFilteredCallers++;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (options.includeCrossLanguage === false && row.isCrossLang) {
|
|
435
|
+
crossLangExcludedCallers++;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (callers.length + callees.length >= maxEdges) {
|
|
439
|
+
totalLimitReached = true;
|
|
440
|
+
noteReason("total-edge-limit");
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
if (emittedCallerRows.has(row.rowId)) {
|
|
444
|
+
collapsedCascadeRepeats++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
emittedCallerRows.add(row.rowId);
|
|
448
|
+
if (row.lineNumber != null && row.lineNumber > 0) {
|
|
449
|
+
const siteKey = `${row.relType}|${row.symId ?? ""}|${row.matchedId ?? symbolId}|${row.srcFileRel ?? ""}|${row.lineNumber}`;
|
|
450
|
+
if (emittedCallerSites.has(siteKey)) {
|
|
451
|
+
collapsedSpellingVariants++;
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
emittedCallerSites.add(siteKey);
|
|
455
|
+
}
|
|
456
|
+
if (depth > 0) deeperCallers++;
|
|
457
|
+
else if (cascadeMatch(row, symbolId) === "exact") exactCallers++;
|
|
458
|
+
else cascadedCallers++;
|
|
459
|
+
const node = nodeFromEdge(row);
|
|
460
|
+
registerNode(node);
|
|
461
|
+
callers.push({
|
|
462
|
+
from: node.id,
|
|
463
|
+
to: symbolId,
|
|
464
|
+
relationshipType: row.relType,
|
|
465
|
+
confidence: row.confidence ?? 0,
|
|
466
|
+
isCrossLanguage: !!row.isCrossLang,
|
|
467
|
+
bridgeMechanism: row.bridge || void 0,
|
|
468
|
+
// The address owner decides what this position
|
|
469
|
+
// IS. A stored line of 0 (the indexer's legacy literal) or NULL is
|
|
470
|
+
// NOT a position — the calling symbol's declaration is only ever a
|
|
471
|
+
// LABELED substitute, never silently presented as the call site.
|
|
472
|
+
callSite: deriveCallSiteAddress({
|
|
473
|
+
storedLine: row.lineNumber,
|
|
474
|
+
file: row.srcFileRel || node.file,
|
|
475
|
+
callerDeclaration: { line: node.line }
|
|
476
|
+
}),
|
|
477
|
+
depth,
|
|
478
|
+
rowId: row.rowId,
|
|
479
|
+
// Whether the stored row names the traced symbol or one of its
|
|
480
|
+
// members. Without it, a class with 180 methods answers every caller
|
|
481
|
+
// question as if all 180 methods were the class.
|
|
482
|
+
targetMatch: cascadeMatch(row, symbolId),
|
|
483
|
+
targetBasis: row.targetBasis === "symbol" || row.targetBasis === "external" || row.targetBasis === "unresolved" ? row.targetBasis : null,
|
|
484
|
+
matchedMember: matchedMember(row, symbolId),
|
|
485
|
+
metadata: edgeMetadata(row.meta)
|
|
486
|
+
});
|
|
487
|
+
maxDepthReached = Math.max(maxDepthReached, depth);
|
|
488
|
+
if (row.symId) children.push(row.symId);
|
|
489
|
+
}
|
|
490
|
+
for (const childId of children) {
|
|
491
|
+
if (visitedCallers.has(childId)) continue;
|
|
492
|
+
if (callerBudget <= 0) {
|
|
493
|
+
if (children.some((c) => !visitedCallers.has(c))) {
|
|
494
|
+
noteReason("caller-budget-exhausted");
|
|
495
|
+
callerBudgetExhausted = true;
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (depth >= maxDepth - 1) {
|
|
500
|
+
if (children.some((c) => !visitedCallers.has(c))) noteReason("caller-depth-cap-reached");
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
callerBudget--;
|
|
504
|
+
traceCallers(childId, depth + 1);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const callees = [];
|
|
508
|
+
const visitedCallees = /* @__PURE__ */ new Set();
|
|
509
|
+
let calleeBudget = options.calleeBudget ?? DEFAULT_BUDGET;
|
|
510
|
+
const traceCallees = (symbolId, srcFileRel, srcLine, depth) => {
|
|
511
|
+
if (depth >= maxDepth || visitedCallees.has(symbolId)) return;
|
|
512
|
+
if (outOfTime()) {
|
|
513
|
+
timeBudgetExhausted = true;
|
|
514
|
+
noteReason("walk-time-budget-exhausted");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
visitedCallees.add(symbolId);
|
|
518
|
+
const isExternal = symbolId.startsWith("ext:");
|
|
519
|
+
const calleeCascade = isExternal ? null : cascadeJoinFor(raw, projectId, symbolId, "source_symbol_id", cascadeCache);
|
|
520
|
+
const selectCols = "SELECT r.id AS rowId, r.line_number AS lineNumber, r.relationship_type AS relType, r.confidence AS confidence, r.is_cross_language AS isCrossLang, r.bridge_mechanism AS bridge, r.metadata AS meta, s.id AS symId, s.name AS symName, s.line_start AS line, s.line_end AS lineEnd, s.complexity AS complexity, s.symbol_type AS symType, f.relative_path AS fileRel, f.path AS filePath, f.language AS fileLang, src.relative_path AS srcFileRel, r.target_external AS extName, r.target_basis AS targetBasis, casc.id AS matchedId, " + KIND_RANK_SQL;
|
|
521
|
+
const selectColsUnresolved = "SELECT r.id AS rowId, r.line_number AS lineNumber, r.relationship_type AS relType, r.confidence AS confidence, r.is_cross_language AS isCrossLang, r.bridge_mechanism AS bridge, r.metadata AS meta, NULL AS symId, NULL AS symName, NULL AS line, NULL AS lineEnd, NULL AS complexity, NULL AS symType, NULL AS fileRel, NULL AS filePath, NULL AS fileLang, src.relative_path AS srcFileRel, r.target_external AS extName, r.target_basis AS targetBasis, casc.id AS matchedId, " + KIND_RANK_SQL;
|
|
522
|
+
let rows;
|
|
523
|
+
if (isExternal) {
|
|
524
|
+
rows = [];
|
|
525
|
+
} else {
|
|
526
|
+
const calleeSql = selectCols + "FROM brain_relationship r JOIN brain_symbol s ON s.id = r.target_symbol_id AND s.project_id = r.project_id JOIN brain_file f ON f.id = s.file_id LEFT JOIN brain_symbol srcsym ON srcsym.id = r.source_symbol_id LEFT JOIN brain_file src ON src.id = srcsym.file_id " + calleeCascade.sql + " WHERE r.project_id = ? AND r.relationship_type IN (" + relPlaceholders + ") AND r.target_symbol_id IS NOT NULL UNION ALL " + // The UNRESOLVED arm. `target_symbol_id IS NULL` is the store saying
|
|
527
|
+
// it could not resolve this target, and `target_external` is the NAME
|
|
528
|
+
// it saw — not an identity.
|
|
529
|
+
//
|
|
530
|
+
// This arm used to JOIN brain_symbol back on that name, which turned
|
|
531
|
+
// the admission into a claim. Measured on real corpora, the name it
|
|
532
|
+
// joined on is not even close to unique: vscode fans a single stored
|
|
533
|
+
// row out to 37,876 internal symbols for the name `match`, svelte to
|
|
534
|
+
// 437,570 for `end`. The tool then emitted those as edges — asked
|
|
535
|
+
// about typescript's `pseudoTypeEquivalentToType` it reported 27
|
|
536
|
+
// internal callees, the first of them `isErrorType` in a different
|
|
537
|
+
// file, when the stored row names `getTypeOfSymbol`. No such
|
|
538
|
+
// relationship exists.
|
|
539
|
+
//
|
|
540
|
+
// Three harms, one JOIN: fabricated edges; the 200-row cap consumed
|
|
541
|
+
// entirely by fan-out so real callees never appear (and
|
|
542
|
+
// `calleesIfUncapped` reporting the inflated number); and
|
|
543
|
+
// `children.push` recursing into the arbitrary match, contaminating
|
|
544
|
+
// everything downstream.
|
|
545
|
+
//
|
|
546
|
+
// An unresolved target is therefore reported as the `ext:<name>` node
|
|
547
|
+
// this contract already documents for it. The name is kept — it is
|
|
548
|
+
// the node's name — so nothing is lost; it is simply labelled for
|
|
549
|
+
// what it is.
|
|
550
|
+
selectColsUnresolved + "FROM brain_relationship r" + externalArmIndexClause(raw) + " LEFT JOIN brain_symbol srcsym ON srcsym.id = r.source_symbol_id LEFT JOIN brain_file src ON src.id = srcsym.file_id " + calleeCascade.sql + " WHERE r.project_id = ? AND r.relationship_type IN (" + relPlaceholders + ") AND r.target_symbol_id IS NULL AND r.target_external IS NOT NULL";
|
|
551
|
+
const calleeParams = [
|
|
552
|
+
...calleeCascade.params,
|
|
553
|
+
projectId,
|
|
554
|
+
...relTypes,
|
|
555
|
+
...calleeCascade.params,
|
|
556
|
+
projectId,
|
|
557
|
+
...relTypes
|
|
558
|
+
];
|
|
559
|
+
const calleeOrder = " ORDER BY " + CALLEE_ORDER;
|
|
560
|
+
const fetched = prepared(raw, calleeSql + calleeOrder + " LIMIT ?").all(...calleeParams, edgeLimit + 1);
|
|
561
|
+
const capped = fetched.length > edgeLimit;
|
|
562
|
+
rows = capped ? fetched.slice(0, edgeLimit) : fetched;
|
|
563
|
+
attachMatchedIdentities(rows, symbolId);
|
|
564
|
+
if (capped) {
|
|
565
|
+
cappedQueries++;
|
|
566
|
+
noteReason(depth === 0 ? "callee-site-limit" : "callee-site-limit-at-depth");
|
|
567
|
+
if (depth === 0 && totalCalleesIfKnown === null && !outOfTime()) {
|
|
568
|
+
totalCalleesIfKnown = countSurviving(calleeSql + calleeOrder, calleeParams, (r) => r.symName || r.extName);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const children = [];
|
|
573
|
+
for (const row of rows) {
|
|
574
|
+
const targetName = row.symName || row.extName || "";
|
|
575
|
+
if (!targetName || analysis_exclusions_exports.isNoiseSymbolName(targetName)) {
|
|
576
|
+
noiseFilteredCallees++;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (row.fileRel && analysis_exclusions_exports.isHardNoisePath(row.fileRel)) {
|
|
580
|
+
noiseFilteredCallees++;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (options.includeCrossLanguage === false && row.isCrossLang) {
|
|
584
|
+
crossLangExcludedCallees++;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (callers.length + callees.length >= maxEdges) {
|
|
588
|
+
totalLimitReached = true;
|
|
589
|
+
noteReason("total-edge-limit");
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
if (emittedCalleeRows.has(row.rowId)) {
|
|
593
|
+
collapsedCascadeRepeats++;
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
emittedCalleeRows.add(row.rowId);
|
|
597
|
+
if (row.lineNumber != null && row.lineNumber > 0) {
|
|
598
|
+
const calleeKey = `${row.relType}|${row.matchedId ?? symbolId}|${row.symId ?? `ext:${row.extName ?? ""}`}|${row.srcFileRel ?? ""}|${row.lineNumber}`;
|
|
599
|
+
if (emittedCalleeSites.has(calleeKey)) {
|
|
600
|
+
collapsedSpellingVariants++;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
emittedCalleeSites.add(calleeKey);
|
|
604
|
+
}
|
|
605
|
+
if (depth > 0) deeperCallees++;
|
|
606
|
+
else if (cascadeMatch(row, symbolId) === "exact") exactCallees++;
|
|
607
|
+
else cascadedCallees++;
|
|
608
|
+
const node = nodeFromEdge(row);
|
|
609
|
+
registerNode(node);
|
|
610
|
+
callees.push({
|
|
611
|
+
from: symbolId,
|
|
612
|
+
to: node.id,
|
|
613
|
+
relationshipType: row.relType,
|
|
614
|
+
confidence: row.confidence ?? 0,
|
|
615
|
+
isCrossLanguage: !!row.isCrossLang,
|
|
616
|
+
bridgeMechanism: row.bridge || void 0,
|
|
617
|
+
// Same rule as the callers side — the calling
|
|
618
|
+
// symbol's declaration (srcLine) is a labeled substitute only.
|
|
619
|
+
callSite: deriveCallSiteAddress({
|
|
620
|
+
storedLine: row.lineNumber,
|
|
621
|
+
file: row.srcFileRel || srcFileRel,
|
|
622
|
+
callerDeclaration: { line: srcLine }
|
|
623
|
+
}),
|
|
624
|
+
depth,
|
|
625
|
+
rowId: row.rowId,
|
|
626
|
+
sourceMatch: cascadeMatch(row, symbolId),
|
|
627
|
+
targetBasis: row.targetBasis === "symbol" || row.targetBasis === "external" || row.targetBasis === "unresolved" ? row.targetBasis : null,
|
|
628
|
+
matchedMember: matchedMember(row, symbolId),
|
|
629
|
+
metadata: edgeMetadata(row.meta)
|
|
630
|
+
});
|
|
631
|
+
maxDepthReached = Math.max(maxDepthReached, depth);
|
|
632
|
+
if (row.symId) children.push({ id: row.symId, file: node.file, line: node.line });
|
|
633
|
+
}
|
|
634
|
+
for (const child of children) {
|
|
635
|
+
if (visitedCallees.has(child.id)) continue;
|
|
636
|
+
if (calleeBudget <= 0) {
|
|
637
|
+
if (children.some((c) => !visitedCallees.has(c.id))) {
|
|
638
|
+
noteReason("callee-budget-exhausted");
|
|
639
|
+
calleeBudgetExhausted = true;
|
|
640
|
+
}
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
if (depth >= maxDepth - 1) {
|
|
644
|
+
if (children.some((c) => !visitedCallees.has(c.id))) noteReason("callee-depth-cap-reached");
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
calleeBudget--;
|
|
648
|
+
traceCallees(child.id, child.file, child.line, depth + 1);
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
if (options.direction !== "callees") traceCallers(root.id, 0);
|
|
652
|
+
if (options.direction !== "callers") traceCallees(root.id, root.relativePath, root.line, 0);
|
|
653
|
+
for (const edge of callers) {
|
|
654
|
+
if (emittedCalleeRows.has(edge.rowId)) edgesInBothDirections++;
|
|
655
|
+
}
|
|
656
|
+
const edgesByType = {};
|
|
657
|
+
for (const edge of [...callers, ...callees]) {
|
|
658
|
+
edgesByType[edge.relationshipType] = (edgesByType[edge.relationshipType] ?? 0) + 1;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
callers,
|
|
662
|
+
callees,
|
|
663
|
+
nodes,
|
|
664
|
+
truncated,
|
|
665
|
+
truncationReason: reasons.size > 0 ? [...reasons].join("+") : void 0,
|
|
666
|
+
truncationReasons: [...reasons],
|
|
667
|
+
filterNarrowed: filterReason !== null,
|
|
668
|
+
filterReason,
|
|
669
|
+
maxDepthReached,
|
|
670
|
+
maxDepth,
|
|
671
|
+
timeBudgetMs,
|
|
672
|
+
timeBudgetExhausted,
|
|
673
|
+
noiseFiltered: { callers: noiseFilteredCallers, callees: noiseFilteredCallees },
|
|
674
|
+
unattributed: countUnattributedRows(raw, projectId, root.id, relTypes, cascadeCache),
|
|
675
|
+
crossLanguageExcluded: { callers: crossLangExcludedCallers, callees: crossLangExcludedCallees },
|
|
676
|
+
edgeLimit,
|
|
677
|
+
maxEdges,
|
|
678
|
+
totalEdgeLimitReached: totalLimitReached,
|
|
679
|
+
cappedQueries,
|
|
680
|
+
totalCallersIfKnown,
|
|
681
|
+
totalCalleesIfKnown,
|
|
682
|
+
totalsAreLowerBounds,
|
|
683
|
+
exactCallers,
|
|
684
|
+
cascadedCallers,
|
|
685
|
+
exactCallees,
|
|
686
|
+
cascadedCallees,
|
|
687
|
+
deeperCallers,
|
|
688
|
+
deeperCallees,
|
|
689
|
+
callerBudget: options.callerBudget ?? DEFAULT_BUDGET,
|
|
690
|
+
calleeBudget: options.calleeBudget ?? DEFAULT_BUDGET,
|
|
691
|
+
callerBudgetExhausted,
|
|
692
|
+
calleeBudgetExhausted,
|
|
693
|
+
edgesInBothDirections,
|
|
694
|
+
edgesByType,
|
|
695
|
+
edgeKindPriority: [...Object.keys(EDGE_KIND_PRIORITY)],
|
|
696
|
+
relationshipTypesUsed: relTypes,
|
|
697
|
+
dedupedAtToolLayer: {
|
|
698
|
+
cascadeRepeatsCollapsed: collapsedCascadeRepeats,
|
|
699
|
+
spellingVariantsCollapsed: collapsedSpellingVariants
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
async function resolveGraphRootDetailed(projectId, name) {
|
|
704
|
+
const raw = await BrainDatabase.getRawInstance(projectId, { readonly: true });
|
|
705
|
+
const STRUCTURAL = "('function','method','class','interface','trait','struct','enum','macro')";
|
|
706
|
+
const baseFor = (symbolHint) => "SELECT s.id, s.name, s.symbol_type AS symbolType, COALESCE(s.line_start,0) AS line, COALESCE(s.line_end,0) AS lineEnd, f.relative_path AS relativePath, f.path AS filePath, COALESCE(f.language,'unknown') AS language, COALESCE(s.complexity,0) AS complexity FROM brain_symbol s" + symbolHint + " JOIN brain_file f ON f.id = s.file_id AND f.project_id = s.project_id WHERE s.project_id = ? ";
|
|
707
|
+
const base = baseFor("");
|
|
708
|
+
const exactBase = baseFor(indexedBy(raw, SYMBOL_NAME_INDEX));
|
|
709
|
+
const RANK = " ORDER BY s.gravity_score DESC, s.id ASC LIMIT 1";
|
|
710
|
+
const exactStructural = exactBase + `AND s.symbol_type IN ${STRUCTURAL} AND s.name = ?`;
|
|
711
|
+
const exactAnyKind = exactBase + "AND s.name = ?";
|
|
712
|
+
const fuzzyStructural = base + `AND s.symbol_type IN ${STRUCTURAL} AND s.name LIKE ? ESCAPE '\\'`;
|
|
713
|
+
let match = "exact";
|
|
714
|
+
let predicate = exactStructural;
|
|
715
|
+
let param = name;
|
|
716
|
+
let rows = raw.prepare(exactStructural + RANK).all(projectId, name);
|
|
717
|
+
if (rows.length === 0) {
|
|
718
|
+
rows = raw.prepare(exactAnyKind + RANK).all(projectId, name);
|
|
719
|
+
predicate = exactAnyKind;
|
|
720
|
+
param = name;
|
|
721
|
+
match = "exact-any-kind";
|
|
722
|
+
}
|
|
723
|
+
if (rows.length === 0) {
|
|
724
|
+
const pattern = likeContains(name);
|
|
725
|
+
rows = raw.prepare(fuzzyStructural + RANK).all(projectId, pattern);
|
|
726
|
+
predicate = fuzzyStructural;
|
|
727
|
+
param = pattern;
|
|
728
|
+
match = "containment";
|
|
729
|
+
}
|
|
730
|
+
const root = rows[0] ?? null;
|
|
731
|
+
if (!root) return { root: null, candidates: 0, match: null };
|
|
732
|
+
const countRow = raw.prepare("SELECT COUNT(*) c FROM (" + predicate + ")").get(projectId, param);
|
|
733
|
+
return { root, candidates: Math.max(1, Number(countRow?.c ?? 1)), match };
|
|
734
|
+
}
|
|
735
|
+
function likeContains(needle) {
|
|
736
|
+
let escaped = "";
|
|
737
|
+
for (const ch of needle) {
|
|
738
|
+
if (ch === "\\" || ch === "%" || ch === "_") escaped += "\\" + ch;
|
|
739
|
+
else escaped += ch;
|
|
740
|
+
}
|
|
741
|
+
return `%${escaped}%`;
|
|
742
|
+
}
|
|
743
|
+
async function resolveGraphRoot(projectId, name) {
|
|
744
|
+
return (await resolveGraphRootDetailed(projectId, name)).root;
|
|
745
|
+
}
|
|
746
|
+
async function graphTraversal(projectId, name, options = {}) {
|
|
747
|
+
const { root, candidates, match } = await resolveGraphRootDetailed(projectId, name);
|
|
748
|
+
if (!root) throw new Error(`Symbol '${name}' not found`);
|
|
749
|
+
const {
|
|
750
|
+
callers,
|
|
751
|
+
callees,
|
|
752
|
+
nodes,
|
|
753
|
+
truncated,
|
|
754
|
+
truncationReason,
|
|
755
|
+
truncationReasons,
|
|
756
|
+
filterNarrowed,
|
|
757
|
+
filterReason,
|
|
758
|
+
maxDepthReached,
|
|
759
|
+
maxDepth,
|
|
760
|
+
timeBudgetMs,
|
|
761
|
+
timeBudgetExhausted,
|
|
762
|
+
noiseFiltered,
|
|
763
|
+
unattributed,
|
|
764
|
+
crossLanguageExcluded,
|
|
765
|
+
edgeLimit,
|
|
766
|
+
maxEdges,
|
|
767
|
+
totalEdgeLimitReached: totalLimitReached,
|
|
768
|
+
cappedQueries,
|
|
769
|
+
totalCallersIfKnown,
|
|
770
|
+
totalCalleesIfKnown,
|
|
771
|
+
totalsAreLowerBounds,
|
|
772
|
+
exactCallers,
|
|
773
|
+
cascadedCallers,
|
|
774
|
+
exactCallees,
|
|
775
|
+
cascadedCallees,
|
|
776
|
+
deeperCallers,
|
|
777
|
+
deeperCallees,
|
|
778
|
+
callerBudget,
|
|
779
|
+
calleeBudget,
|
|
780
|
+
callerBudgetExhausted,
|
|
781
|
+
calleeBudgetExhausted,
|
|
782
|
+
edgesInBothDirections,
|
|
783
|
+
edgesByType,
|
|
784
|
+
edgeKindPriority,
|
|
785
|
+
relationshipTypesUsed,
|
|
786
|
+
dedupedAtToolLayer
|
|
787
|
+
} = await traverseGraph(projectId, root, options);
|
|
788
|
+
const rootNode = nodes[root.id];
|
|
789
|
+
const crossLanguageEdges = [...callers, ...callees].filter((e) => e.isCrossLanguage).length;
|
|
790
|
+
return {
|
|
791
|
+
root: rootNode,
|
|
792
|
+
rootCandidates: candidates,
|
|
793
|
+
// WHICH step found the root, beside the root itself. Without this a
|
|
794
|
+
// containment match is indistinguishable from the symbol that was asked
|
|
795
|
+
// for, and the whole answer is confidently about the wrong symbol.
|
|
796
|
+
rootMatch: match,
|
|
797
|
+
callers,
|
|
798
|
+
callees,
|
|
799
|
+
nodes,
|
|
800
|
+
provenance: makeProvenance({
|
|
801
|
+
evidenceClass: "inferred",
|
|
802
|
+
engine: "graph@intelligence",
|
|
803
|
+
engineVersion: "m10.02",
|
|
804
|
+
projectId,
|
|
805
|
+
source: "brain_relationship",
|
|
806
|
+
limitations: [
|
|
807
|
+
"Traversal is bounded and unresolved external nodes cannot be recursively traversed.",
|
|
808
|
+
// WHY the whole answer is `inferred` while single edges can be
|
|
809
|
+
// `structural` — stated, not left to convention.
|
|
810
|
+
// The provenance class describes the ANSWER, and the answer is a
|
|
811
|
+
// construction over stored rows: the root is picked by a gravity
|
|
812
|
+
// tie-break (or a containment fallback), the owner cascade attributes
|
|
813
|
+
// member rows to an owner, a noise policy removes rows, and any cap
|
|
814
|
+
// makes the edge set a prefix. The class of a construction is the
|
|
815
|
+
// weakest part of it, so `inferred` is correct here and the per-row
|
|
816
|
+
// evidence class lives on each edge's own `tier`/`confidence`.
|
|
817
|
+
"provenance.evidenceClass is `inferred` for the ANSWER, not for every edge: root resolution, the owner cascade, the noise policy and any cap are constructions over stored rows, so the whole object cannot claim `structural` even when each returned edge carries a stored row. Per-row evidence is on `edge.tier` (a stored `calls` row is `structural`)."
|
|
818
|
+
],
|
|
819
|
+
truncated,
|
|
820
|
+
unknowns: [
|
|
821
|
+
...noiseFiltered.callers + noiseFiltered.callees > 0 ? [`noise-policy: ${noiseFiltered.callers} caller and ${noiseFiltered.callees} callee stored rows were removed because they live in a vendored, generated, fixture, snapshot, dist or build path, or carry a generated name. They are excluded by POLICY, not absent: an empty edge set here does not mean nothing references this symbol. Applies to bb_search, bb_impact and the test-intelligence modes too, and NOT to mode "complexity", which applies no noise filter \u2014 so the same symbol can be unmeasurable there and unmentioned here.`] : [],
|
|
822
|
+
...unattributed == null ? [`unattributed-source: the count of stored rows naming this symbol with no resolvable SOURCE could not be taken, so this answer cannot say whether such rows exist. It is NOT zero \u2014 the field is null for that reason.`] : unattributed.callers + unattributed.callees > 0 ? [`unattributed-source: ${unattributed.callers} caller and ${unattributed.callees} callee stored rows name this symbol (or a member of its owner cascade) but carry no source symbol, so no traversal can show them in either direction. They are counted, not listed \u2014 the rows exist and name this symbol; treating the edge sets above as the whole set would understate it. Depth-0 only: deeper symbols report their own edges, not their own unattributed rows. Remediation, if the count matters: a full re-index (the bb index command) is what re-attributes sources; a changed-only run does not re-parse the files those rows came from.`] : [],
|
|
823
|
+
...crossLanguageExcluded.callers + crossLanguageExcluded.callees > 0 ? [`cross-language-scope: ${crossLanguageExcluded.callers} caller and ${crossLanguageExcluded.callees} callee stored rows were removed because this call passed includeCrossLanguage: false. The rows are stored and resolvable; they are excluded BY REQUEST, so this answer is a subset rather than the whole set. Re-run with includeCrossLanguage: true (the default) for the complete edge sets.`] : []
|
|
824
|
+
]
|
|
825
|
+
}),
|
|
826
|
+
summary: {
|
|
827
|
+
totalCallers: callers.length,
|
|
828
|
+
totalCallees: callees.length,
|
|
829
|
+
maxDepthReached,
|
|
830
|
+
maxDepth,
|
|
831
|
+
timeBudgetMs,
|
|
832
|
+
timeBudgetExhausted,
|
|
833
|
+
// How many stored rows the vendored/generated/fixture/noise policy removed
|
|
834
|
+
// before any bound applied. Non-zero means the edge sets above are
|
|
835
|
+
// smaller than the store's, BY POLICY — not because nothing was found.
|
|
836
|
+
noiseFiltered,
|
|
837
|
+
// The JOIN's blind spot, counted: rows naming this symbol with no source to
|
|
838
|
+
// attach them to. `null` = not counted, never zero. See the field's own
|
|
839
|
+
// comment on `GraphTraversalSummary`.
|
|
840
|
+
unattributed,
|
|
841
|
+
// Rows the includeCrossLanguage scope option removed. A REQUEST, not a
|
|
842
|
+
// policy or a bound, and now a number rather than an inference from
|
|
843
|
+
// `crossLanguageEdges: 0`.
|
|
844
|
+
crossLanguageExcluded,
|
|
845
|
+
crossLanguageEdges,
|
|
846
|
+
truncated,
|
|
847
|
+
truncationReason,
|
|
848
|
+
// D10: the reasons as data, beside the `+`-joined string. Both are
|
|
849
|
+
// published: the string is what existing consumers parse, and the list is
|
|
850
|
+
// what a reason containing `+` cannot corrupt.
|
|
851
|
+
truncationReasons,
|
|
852
|
+
// D5: the filter, in its own channel. `truncated` above now means ONLY
|
|
853
|
+
// "a declared bound bit", so `truncated: false` beside
|
|
854
|
+
// `filterNarrowed: true` reads correctly: the walk finished, and the
|
|
855
|
+
// question it answered was a subset the caller asked for.
|
|
856
|
+
filterNarrowed,
|
|
857
|
+
filterReason,
|
|
858
|
+
// `totalCallers`/`totalCallees` above are the RETURNED
|
|
859
|
+
// counts. These fields say so explicitly and add the true root count when
|
|
860
|
+
// a cap was hit and it was computable — a floor can no longer be read as a
|
|
861
|
+
// total by a caller that only looks at the summary.
|
|
862
|
+
edgeLimit,
|
|
863
|
+
// The bound on the WHOLE answer, as distinct from the per-query site
|
|
864
|
+
// limit. Declared because the product of the per-query bound and the
|
|
865
|
+
// fan-out budget is otherwise unbounded, and unstated.
|
|
866
|
+
maxEdges,
|
|
867
|
+
totalEdgeLimitReached: totalLimitReached,
|
|
868
|
+
cappedQueries,
|
|
869
|
+
callersIfUncapped: totalCallersIfKnown,
|
|
870
|
+
calleesIfUncapped: totalCalleesIfKnown,
|
|
871
|
+
totalsAreLowerBounds,
|
|
872
|
+
// The owner cascade is a superset by design. These four numbers are how
|
|
873
|
+
// much of it is about the symbol that was actually asked for. An agent
|
|
874
|
+
// that sees `totalCallers: 184, exactCallers: 24` knows at once that the
|
|
875
|
+
// answer is mostly about the symbol's members — which no count above this
|
|
876
|
+
// line would ever have told it.
|
|
877
|
+
exactCallers,
|
|
878
|
+
cascadedCallers,
|
|
879
|
+
exactCallees,
|
|
880
|
+
cascadedCallees,
|
|
881
|
+
deeperCallers,
|
|
882
|
+
deeperCallees,
|
|
883
|
+
// The fan-out bound, and whether it bit. `truncationReason` can name
|
|
884
|
+
// `caller-budget-exhausted`; before this it did so without saying what
|
|
885
|
+
// the budget was, so the bound was announced and never quantified.
|
|
886
|
+
callerBudget,
|
|
887
|
+
calleeBudget,
|
|
888
|
+
callerBudgetExhausted,
|
|
889
|
+
calleeBudgetExhausted,
|
|
890
|
+
// What the answer is made of. With ten kinds in the default filter, a
|
|
891
|
+
// caller that wanted calls had no way to find out how many of the
|
|
892
|
+
// returned edges were calls without walking all of them.
|
|
893
|
+
edgesByType,
|
|
894
|
+
// The kinds this run actually searched, resolved — not a restatement of
|
|
895
|
+
// the default. The handler's scope echo carried its own hardcoded copy
|
|
896
|
+
// and went stale the moment the default was widened, so it named four
|
|
897
|
+
// kinds while the answer held `uses` and `contains` edges.
|
|
898
|
+
relationshipTypesUsed,
|
|
899
|
+
// Which kinds survive a bound first. Only observable under truncation,
|
|
900
|
+
// but published always: the cap's ORDER BY used to be indifferent to
|
|
901
|
+
// kind, so asking for MORE kinds returned FEWER call edges (measured on
|
|
902
|
+
// lodash: 99 calls alone, 71 inside the 230-edge default answer). The
|
|
903
|
+
// rule that decides that is now declared here.
|
|
904
|
+
edgeKindPriority,
|
|
905
|
+
// Rows present in BOTH callers and callees. `totalCallers + totalCallees`
|
|
906
|
+
// counts each of these twice, and the number above is the correction.
|
|
907
|
+
edgesInBothDirections,
|
|
908
|
+
// What the tool layer collapsed before handing edges
|
|
909
|
+
// to the agent. cascadeRepeatsCollapsed: the same stored row re-fetched
|
|
910
|
+
// by a deeper cascade. spellingVariantsCollapsed: same call site + same
|
|
911
|
+
// resolved target stored under different spellings, emitted once.
|
|
912
|
+
dedupedAtToolLayer
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
export {
|
|
918
|
+
STORED_LINE_SELECT,
|
|
919
|
+
storedLineFromRow,
|
|
920
|
+
ADDRESS_NOTES,
|
|
921
|
+
deriveCallSiteAddress,
|
|
922
|
+
addressLine0,
|
|
923
|
+
isExactAddress,
|
|
924
|
+
unattributedAddress,
|
|
925
|
+
traverseGraph,
|
|
926
|
+
resolveGraphRootDetailed,
|
|
927
|
+
likeContains,
|
|
928
|
+
resolveGraphRoot,
|
|
929
|
+
graphTraversal
|
|
930
|
+
};
|