Skip to content

Commit b808751

Browse files
authored
fix(bench): update elixir/julia/objc expected-edges to module-qualified names (#1496)
* chore: gitignore napi-generated artifacts in crates/codegraph-core * chore(tests): remove unused biome suppression in visitor.test.ts * fix(titan-run): sync --start-from enum and phase-timestamp list with actual phases * fix(hooks): track Bash file modifications via before/after git status diff Adds snapshot-pre-bash.sh (PreToolUse Bash) + track-bash-writes.sh (PostToolUse Bash): the pre-hook captures git status --porcelain to a per-worktree temp file before each Bash call; the post-hook diffs the before/after state and appends newly modified or created files to .claude/session-edits.log. This closes the gap where files written by sed -i, printf redirects, tee, heredocs, or build tools (Cargo.lock, lockfiles) were never recorded, causing guard-git.sh to emit false-positive BLOCKED errors. Closes #1457 * chore(native): remove dead code (unused var, method, variant, fields) - clojure.rs: annotate lifetime-anchor assignment to silence false-positive - cfg.rs: remove never-called start_line_of method - complexity.rs: remove never-constructed NotHandled variant; convert irrefutable if-let patterns to plain let destructures - dataflow.rs: remove never-read callee fields from CallReturn/Destructured - incremental.rs: remove never-read lang field from CacheEntry cargo check and cargo clippy both clean after these changes. * refactor(native): extract emit_pts_alias_edges params into PtsAliasCtx struct * fix(wasm): sort call targets by confidence before emit to match native engine * fix(bench): add 2 warmup runs and raise INCREMENTAL_RUNS to 5 for incremental tiers * ci(bench): add per-PR perf canary for extractor/graph/native changes Adds .github/workflows/perf-canary.yml — a path-filtered workflow that fires on PRs touching src/extractors/, src/domain/graph/, or crates/** and runs only the incremental-benchmark suite (full build + no-op + 1-file rebuild, both engines). Catches the class of regressions that accumulated invisibly across the Phase 8.x PRs and were only detected at v3.12.0 publish time. The regression guard gains BENCH_CANARY=1 mode: raises thresholds to 50%/100%/150% (standard/noisy/WASM) and skips the build, query, and resolution suites — only incremental checks run. This absorbs shared- runner timing variance while still blocking catastrophic regressions (+98% full build, +1827% 1-file rebuild from v3.12.0). Closes #1433 * fix(perf): plumb symbolsOnly through parseFilesWasmInline to skip analysis visitors * fix(perf): scope runPostNativeCha to changed files on incremental builds On incremental builds, runPostNativeCha previously scanned all call→qualified-method edges in the DB (~12ms flat, O(graph size)), even for 1-file changes where no hierarchy or RTA evidence changed. Add two cheap indexed gate queries. Gate A checks whether any changed file introduced a class/interface/trait/struct/record node (hierarchy may have new implementors reachable from unchanged call sites). Gate B checks whether any changed file added a call edge to a class-kind target (RTA set may have grown, enabling previously filtered expansions in unchanged callers). If neither gate fires, restrict the candidate query to src.file IN changedFiles — safe because the hierarchy and instantiated set are unchanged for all other files. Full builds (isFullBuild=true) and cases where either gate fires retain the existing full-scan behaviour. Mirrors the changed-files scoping pattern of runPostNativeThisDispatch. Closes #1441 * fix(native): add post-pass phase timings to result.phases Times each JS post-pass in tryNativeOrchestrator and exposes the measurements in BuildResult.phases: - gapDetectMs — dropped-language gap detection + backfill - chaMs — CHA expansion (interface dispatch) - thisDispatchMs — this/super dispatch WASM re-parse (was already tracked but now properly named alongside the rest) - reclassifyMs — scoped role re-classification after edge insertion - techniqueBackfillMs — technique-column UPDATE on native-written edges Previously only thisDispatchMs was reported, causing wall-clock vs phaseSum to diverge by 1.1s+ on 1-file rebuilds and making benchmark regressions undiagnosable from committed history. Updates update-incremental-report.ts to render the new phases in a collapsible details block under each engine's 1-file rebuild section. Closes #1434 * fix(perf): correct INLINE_BACKFILL_THRESHOLD docstring; raise threshold for required-tier grammars The docstring claimed pool cost was "amortised over enough parse work" — measurements show IPC overhead scales linearly (~55–64ms/file pool vs ~8–10ms/file inline). The real motivation is crash safety for exotic WASM grammars (#965); JS/TS/TSX (required-tier, used in all this-dispatch backfill calls) have never triggered the V8 fatal crash class and are safe to run inline. Raise threshold 16 → 32 to keep typical this-dispatch batches (≤ 18 files on the codegraph corpus) on the inline fast path. Exotic-language drops are almost always well under 32 files and also benefit from the inline path without meaningful crash risk increase. Closes #1435 * fix(perf): guard post-native passes against unnecessary work on 1-file incremental rebuilds On 1-file native incremental builds, two JS post-passes ran unconditionally even when they had no work to do: - `backfillNativeDroppedFiles`: called whenever changedCount > 0, even when detectDroppedLanguageGap returned an empty gap. Gate now checks gap.missingAbs.length > 0 || gap.staleRel.length > 0 directly, matching backfillNativeDroppedFiles's own internal early-exit guard. - Node/edge COUNT(*) re-count: ran unconditionally after all post-passes even when none of them wrote any edges. COUNT(*) over 50K+ edge tables is non-trivial, especially via the NativeDbProxy napi-rs round-trip. Now gated on postPassWroteData (backfill | CHA edges | this-dispatch edges). Closes #1454 * chore(types): remove dead protoMethodsMs field and stale comment The post-pass it timed (runPostNativePrototypeMethods) was deleted in b5c03a2 when func-prop extraction moved to Rust (#1432). The optional field was never set by any code path that survived the deletion. Also remove the stale reference to "prototype-methods post-pass" from the parseFilesWasmForBackfill docstring — only the this-dispatch post-pass uses symbolsOnly now. Closes #1432 * fix: class-scope field annotation typeMap keys to prevent cross-class collision Field type annotations (`private repo: OrderRepository`) were seeded as bare file-wide typeMap keys, causing `this.repo` inside `UserService` to resolve to `OrderRepository` when both classes had a `repo` field (issue #1458). Both extractors (TS `handleFieldDefTypeMap` and Rust `field_definition` branch) now seed `ClassName.field` keys at confidence 0.9, matching the `CallerClass.X` resolver fallback added in PR #1382. Bare keys are kept at confidence 0.6 as fallbacks for single-class files or class expressions where no enclosing class name is available. Both engines change identically — parity preserved. * fix(bench): update elixir/julia/objc expected-edges to module-qualified names The resolution benchmark uses WASM-built graphs where the Elixir, Julia, and Objective-C extractors emit module-qualified symbol names (Main.run, App.main, UserService.create_user, etc.). The expected-edges manifests were written with bare unqualified names (run, main, create_user), so every correctly-resolved edge appeared as a false positive and every expected edge appeared as a false negative — causing all three languages to show 0% precision even though resolution was working correctly. Root cause: starting in v3.12.0, cross-module call resolution began working for these languages (via the improved receiver-dispatch and same-class fallback in resolveByMethodOrGlobal / build-edges.ts). With 0 edges previously resolved, the name mismatch was invisible; once edges started resolving, the manifests showed 17 FP (elixir), 11 FP (julia), 6 FP (objc) — all correctly resolved edges misidentified as false positives. Fix: - Update all three expected-edges.json manifests to use the module-qualified names matching actual extractor output: elixir: Main.run, UserService.create_user, Validators.validate_user, etc. julia: App.main, Service.create_user, Repository.new_repo, etc. objc: full ObjC selectors (createUserWithId:name:email:, isValidEmail:, etc.) plus add main -> run (plain C call correctly resolved) - Ratchet THRESHOLDS for all three: elixir: precision 0.0 -> 1.0, recall 0.0 -> 0.8 (17/21 resolved) julia: precision 0.0 -> 1.0, recall 0.0 -> 0.7 (11/15 resolved) objc: precision 0.0 -> 1.0, recall 0.0 -> 0.4 (6/13 resolved) Remaining FNs are genuine unresolved edges (same-file bare calls in elixir/julia, receiver-typed message sends in objc) — not regressions. Closes #1447 * fix(perf): update stale parseFilesWasmForBackfill docstring to reference threshold constant The secondary docstring still described the old 16-value rationale ("engine-parity drop sizes"). Replace with a pointer to INLINE_BACKFILL_THRESHOLD where the full rationale now lives. * docs(bench): mention constructor FN alongside receiver-typed FNs in objc threshold comment The previous comment only listed receiver-typed instance message sends as unresolved false negatives, omitting the constructor edge (run → UserService.initWithRepository:). Both modes are FNs; the comment now lists them together so future contributors see the complete picture. * fix(resolver): check class-scoped typeMap key before bare fallback for this. receivers When handleFieldDefTypeMap seeds ClassName.field at 0.9 and bare field/ this.field at 0.6, the resolver was still finding the bare 0.6 entry first (effectiveReceiver lookup) and skipping the class-scoped check entirely. The result: cross-class collision from issue #1458 persisted at runtime even though the extractor emitted the right keys. Fix: for this. receivers, check the class-scoped key (ClassName.prop) first, then fall back to bare keys. Both TS (call-resolver.ts) and Rust (build_edges.rs) resolvers are updated identically. Fixes #1458. * test(1458): add Rust multi-class field collision unit test and end-to-end integration test - Rust: field_annotation_multi_class_seeds_separate_scoped_keys confirms that two classes with identically-named fields produce separate class-scoped typeMap keys at confidence 0.9 (mirrors the TS prevents-cross-class-collision test). - Integration: issue-1458-cross-class-field-typemap.test.ts exercises the full buildGraph → resolver path (WASM engine) and asserts that OrderService.run resolves to OrderRepository.save and UserService.run to UserRepository.save, with no cross-class false edges. * style(tests): expand toEqual object literals to pass Biome format check * fix(test): use TypeScript parser for field annotation collision test The field_annotation_multi_class_seeds_separate_scoped_keys test used parse_js() (tree-sitter-javascript), but the test fixture uses TypeScript syntax (private field declarations with type annotations). The JS grammar does not support type annotations, so the type_map came back empty and the assertion failed. Add parse_ts() helper using LANGUAGE_TYPESCRIPT and switch the test to it. All 396 Rust unit tests pass.
1 parent 3101226 commit b808751

5 files changed

Lines changed: 153 additions & 126 deletions

File tree

src/domain/graph/builder/stages/native-orchestrator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,14 @@ interface PostPassTimings {
959959
techniqueBackfillMs: number;
960960
}
961961

962+
interface PostPassTimings {
963+
gapDetectMs: number;
964+
chaMs: number;
965+
thisDispatchMs: number;
966+
reclassifyMs: number;
967+
techniqueBackfillMs: number;
968+
}
969+
962970
/** Format timing result from native orchestrator phases + JS post-processing. */
963971
function formatNativeTimingResult(
964972
p: Record<string, number>,

tests/benchmarks/resolution/fixtures/elixir/expected-edges.json

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,148 +4,148 @@
44
"description": "Hand-annotated call edges for Elixir resolution benchmark",
55
"edges": [
66
{
7-
"source": { "name": "validate_user", "file": "validators.ex" },
8-
"target": { "name": "valid_name?", "file": "validators.ex" },
7+
"source": { "name": "Validators.validate_user", "file": "validators.ex" },
8+
"target": { "name": "Validators.valid_name?", "file": "validators.ex" },
99
"kind": "calls",
1010
"mode": "same-file",
11-
"notes": "Same-module helper call within Validators"
11+
"notes": "Same-module helper call within Validators — extractor emits module-qualified names"
1212
},
1313
{
14-
"source": { "name": "validate_user", "file": "validators.ex" },
15-
"target": { "name": "valid_email?", "file": "validators.ex" },
14+
"source": { "name": "Validators.validate_user", "file": "validators.ex" },
15+
"target": { "name": "Validators.valid_email?", "file": "validators.ex" },
1616
"kind": "calls",
1717
"mode": "same-file",
18-
"notes": "Same-module helper call within Validators"
18+
"notes": "Same-module helper call within Validators — extractor emits module-qualified names"
1919
},
2020
{
21-
"source": { "name": "create_user", "file": "service.ex" },
22-
"target": { "name": "validate_user", "file": "validators.ex" },
21+
"source": { "name": "UserService.create_user", "file": "service.ex" },
22+
"target": { "name": "Validators.validate_user", "file": "validators.ex" },
2323
"kind": "calls",
2424
"mode": "module-function",
2525
"notes": "Validators.validate_user() — cross-module qualified call"
2626
},
2727
{
28-
"source": { "name": "create_user", "file": "service.ex" },
29-
"target": { "name": "save", "file": "repository.ex" },
28+
"source": { "name": "UserService.create_user", "file": "service.ex" },
29+
"target": { "name": "UserRepository.save", "file": "repository.ex" },
3030
"kind": "calls",
3131
"mode": "module-function",
3232
"notes": "UserRepository.save() — cross-module qualified call"
3333
},
3434
{
35-
"source": { "name": "get_user", "file": "service.ex" },
36-
"target": { "name": "find_by_id", "file": "repository.ex" },
35+
"source": { "name": "UserService.get_user", "file": "service.ex" },
36+
"target": { "name": "UserRepository.find_by_id", "file": "repository.ex" },
3737
"kind": "calls",
3838
"mode": "module-function",
3939
"notes": "UserRepository.find_by_id() — cross-module qualified call"
4040
},
4141
{
42-
"source": { "name": "remove_user", "file": "service.ex" },
43-
"target": { "name": "delete", "file": "repository.ex" },
42+
"source": { "name": "UserService.remove_user", "file": "service.ex" },
43+
"target": { "name": "UserRepository.delete", "file": "repository.ex" },
4444
"kind": "calls",
4545
"mode": "module-function",
4646
"notes": "UserRepository.delete() — cross-module qualified call"
4747
},
4848
{
49-
"source": { "name": "list_users", "file": "service.ex" },
50-
"target": { "name": "list_all", "file": "repository.ex" },
49+
"source": { "name": "UserService.list_users", "file": "service.ex" },
50+
"target": { "name": "UserRepository.list_all", "file": "repository.ex" },
5151
"kind": "calls",
5252
"mode": "module-function",
5353
"notes": "UserRepository.list_all() — cross-module qualified call"
5454
},
5555
{
56-
"source": { "name": "display_user", "file": "service.ex" },
57-
"target": { "name": "get_user", "file": "service.ex" },
56+
"source": { "name": "UserService.display_user", "file": "service.ex" },
57+
"target": { "name": "UserService.get_user", "file": "service.ex" },
5858
"kind": "calls",
5959
"mode": "same-file",
60-
"notes": "Same-module call to get_user within UserService"
60+
"notes": "Same-module call to get_user within UserService — extractor emits module-qualified names"
6161
},
6262
{
63-
"source": { "name": "display_user", "file": "service.ex" },
64-
"target": { "name": "format_user", "file": "service.ex" },
63+
"source": { "name": "UserService.display_user", "file": "service.ex" },
64+
"target": { "name": "UserService.format_user", "file": "service.ex" },
6565
"kind": "calls",
6666
"mode": "same-file",
67-
"notes": "Same-module call to private helper format_user"
67+
"notes": "Same-module call to private helper format_user — extractor emits module-qualified names"
6868
},
6969
{
70-
"source": { "name": "run", "file": "main.ex" },
71-
"target": { "name": "new_store", "file": "repository.ex" },
70+
"source": { "name": "Main.run", "file": "main.ex" },
71+
"target": { "name": "UserRepository.new_store", "file": "repository.ex" },
7272
"kind": "calls",
7373
"mode": "module-function",
7474
"notes": "UserRepository.new_store() — cross-module qualified call"
7575
},
7676
{
77-
"source": { "name": "run", "file": "main.ex" },
78-
"target": { "name": "create_user", "file": "service.ex" },
77+
"source": { "name": "Main.run", "file": "main.ex" },
78+
"target": { "name": "UserService.create_user", "file": "service.ex" },
7979
"kind": "calls",
8080
"mode": "module-function",
8181
"notes": "UserService.create_user() — cross-module qualified call"
8282
},
8383
{
84-
"source": { "name": "run", "file": "main.ex" },
85-
"target": { "name": "get_user", "file": "service.ex" },
84+
"source": { "name": "Main.run", "file": "main.ex" },
85+
"target": { "name": "UserService.get_user", "file": "service.ex" },
8686
"kind": "calls",
8787
"mode": "module-function",
8888
"notes": "UserService.get_user() — cross-module qualified call"
8989
},
9090
{
91-
"source": { "name": "run", "file": "main.ex" },
92-
"target": { "name": "list_users", "file": "service.ex" },
91+
"source": { "name": "Main.run", "file": "main.ex" },
92+
"target": { "name": "UserService.list_users", "file": "service.ex" },
9393
"kind": "calls",
9494
"mode": "module-function",
9595
"notes": "UserService.list_users() — cross-module qualified call"
9696
},
9797
{
98-
"source": { "name": "run", "file": "main.ex" },
99-
"target": { "name": "display_user", "file": "service.ex" },
98+
"source": { "name": "Main.run", "file": "main.ex" },
99+
"target": { "name": "UserService.display_user", "file": "service.ex" },
100100
"kind": "calls",
101101
"mode": "module-function",
102102
"notes": "UserService.display_user() — cross-module qualified call"
103103
},
104104
{
105-
"source": { "name": "run", "file": "main.ex" },
106-
"target": { "name": "remove_user", "file": "service.ex" },
105+
"source": { "name": "Main.run", "file": "main.ex" },
106+
"target": { "name": "UserService.remove_user", "file": "service.ex" },
107107
"kind": "calls",
108108
"mode": "module-function",
109109
"notes": "UserService.remove_user() — cross-module qualified call"
110110
},
111111
{
112-
"source": { "name": "run", "file": "main.ex" },
113-
"target": { "name": "fetch", "file": "patterns.ex" },
112+
"source": { "name": "Main.run", "file": "main.ex" },
113+
"target": { "name": "Patterns.fetch", "file": "patterns.ex" },
114114
"kind": "calls",
115115
"mode": "module-function",
116116
"notes": "Patterns.fetch() — exercises default-value parameter extraction"
117117
},
118118
{
119-
"source": { "name": "run", "file": "main.ex" },
120-
"target": { "name": "first_of", "file": "patterns.ex" },
119+
"source": { "name": "Main.run", "file": "main.ex" },
120+
"target": { "name": "Patterns.first_of", "file": "patterns.ex" },
121121
"kind": "calls",
122122
"mode": "module-function",
123123
"notes": "Patterns.first_of() — exercises tuple-pattern parameter extraction"
124124
},
125125
{
126-
"source": { "name": "run", "file": "main.ex" },
127-
"target": { "name": "name_of", "file": "patterns.ex" },
126+
"source": { "name": "Main.run", "file": "main.ex" },
127+
"target": { "name": "Patterns.name_of", "file": "patterns.ex" },
128128
"kind": "calls",
129129
"mode": "module-function",
130130
"notes": "Patterns.name_of() — exercises map-pattern parameter extraction"
131131
},
132132
{
133-
"source": { "name": "run", "file": "main.ex" },
134-
"target": { "name": "id_of", "file": "patterns.ex" },
133+
"source": { "name": "Main.run", "file": "main.ex" },
134+
"target": { "name": "Patterns.id_of", "file": "patterns.ex" },
135135
"kind": "calls",
136136
"mode": "module-function",
137137
"notes": "Patterns.id_of() — exercises struct-pattern parameter extraction"
138138
},
139139
{
140-
"source": { "name": "run", "file": "main.ex" },
141-
"target": { "name": "head_of", "file": "patterns.ex" },
140+
"source": { "name": "Main.run", "file": "main.ex" },
141+
"target": { "name": "Patterns.head_of", "file": "patterns.ex" },
142142
"kind": "calls",
143143
"mode": "module-function",
144144
"notes": "Patterns.head_of() — exercises list-cons pattern parameter extraction"
145145
},
146146
{
147-
"source": { "name": "run", "file": "main.ex" },
148-
"target": { "name": "all_of", "file": "patterns.ex" },
147+
"source": { "name": "Main.run", "file": "main.ex" },
148+
"target": { "name": "Patterns.all_of", "file": "patterns.ex" },
149149
"kind": "calls",
150150
"mode": "module-function",
151151
"notes": "Patterns.all_of() — exercises list pattern parameter extraction"

tests/benchmarks/resolution/fixtures/julia/expected-edges.json

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,109 +4,109 @@
44
"description": "Hand-annotated call edges for Julia resolution benchmark",
55
"edges": [
66
{
7-
"source": { "name": "main", "file": "main.jl" },
8-
"target": { "name": "new_repo", "file": "repository.jl" },
7+
"source": { "name": "App.main", "file": "main.jl" },
8+
"target": { "name": "Repository.new_repo", "file": "repository.jl" },
99
"kind": "calls",
1010
"mode": "module-function",
11-
"notes": "Repository.new_repo() — qualified call to Repository module"
11+
"notes": "Repository.new_repo() — qualified call to Repository module; extractor emits module-qualified names"
1212
},
1313
{
14-
"source": { "name": "main", "file": "main.jl" },
15-
"target": { "name": "create_user", "file": "service.jl" },
14+
"source": { "name": "App.main", "file": "main.jl" },
15+
"target": { "name": "Service.create_user", "file": "service.jl" },
1616
"kind": "calls",
1717
"mode": "module-function",
18-
"notes": "Service.create_user() — qualified call to Service module"
18+
"notes": "Service.create_user() — qualified call to Service module; extractor emits module-qualified names"
1919
},
2020
{
21-
"source": { "name": "main", "file": "main.jl" },
22-
"target": { "name": "get_user", "file": "service.jl" },
21+
"source": { "name": "App.main", "file": "main.jl" },
22+
"target": { "name": "Service.get_user", "file": "service.jl" },
2323
"kind": "calls",
2424
"mode": "module-function",
25-
"notes": "Service.get_user() — qualified call to Service module"
25+
"notes": "Service.get_user() — qualified call to Service module; extractor emits module-qualified names"
2626
},
2727
{
28-
"source": { "name": "main", "file": "main.jl" },
29-
"target": { "name": "remove_user", "file": "service.jl" },
28+
"source": { "name": "App.main", "file": "main.jl" },
29+
"target": { "name": "Service.remove_user", "file": "service.jl" },
3030
"kind": "calls",
3131
"mode": "module-function",
32-
"notes": "Service.remove_user() — qualified call to Service module"
32+
"notes": "Service.remove_user() — qualified call to Service module; extractor emits module-qualified names"
3333
},
3434
{
35-
"source": { "name": "main", "file": "main.jl" },
36-
"target": { "name": "summary", "file": "service.jl" },
35+
"source": { "name": "App.main", "file": "main.jl" },
36+
"target": { "name": "Service.summary", "file": "service.jl" },
3737
"kind": "calls",
3838
"mode": "module-function",
39-
"notes": "Service.summary() — qualified call to Service module"
39+
"notes": "Service.summary() — qualified call to Service module; extractor emits module-qualified names"
4040
},
4141
{
42-
"source": { "name": "create_user", "file": "service.jl" },
43-
"target": { "name": "validate_name", "file": "validators.jl" },
42+
"source": { "name": "Service.create_user", "file": "service.jl" },
43+
"target": { "name": "Validators.validate_name", "file": "validators.jl" },
4444
"kind": "calls",
4545
"mode": "module-function",
46-
"notes": "Validators.validate_name() — qualified call to Validators module"
46+
"notes": "Validators.validate_name() — qualified call to Validators module; extractor emits module-qualified names"
4747
},
4848
{
49-
"source": { "name": "create_user", "file": "service.jl" },
50-
"target": { "name": "validate_email", "file": "validators.jl" },
49+
"source": { "name": "Service.create_user", "file": "service.jl" },
50+
"target": { "name": "Validators.validate_email", "file": "validators.jl" },
5151
"kind": "calls",
5252
"mode": "module-function",
53-
"notes": "Validators.validate_email() — qualified call to Validators module"
53+
"notes": "Validators.validate_email() — qualified call to Validators module; extractor emits module-qualified names"
5454
},
5555
{
56-
"source": { "name": "create_user", "file": "service.jl" },
57-
"target": { "name": "save", "file": "repository.jl" },
56+
"source": { "name": "Service.create_user", "file": "service.jl" },
57+
"target": { "name": "Repository.save", "file": "repository.jl" },
5858
"kind": "calls",
5959
"mode": "module-function",
60-
"notes": "Repository.save() — qualified call to Repository module"
60+
"notes": "Repository.save() — qualified call to Repository module; extractor emits module-qualified names"
6161
},
6262
{
63-
"source": { "name": "get_user", "file": "service.jl" },
64-
"target": { "name": "find_by_id", "file": "repository.jl" },
63+
"source": { "name": "Service.get_user", "file": "service.jl" },
64+
"target": { "name": "Repository.find_by_id", "file": "repository.jl" },
6565
"kind": "calls",
6666
"mode": "module-function",
67-
"notes": "Repository.find_by_id() — qualified call to Repository module"
67+
"notes": "Repository.find_by_id() — qualified call to Repository module; extractor emits module-qualified names"
6868
},
6969
{
70-
"source": { "name": "remove_user", "file": "service.jl" },
71-
"target": { "name": "delete", "file": "repository.jl" },
70+
"source": { "name": "Service.remove_user", "file": "service.jl" },
71+
"target": { "name": "Repository.delete", "file": "repository.jl" },
7272
"kind": "calls",
7373
"mode": "module-function",
74-
"notes": "Repository.delete() — qualified call to Repository module"
74+
"notes": "Repository.delete() — qualified call to Repository module; extractor emits module-qualified names"
7575
},
7676
{
77-
"source": { "name": "summary", "file": "service.jl" },
78-
"target": { "name": "count", "file": "repository.jl" },
77+
"source": { "name": "Service.summary", "file": "service.jl" },
78+
"target": { "name": "Repository.count", "file": "repository.jl" },
7979
"kind": "calls",
8080
"mode": "module-function",
81-
"notes": "Repository.count() — qualified call to Repository module"
81+
"notes": "Repository.count() — qualified call to Repository module; extractor emits module-qualified names"
8282
},
8383
{
84-
"source": { "name": "summary", "file": "service.jl" },
85-
"target": { "name": "format_summary", "file": "service.jl" },
84+
"source": { "name": "Service.summary", "file": "service.jl" },
85+
"target": { "name": "Service.format_summary", "file": "service.jl" },
8686
"kind": "calls",
8787
"mode": "same-file",
88-
"notes": "Same-file call to private helper function"
88+
"notes": "Same-file call to private helper function — extractor emits module-qualified names"
8989
},
9090
{
91-
"source": { "name": "validate_name", "file": "validators.jl" },
92-
"target": { "name": "check_length", "file": "validators.jl" },
91+
"source": { "name": "Validators.validate_name", "file": "validators.jl" },
92+
"target": { "name": "Validators.check_length", "file": "validators.jl" },
9393
"kind": "calls",
9494
"mode": "same-file",
95-
"notes": "Same-file call to private helper function"
95+
"notes": "Same-file call to private helper function — extractor emits module-qualified names"
9696
},
9797
{
98-
"source": { "name": "validate_email", "file": "validators.jl" },
99-
"target": { "name": "contains_at", "file": "validators.jl" },
98+
"source": { "name": "Validators.validate_email", "file": "validators.jl" },
99+
"target": { "name": "Validators.contains_at", "file": "validators.jl" },
100100
"kind": "calls",
101101
"mode": "same-file",
102-
"notes": "Same-file call to private helper function"
102+
"notes": "Same-file call to private helper function — extractor emits module-qualified names"
103103
},
104104
{
105-
"source": { "name": "count", "file": "repository.jl" },
106-
"target": { "name": "count_entries", "file": "repository.jl" },
105+
"source": { "name": "Repository.count", "file": "repository.jl" },
106+
"target": { "name": "Repository.count_entries", "file": "repository.jl" },
107107
"kind": "calls",
108108
"mode": "same-file",
109-
"notes": "Same-file call to private helper function"
109+
"notes": "Same-file call to private helper function — extractor emits module-qualified names"
110110
}
111111
]
112112
}

0 commit comments

Comments
 (0)