()
+
+let save (uid: string) (record: UserRecord) =
+ store.[uid] <- record
+
+let findById (uid: string) =
+ match store.TryGetValue(uid) with
+ | true, record -> Some record
+ | _ -> None
+
+let delete (uid: string) =
+ store.Remove(uid) |> ignore
+
+let count () =
+ store.Count
diff --git a/tests/benchmarks/resolution/fixtures/fsharp/Service.fs b/tests/benchmarks/resolution/fixtures/fsharp/Service.fs
new file mode 100644
index 00000000..4361b908
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/fsharp/Service.fs
@@ -0,0 +1,22 @@
+module Service
+
+let validateUser name email age =
+ Validators.validateName name
+ && Validators.validateEmail email
+ && Validators.validateAge age
+
+let createUser uid name email age =
+ if validateUser name email age then
+ Repository.save uid { Repository.Name = name; Repository.Email = email; Repository.Age = age }
+ Ok ()
+ else
+ Error "Validation failed"
+
+let getUser uid =
+ Repository.findById uid
+
+let removeUser uid =
+ Repository.delete uid
+
+let summary () =
+ Repository.count ()
diff --git a/tests/benchmarks/resolution/fixtures/fsharp/Validators.fs b/tests/benchmarks/resolution/fixtures/fsharp/Validators.fs
new file mode 100644
index 00000000..ea153251
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/fsharp/Validators.fs
@@ -0,0 +1,10 @@
+module Validators
+
+let validateEmail (email: string) =
+ email.Contains("@") && email.Contains(".")
+
+let validateName (name: string) =
+ name.Length >= 2 && name.Length <= 50
+
+let validateAge (age: int) =
+ age >= 0 && age <= 150
diff --git a/tests/benchmarks/resolution/fixtures/fsharp/expected-edges.json b/tests/benchmarks/resolution/fixtures/fsharp/expected-edges.json
new file mode 100644
index 00000000..51b93bf8
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/fsharp/expected-edges.json
@@ -0,0 +1,91 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "fsharp",
+ "description": "Hand-annotated call edges for F# resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "Main.fs" },
+ "target": { "name": "createUser", "file": "Service.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.createUser — qualified cross-module call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.fs" },
+ "target": { "name": "getUser", "file": "Service.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.getUser — qualified cross-module call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.fs" },
+ "target": { "name": "removeUser", "file": "Service.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.removeUser — qualified cross-module call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.fs" },
+ "target": { "name": "summary", "file": "Service.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.summary — qualified cross-module call (called twice)"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.fs" },
+ "target": { "name": "validateName", "file": "Validators.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validateName — qualified cross-module call"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.fs" },
+ "target": { "name": "validateEmail", "file": "Validators.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validateEmail — qualified cross-module call"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.fs" },
+ "target": { "name": "validateAge", "file": "Validators.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validateAge — qualified cross-module call"
+ },
+ {
+ "source": { "name": "createUser", "file": "Service.fs" },
+ "target": { "name": "validateUser", "file": "Service.fs" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-module call to local helper function"
+ },
+ {
+ "source": { "name": "createUser", "file": "Service.fs" },
+ "target": { "name": "save", "file": "Repository.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.save — qualified cross-module call"
+ },
+ {
+ "source": { "name": "getUser", "file": "Service.fs" },
+ "target": { "name": "findById", "file": "Repository.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.findById — qualified cross-module call"
+ },
+ {
+ "source": { "name": "removeUser", "file": "Service.fs" },
+ "target": { "name": "delete", "file": "Repository.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.delete — qualified cross-module call"
+ },
+ {
+ "source": { "name": "summary", "file": "Service.fs" },
+ "target": { "name": "count", "file": "Repository.fs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.count — qualified cross-module call"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/gleam/expected-edges.json b/tests/benchmarks/resolution/fixtures/gleam/expected-edges.json
new file mode 100644
index 00000000..686a698d
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/gleam/expected-edges.json
@@ -0,0 +1,112 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "gleam",
+ "description": "Hand-annotated call edges for Gleam resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "main.gleam" },
+ "target": { "name": "new_repo", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.new_repo() — qualified call to repository module"
+ },
+ {
+ "source": { "name": "main", "file": "main.gleam" },
+ "target": { "name": "create_user", "file": "service.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.create_user() — qualified call to service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.gleam" },
+ "target": { "name": "get_user", "file": "service.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.get_user() — qualified call to service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.gleam" },
+ "target": { "name": "remove_user", "file": "service.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.remove_user() — qualified call to service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.gleam" },
+ "target": { "name": "summary", "file": "service.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.summary() — qualified call to service module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.gleam" },
+ "target": { "name": "validate_name", "file": "validators.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validate_name() — qualified call to validators module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.gleam" },
+ "target": { "name": "validate_email", "file": "validators.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validate_email() — qualified call to validators module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.gleam" },
+ "target": { "name": "save", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.save() — qualified call to repository module"
+ },
+ {
+ "source": { "name": "get_user", "file": "service.gleam" },
+ "target": { "name": "find_by_id", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.find_by_id() — qualified call to repository module"
+ },
+ {
+ "source": { "name": "remove_user", "file": "service.gleam" },
+ "target": { "name": "delete", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.delete() — qualified call to repository module"
+ },
+ {
+ "source": { "name": "summary", "file": "service.gleam" },
+ "target": { "name": "count", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.count() — qualified call to repository module"
+ },
+ {
+ "source": { "name": "summary", "file": "service.gleam" },
+ "target": { "name": "format_summary", "file": "service.gleam" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "validate_name", "file": "validators.gleam" },
+ "target": { "name": "check_length", "file": "validators.gleam" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "validate_email", "file": "validators.gleam" },
+ "target": { "name": "contains_at", "file": "validators.gleam" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "count", "file": "repository.gleam" },
+ "target": { "name": "count_helper", "file": "repository.gleam" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private recursive helper"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/gleam/main.gleam b/tests/benchmarks/resolution/fixtures/gleam/main.gleam
new file mode 100644
index 00000000..41230f15
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/gleam/main.gleam
@@ -0,0 +1,11 @@
+import service
+import repository
+
+pub fn main() {
+ let repo = repository.new_repo()
+ let result = service.create_user(repo, "alice", "alice@example.com")
+ let user = service.get_user(repo, "alice")
+ let removed = service.remove_user(repo, "alice")
+ let summary = service.summary(repo)
+ result
+}
diff --git a/tests/benchmarks/resolution/fixtures/gleam/repository.gleam b/tests/benchmarks/resolution/fixtures/gleam/repository.gleam
new file mode 100644
index 00000000..5695e24a
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/gleam/repository.gleam
@@ -0,0 +1,40 @@
+pub fn new_repo() {
+ []
+}
+
+pub fn save(repo, name, email) {
+ [#(name, email), ..repo]
+}
+
+pub fn find_by_id(repo, id) {
+ case repo {
+ [] -> Error("not found")
+ [#(name, email), ..rest] ->
+ case name == id {
+ True -> Ok(#(name, email))
+ False -> find_by_id(rest, id)
+ }
+ }
+}
+
+pub fn delete(repo, id) {
+ case repo {
+ [] -> Error("not found")
+ [#(name, _), ..rest] ->
+ case name == id {
+ True -> Ok(rest)
+ False -> delete(rest, id)
+ }
+ }
+}
+
+pub fn count(repo) {
+ count_helper(repo, 0)
+}
+
+fn count_helper(repo, acc) {
+ case repo {
+ [] -> acc
+ [_, ..rest] -> count_helper(rest, acc + 1)
+ }
+}
diff --git a/tests/benchmarks/resolution/fixtures/gleam/service.gleam b/tests/benchmarks/resolution/fixtures/gleam/service.gleam
new file mode 100644
index 00000000..5b695d3a
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/gleam/service.gleam
@@ -0,0 +1,28 @@
+import repository
+import validators
+
+pub fn create_user(repo, name, email) {
+ let valid_name = validators.validate_name(name)
+ let valid_email = validators.validate_email(email)
+ case valid_name, valid_email {
+ True, True -> repository.save(repo, name, email)
+ _, _ -> Error("validation failed")
+ }
+}
+
+pub fn get_user(repo, id) {
+ repository.find_by_id(repo, id)
+}
+
+pub fn remove_user(repo, id) {
+ repository.delete(repo, id)
+}
+
+pub fn summary(repo) {
+ let count = repository.count(repo)
+ format_summary(count)
+}
+
+fn format_summary(count) {
+ "repository contains " <> int_to_string(count) <> " users"
+}
diff --git a/tests/benchmarks/resolution/fixtures/gleam/validators.gleam b/tests/benchmarks/resolution/fixtures/gleam/validators.gleam
new file mode 100644
index 00000000..f163c493
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/gleam/validators.gleam
@@ -0,0 +1,22 @@
+pub fn validate_name(name) {
+ case name {
+ "" -> False
+ _ -> check_length(name, 1, 100)
+ }
+}
+
+pub fn validate_email(email) {
+ case email {
+ "" -> False
+ _ -> contains_at(email)
+ }
+}
+
+fn check_length(value, min, max) {
+ let len = string_length(value)
+ len >= min && len <= max
+}
+
+fn contains_at(email) {
+ string_contains(email, "@")
+}
diff --git a/tests/benchmarks/resolution/fixtures/haskell/Main.hs b/tests/benchmarks/resolution/fixtures/haskell/Main.hs
new file mode 100644
index 00000000..12b944d4
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/haskell/Main.hs
@@ -0,0 +1,17 @@
+module Main where
+
+import qualified Data.Map.Strict as Map
+import Service (createUser, getUser, removeUser, summary)
+
+main :: IO ()
+main = do
+ let store = Map.empty
+ case createUser "u1" "Alice" "alice@example.com" 30 store of
+ Left err -> putStrLn ("Error: " ++ err)
+ Right store1 -> do
+ let user = getUser "u1" store1
+ putStrLn ("Found user: " ++ show user)
+ let total = summary store1
+ putStrLn ("Total users: " ++ show total)
+ let store2 = removeUser "u1" store1
+ putStrLn ("After removal: " ++ show (summary store2))
diff --git a/tests/benchmarks/resolution/fixtures/haskell/Repository.hs b/tests/benchmarks/resolution/fixtures/haskell/Repository.hs
new file mode 100644
index 00000000..ce4eed70
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/haskell/Repository.hs
@@ -0,0 +1,24 @@
+module Repository
+ ( save
+ , findById
+ , delete
+ , count
+ ) where
+
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+
+type UserId = String
+type UserRecord = (String, String, Int)
+
+save :: UserId -> UserRecord -> Map UserId UserRecord -> Map UserId UserRecord
+save uid record store = Map.insert uid record store
+
+findById :: UserId -> Map UserId UserRecord -> Maybe UserRecord
+findById uid store = Map.lookup uid store
+
+delete :: UserId -> Map UserId UserRecord -> Map UserId UserRecord
+delete uid store = Map.delete uid store
+
+count :: Map UserId UserRecord -> Int
+count store = Map.size store
diff --git a/tests/benchmarks/resolution/fixtures/haskell/Service.hs b/tests/benchmarks/resolution/fixtures/haskell/Service.hs
new file mode 100644
index 00000000..0e8d1eb7
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/haskell/Service.hs
@@ -0,0 +1,33 @@
+module Service
+ ( createUser
+ , getUser
+ , removeUser
+ , summary
+ ) where
+
+import qualified Repository as Repo
+import Validators (validateEmail, validateName, validateAge)
+import Data.Map.Strict (Map)
+
+type UserId = String
+type UserRecord = (String, String, Int)
+type Store = Map UserId UserRecord
+
+validateUser :: String -> String -> Int -> Bool
+validateUser name email age =
+ validateName name && validateEmail email && validateAge age
+
+createUser :: UserId -> String -> String -> Int -> Store -> Either String Store
+createUser uid name email age store =
+ if validateUser name email age
+ then Right (Repo.save uid (name, email, age) store)
+ else Left "Validation failed"
+
+getUser :: UserId -> Store -> Maybe UserRecord
+getUser uid store = Repo.findById uid store
+
+removeUser :: UserId -> Store -> Store
+removeUser uid store = Repo.delete uid store
+
+summary :: Store -> Int
+summary store = Repo.count store
diff --git a/tests/benchmarks/resolution/fixtures/haskell/Validators.hs b/tests/benchmarks/resolution/fixtures/haskell/Validators.hs
new file mode 100644
index 00000000..0d454fd4
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/haskell/Validators.hs
@@ -0,0 +1,14 @@
+module Validators
+ ( validateEmail
+ , validateName
+ , validateAge
+ ) where
+
+validateEmail :: String -> Bool
+validateEmail email = '@' `elem` email && '.' `elem` email
+
+validateName :: String -> Bool
+validateName name = length name >= 2 && length name <= 50
+
+validateAge :: Int -> Bool
+validateAge age = age >= 0 && age <= 150
diff --git a/tests/benchmarks/resolution/fixtures/haskell/expected-edges.json b/tests/benchmarks/resolution/fixtures/haskell/expected-edges.json
new file mode 100644
index 00000000..c16b2b4e
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/haskell/expected-edges.json
@@ -0,0 +1,91 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "haskell",
+ "description": "Hand-annotated call edges for Haskell resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "Main.hs" },
+ "target": { "name": "createUser", "file": "Service.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Service (createUser) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.hs" },
+ "target": { "name": "getUser", "file": "Service.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Service (getUser) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.hs" },
+ "target": { "name": "summary", "file": "Service.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Service (summary) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "main", "file": "Main.hs" },
+ "target": { "name": "removeUser", "file": "Service.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Service (removeUser) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.hs" },
+ "target": { "name": "validateName", "file": "Validators.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Validators (validateName) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.hs" },
+ "target": { "name": "validateEmail", "file": "Validators.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Validators (validateEmail) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "validateUser", "file": "Service.hs" },
+ "target": { "name": "validateAge", "file": "Validators.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "import Validators (validateAge) — cross-module imported function call"
+ },
+ {
+ "source": { "name": "createUser", "file": "Service.hs" },
+ "target": { "name": "validateUser", "file": "Service.hs" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-module call to local helper function"
+ },
+ {
+ "source": { "name": "createUser", "file": "Service.hs" },
+ "target": { "name": "save", "file": "Repository.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repo.save — qualified import call via import qualified Repository as Repo"
+ },
+ {
+ "source": { "name": "getUser", "file": "Service.hs" },
+ "target": { "name": "findById", "file": "Repository.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repo.findById — qualified import call via import qualified Repository as Repo"
+ },
+ {
+ "source": { "name": "removeUser", "file": "Service.hs" },
+ "target": { "name": "delete", "file": "Repository.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repo.delete — qualified import call via import qualified Repository as Repo"
+ },
+ {
+ "source": { "name": "summary", "file": "Service.hs" },
+ "target": { "name": "count", "file": "Repository.hs" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repo.count — qualified import call via import qualified Repository as Repo"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/julia/expected-edges.json b/tests/benchmarks/resolution/fixtures/julia/expected-edges.json
new file mode 100644
index 00000000..9d0661c7
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/julia/expected-edges.json
@@ -0,0 +1,112 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "julia",
+ "description": "Hand-annotated call edges for Julia resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "main.jl" },
+ "target": { "name": "new_repo", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.new_repo() — qualified call to Repository module"
+ },
+ {
+ "source": { "name": "main", "file": "main.jl" },
+ "target": { "name": "create_user", "file": "service.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.create_user() — qualified call to Service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.jl" },
+ "target": { "name": "get_user", "file": "service.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.get_user() — qualified call to Service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.jl" },
+ "target": { "name": "remove_user", "file": "service.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.remove_user() — qualified call to Service module"
+ },
+ {
+ "source": { "name": "main", "file": "main.jl" },
+ "target": { "name": "summary", "file": "service.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.summary() — qualified call to Service module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.jl" },
+ "target": { "name": "validate_name", "file": "validators.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validate_name() — qualified call to Validators module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.jl" },
+ "target": { "name": "validate_email", "file": "validators.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validate_email() — qualified call to Validators module"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.jl" },
+ "target": { "name": "save", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.save() — qualified call to Repository module"
+ },
+ {
+ "source": { "name": "get_user", "file": "service.jl" },
+ "target": { "name": "find_by_id", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.find_by_id() — qualified call to Repository module"
+ },
+ {
+ "source": { "name": "remove_user", "file": "service.jl" },
+ "target": { "name": "delete", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.delete() — qualified call to Repository module"
+ },
+ {
+ "source": { "name": "summary", "file": "service.jl" },
+ "target": { "name": "count", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.count() — qualified call to Repository module"
+ },
+ {
+ "source": { "name": "summary", "file": "service.jl" },
+ "target": { "name": "format_summary", "file": "service.jl" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "validate_name", "file": "validators.jl" },
+ "target": { "name": "check_length", "file": "validators.jl" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "validate_email", "file": "validators.jl" },
+ "target": { "name": "contains_at", "file": "validators.jl" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "count", "file": "repository.jl" },
+ "target": { "name": "count_entries", "file": "repository.jl" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/julia/main.jl b/tests/benchmarks/resolution/fixtures/julia/main.jl
new file mode 100644
index 00000000..863ccd23
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/julia/main.jl
@@ -0,0 +1,20 @@
+module App
+
+include("repository.jl")
+include("validators.jl")
+include("service.jl")
+
+using .Repository
+using .Validators
+using .Service
+
+function main()
+ repo = Repository.new_repo()
+ result = Service.create_user(repo, "alice", "alice@example.com")
+ user = Service.get_user(repo, "alice")
+ removed = Service.remove_user(repo, "alice")
+ summary = Service.summary(repo)
+ println(summary)
+end
+
+end # module App
diff --git a/tests/benchmarks/resolution/fixtures/julia/repository.jl b/tests/benchmarks/resolution/fixtures/julia/repository.jl
new file mode 100644
index 00000000..e763b527
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/julia/repository.jl
@@ -0,0 +1,37 @@
+module Repository
+
+function new_repo()
+ Dict{String, Tuple{String, String}}()
+end
+
+function save(repo, name, email)
+ repo[name] = (name, email)
+ :ok
+end
+
+function find_by_id(repo, id)
+ if haskey(repo, id)
+ repo[id]
+ else
+ error("not found")
+ end
+end
+
+function delete(repo, id)
+ if haskey(repo, id)
+ delete!(repo, id)
+ :ok
+ else
+ error("not found")
+ end
+end
+
+function count(repo)
+ count_entries(repo)
+end
+
+function count_entries(repo)
+ length(repo)
+end
+
+end # module Repository
diff --git a/tests/benchmarks/resolution/fixtures/julia/service.jl b/tests/benchmarks/resolution/fixtures/julia/service.jl
new file mode 100644
index 00000000..96787327
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/julia/service.jl
@@ -0,0 +1,33 @@
+module Service
+
+using ..Repository
+using ..Validators
+
+function create_user(repo, name, email)
+ valid_name = Validators.validate_name(name)
+ valid_email = Validators.validate_email(email)
+ if valid_name && valid_email
+ Repository.save(repo, name, email)
+ else
+ error("validation failed")
+ end
+end
+
+function get_user(repo, id)
+ Repository.find_by_id(repo, id)
+end
+
+function remove_user(repo, id)
+ Repository.delete(repo, id)
+end
+
+function summary(repo)
+ cnt = Repository.count(repo)
+ format_summary(cnt)
+end
+
+function format_summary(cnt)
+ "repository contains $cnt users"
+end
+
+end # module Service
diff --git a/tests/benchmarks/resolution/fixtures/julia/validators.jl b/tests/benchmarks/resolution/fixtures/julia/validators.jl
new file mode 100644
index 00000000..876ed8de
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/julia/validators.jl
@@ -0,0 +1,26 @@
+module Validators
+
+function validate_name(name)
+ if isempty(name)
+ return false
+ end
+ check_length(name, 1, 100)
+end
+
+function validate_email(email)
+ if isempty(email)
+ return false
+ end
+ contains_at(email)
+end
+
+function check_length(value, min_len, max_len)
+ len = length(value)
+ len >= min_len && len <= max_len
+end
+
+function contains_at(email)
+ occursin("@", email)
+end
+
+end # module Validators
diff --git a/tests/benchmarks/resolution/fixtures/lua/expected-edges.json b/tests/benchmarks/resolution/fixtures/lua/expected-edges.json
new file mode 100644
index 00000000..15a0a21c
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/lua/expected-edges.json
@@ -0,0 +1,98 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "lua",
+ "description": "Hand-annotated call edges for Lua resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "main.lua" },
+ "target": { "name": "M.validate_email", "file": "validators.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validate_email() — call via required module table"
+ },
+ {
+ "source": { "name": "main", "file": "main.lua" },
+ "target": { "name": "M.create_user", "file": "service.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.create_user() — call via required module table"
+ },
+ {
+ "source": { "name": "main", "file": "main.lua" },
+ "target": { "name": "M.get_user", "file": "service.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.get_user() — call via required module table"
+ },
+ {
+ "source": { "name": "main", "file": "main.lua" },
+ "target": { "name": "M.remove_user", "file": "service.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.remove_user() — call via required module table"
+ },
+ {
+ "source": { "name": "main", "file": "main.lua" },
+ "target": { "name": "M.summary", "file": "service.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "service.summary() — call via required module table"
+ },
+ {
+ "source": { "name": "M.create_user", "file": "service.lua" },
+ "target": { "name": "M.validate_name", "file": "validators.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validate_name() — call via required module table"
+ },
+ {
+ "source": { "name": "M.create_user", "file": "service.lua" },
+ "target": { "name": "M.validate_email", "file": "validators.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validate_email() — call via required module table"
+ },
+ {
+ "source": { "name": "M.create_user", "file": "service.lua" },
+ "target": { "name": "M.save", "file": "repository.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.save() — call via required module table"
+ },
+ {
+ "source": { "name": "M.get_user", "file": "service.lua" },
+ "target": { "name": "M.find_by_id", "file": "repository.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.find_by_id() — call via required module table"
+ },
+ {
+ "source": { "name": "M.remove_user", "file": "service.lua" },
+ "target": { "name": "M.delete", "file": "repository.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.delete() — call via required module table"
+ },
+ {
+ "source": { "name": "M.summary", "file": "service.lua" },
+ "target": { "name": "M.count", "file": "repository.lua" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "repository.count() — call via required module table"
+ },
+ {
+ "source": { "name": "M.validate_name", "file": "validators.lua" },
+ "target": { "name": "check_not_empty", "file": "validators.lua" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to local helper function"
+ },
+ {
+ "source": { "name": "M.validate_email", "file": "validators.lua" },
+ "target": { "name": "check_not_empty", "file": "validators.lua" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to local helper function"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/lua/main.lua b/tests/benchmarks/resolution/fixtures/lua/main.lua
new file mode 100644
index 00000000..5d6eb055
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/lua/main.lua
@@ -0,0 +1,23 @@
+local service = require("service")
+local validators = require("validators")
+
+local function main()
+ local valid, err = validators.validate_email("alice@example.com")
+ if not valid then
+ print("invalid email: " .. err)
+ return
+ end
+
+ service.create_user("1", "Alice", "alice@example.com")
+ service.create_user("2", "Bob", "bob@example.com")
+
+ local user = service.get_user("1")
+ if user then
+ print("found: " .. user.name .. " <" .. user.email .. ">")
+ end
+
+ service.remove_user("2")
+ print(service.summary())
+end
+
+main()
diff --git a/tests/benchmarks/resolution/fixtures/lua/repository.lua b/tests/benchmarks/resolution/fixtures/lua/repository.lua
new file mode 100644
index 00000000..92a2e637
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/lua/repository.lua
@@ -0,0 +1,29 @@
+local M = {}
+
+local users = {}
+
+function M.save(user)
+ users[user.id] = user
+end
+
+function M.find_by_id(id)
+ return users[id]
+end
+
+function M.delete(id)
+ if users[id] then
+ users[id] = nil
+ return true
+ end
+ return false
+end
+
+function M.count()
+ local n = 0
+ for _ in pairs(users) do
+ n = n + 1
+ end
+ return n
+end
+
+return M
diff --git a/tests/benchmarks/resolution/fixtures/lua/service.lua b/tests/benchmarks/resolution/fixtures/lua/service.lua
new file mode 100644
index 00000000..0db187cc
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/lua/service.lua
@@ -0,0 +1,33 @@
+local repository = require("repository")
+local validators = require("validators")
+
+local M = {}
+
+function M.create_user(id, name, email)
+ local valid, err = validators.validate_name(name)
+ if not valid then
+ return nil, err
+ end
+ valid, err = validators.validate_email(email)
+ if not valid then
+ return nil, err
+ end
+ local user = { id = id, name = name, email = email }
+ repository.save(user)
+ return user, nil
+end
+
+function M.get_user(id)
+ return repository.find_by_id(id)
+end
+
+function M.remove_user(id)
+ return repository.delete(id)
+end
+
+function M.summary()
+ local count = repository.count()
+ return string.format("repository contains %d users", count)
+end
+
+return M
diff --git a/tests/benchmarks/resolution/fixtures/lua/validators.lua b/tests/benchmarks/resolution/fixtures/lua/validators.lua
new file mode 100644
index 00000000..a005f6a8
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/lua/validators.lua
@@ -0,0 +1,27 @@
+local M = {}
+
+local function check_not_empty(value)
+ return value ~= nil and value ~= ""
+end
+
+function M.validate_name(name)
+ if not check_not_empty(name) then
+ return false, "name must not be empty"
+ end
+ if #name < 2 then
+ return false, "name must be at least 2 characters"
+ end
+ return true, nil
+end
+
+function M.validate_email(email)
+ if not check_not_empty(email) then
+ return false, "email must not be empty"
+ end
+ if not string.find(email, "@") then
+ return false, "email must contain @"
+ end
+ return true, nil
+end
+
+return M
diff --git a/tests/benchmarks/resolution/fixtures/ocaml/expected-edges.json b/tests/benchmarks/resolution/fixtures/ocaml/expected-edges.json
new file mode 100644
index 00000000..fb4df7ce
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/ocaml/expected-edges.json
@@ -0,0 +1,91 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "ocaml",
+ "description": "Hand-annotated call edges for OCaml resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "validate_user", "file": "service.ml" },
+ "target": { "name": "validate_name", "file": "validators.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validate_name — qualified cross-module call"
+ },
+ {
+ "source": { "name": "validate_user", "file": "service.ml" },
+ "target": { "name": "validate_email", "file": "validators.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validate_email — qualified cross-module call"
+ },
+ {
+ "source": { "name": "validate_user", "file": "service.ml" },
+ "target": { "name": "validate_age", "file": "validators.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Validators.validate_age — qualified cross-module call"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.ml" },
+ "target": { "name": "validate_user", "file": "service.ml" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to local helper function"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.ml" },
+ "target": { "name": "save", "file": "repository.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.save — qualified cross-module call"
+ },
+ {
+ "source": { "name": "get_user", "file": "service.ml" },
+ "target": { "name": "find_by_id", "file": "repository.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.find_by_id — qualified cross-module call"
+ },
+ {
+ "source": { "name": "remove_user", "file": "service.ml" },
+ "target": { "name": "delete", "file": "repository.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.delete — qualified cross-module call"
+ },
+ {
+ "source": { "name": "summary", "file": "service.ml" },
+ "target": { "name": "count", "file": "repository.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Repository.count — qualified cross-module call"
+ },
+ {
+ "source": { "name": "main", "file": "main.ml" },
+ "target": { "name": "create_user", "file": "service.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.create_user — qualified cross-module call from top-level let () ="
+ },
+ {
+ "source": { "name": "main", "file": "main.ml" },
+ "target": { "name": "get_user", "file": "service.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.get_user — qualified cross-module call from top-level let () ="
+ },
+ {
+ "source": { "name": "main", "file": "main.ml" },
+ "target": { "name": "remove_user", "file": "service.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.remove_user — qualified cross-module call from top-level let () ="
+ },
+ {
+ "source": { "name": "main", "file": "main.ml" },
+ "target": { "name": "summary", "file": "service.ml" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "Service.summary — qualified cross-module call (called twice) from top-level let () ="
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/ocaml/main.ml b/tests/benchmarks/resolution/fixtures/ocaml/main.ml
new file mode 100644
index 00000000..4fda7096
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/ocaml/main.ml
@@ -0,0 +1,12 @@
+let () =
+ match Service.create_user "u1" "Alice" "alice@example.com" 30 with
+ | Error msg -> Printf.printf "Error: %s\n" msg
+ | Ok () ->
+ let user = Service.get_user "u1" in
+ (match user with
+ | None -> print_endline "User not found"
+ | Some u -> Printf.printf "Found user: %s\n" u.Repository.name);
+ let total = Service.summary () in
+ Printf.printf "Total users: %d\n" total;
+ Service.remove_user "u1";
+ Printf.printf "After removal: %d\n" (Service.summary ())
diff --git a/tests/benchmarks/resolution/fixtures/ocaml/repository.ml b/tests/benchmarks/resolution/fixtures/ocaml/repository.ml
new file mode 100644
index 00000000..f5fcfa17
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/ocaml/repository.ml
@@ -0,0 +1,19 @@
+type user_record = {
+ name : string;
+ email : string;
+ age : int;
+}
+
+let store : (string, user_record) Hashtbl.t = Hashtbl.create 16
+
+let save uid record =
+ Hashtbl.replace store uid record
+
+let find_by_id uid =
+ Hashtbl.find_opt store uid
+
+let delete uid =
+ Hashtbl.remove store uid
+
+let count () =
+ Hashtbl.length store
diff --git a/tests/benchmarks/resolution/fixtures/ocaml/service.ml b/tests/benchmarks/resolution/fixtures/ocaml/service.ml
new file mode 100644
index 00000000..05b725e7
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/ocaml/service.ml
@@ -0,0 +1,20 @@
+let validate_user name email age =
+ Validators.validate_name name
+ && Validators.validate_email email
+ && Validators.validate_age age
+
+let create_user uid name email age =
+ if validate_user name email age then begin
+ Repository.save uid { Repository.name; email; age };
+ Ok ()
+ end else
+ Error "Validation failed"
+
+let get_user uid =
+ Repository.find_by_id uid
+
+let remove_user uid =
+ Repository.delete uid
+
+let summary () =
+ Repository.count ()
diff --git a/tests/benchmarks/resolution/fixtures/ocaml/validators.ml b/tests/benchmarks/resolution/fixtures/ocaml/validators.ml
new file mode 100644
index 00000000..153d10e1
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/ocaml/validators.ml
@@ -0,0 +1,9 @@
+let validate_email email =
+ String.contains email '@' && String.contains email '.'
+
+let validate_name name =
+ let len = String.length name in
+ len >= 2 && len <= 50
+
+let validate_age age =
+ age >= 0 && age <= 150
diff --git a/tests/benchmarks/resolution/fixtures/r/expected-edges.json b/tests/benchmarks/resolution/fixtures/r/expected-edges.json
new file mode 100644
index 00000000..e1fffde1
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/r/expected-edges.json
@@ -0,0 +1,84 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "r",
+ "description": "Hand-annotated call edges for R resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "run", "file": "main.R" },
+ "target": { "name": "create_user", "file": "service.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source(); create_user is in global scope from service.R"
+ },
+ {
+ "source": { "name": "run", "file": "main.R" },
+ "target": { "name": "get_user", "file": "service.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source(); get_user is in global scope from service.R"
+ },
+ {
+ "source": { "name": "run", "file": "main.R" },
+ "target": { "name": "remove_user", "file": "service.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source(); remove_user is in global scope from service.R"
+ },
+ {
+ "source": { "name": "run", "file": "main.R" },
+ "target": { "name": "list_all_users", "file": "repository.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call; list_all_users is in global scope transitively via service.R sourcing repository.R"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.R" },
+ "target": { "name": "validate_user_input", "file": "validators.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source('validators.R'); validate_user_input in global scope"
+ },
+ {
+ "source": { "name": "create_user", "file": "service.R" },
+ "target": { "name": "save_user", "file": "repository.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source('repository.R'); save_user in global scope"
+ },
+ {
+ "source": { "name": "remove_user", "file": "service.R" },
+ "target": { "name": "find_user_by_id", "file": "repository.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source('repository.R'); find_user_by_id in global scope"
+ },
+ {
+ "source": { "name": "remove_user", "file": "service.R" },
+ "target": { "name": "delete_user", "file": "repository.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source('repository.R'); delete_user in global scope"
+ },
+ {
+ "source": { "name": "get_user", "file": "service.R" },
+ "target": { "name": "find_user_by_id", "file": "repository.R" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Cross-file call via source('repository.R'); find_user_by_id in global scope"
+ },
+ {
+ "source": { "name": "validate_user_input", "file": "validators.R" },
+ "target": { "name": "validate_name", "file": "validators.R" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call within validators.R"
+ },
+ {
+ "source": { "name": "validate_user_input", "file": "validators.R" },
+ "target": { "name": "validate_email", "file": "validators.R" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call within validators.R"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/r/main.R b/tests/benchmarks/resolution/fixtures/r/main.R
new file mode 100644
index 00000000..1e102deb
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/r/main.R
@@ -0,0 +1,12 @@
+source("service.R")
+
+run <- function() {
+ user <- create_user("u1", "Alice", "alice@example.com")
+ found <- get_user("u1")
+ print(found)
+ remove_user("u1")
+ all <- list_all_users()
+ print(all)
+}
+
+run()
diff --git a/tests/benchmarks/resolution/fixtures/r/repository.R b/tests/benchmarks/resolution/fixtures/r/repository.R
new file mode 100644
index 00000000..5f4bc8e9
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/r/repository.R
@@ -0,0 +1,19 @@
+user_store <- list()
+
+save_user <- function(user) {
+ user_store[[user$id]] <<- user
+ user
+}
+
+find_user_by_id <- function(id) {
+ user_store[[id]]
+}
+
+delete_user <- function(id) {
+ user_store[[id]] <<- NULL
+ TRUE
+}
+
+list_all_users <- function() {
+ user_store
+}
diff --git a/tests/benchmarks/resolution/fixtures/r/service.R b/tests/benchmarks/resolution/fixtures/r/service.R
new file mode 100644
index 00000000..b34690d4
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/r/service.R
@@ -0,0 +1,20 @@
+source("validators.R")
+source("repository.R")
+
+create_user <- function(id, name, email) {
+ validate_user_input(name, email)
+ user <- list(id = id, name = name, email = email)
+ save_user(user)
+}
+
+get_user <- function(id) {
+ find_user_by_id(id)
+}
+
+remove_user <- function(id) {
+ existing <- find_user_by_id(id)
+ if (is.null(existing)) {
+ stop("User not found")
+ }
+ delete_user(id)
+}
diff --git a/tests/benchmarks/resolution/fixtures/r/validators.R b/tests/benchmarks/resolution/fixtures/r/validators.R
new file mode 100644
index 00000000..2d22bd86
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/r/validators.R
@@ -0,0 +1,18 @@
+validate_email <- function(email) {
+ if (!grepl("@", email)) {
+ stop("Invalid email address")
+ }
+ TRUE
+}
+
+validate_name <- function(name) {
+ if (nchar(name) < 2) {
+ stop("Name must be at least 2 characters")
+ }
+ TRUE
+}
+
+validate_user_input <- function(name, email) {
+ validate_name(name)
+ validate_email(email)
+}
diff --git a/tests/benchmarks/resolution/fixtures/solidity/Main.sol b/tests/benchmarks/resolution/fixtures/solidity/Main.sol
new file mode 100644
index 00000000..48f96662
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/solidity/Main.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Service.sol";
+import "./Repository.sol";
+
+contract Main {
+ UserService private service;
+ UserRepository private repo;
+
+ constructor() {
+ repo = new UserRepository();
+ service = new UserService(repo);
+ }
+
+ function run() public {
+ service.createUser("u1", "Alice", "alice@example.com");
+ service.getUser("u1");
+ service.removeUser("u1");
+ repo.count();
+ }
+}
diff --git a/tests/benchmarks/resolution/fixtures/solidity/Repository.sol b/tests/benchmarks/resolution/fixtures/solidity/Repository.sol
new file mode 100644
index 00000000..3b99bf84
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/solidity/Repository.sol
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+contract UserRepository {
+ struct User {
+ string id;
+ string name;
+ string email;
+ bool exists;
+ }
+
+ mapping(string => User) private users;
+ string[] private userIds;
+
+ function save(string memory id, string memory name, string memory email) public returns (bool) {
+ users[id] = User(id, name, email, true);
+ userIds.push(id);
+ return true;
+ }
+
+ function findById(string memory id) public view returns (string memory, string memory, string memory) {
+ require(users[id].exists, "User not found");
+ User memory u = users[id];
+ return (u.id, u.name, u.email);
+ }
+
+ function remove(string memory id) public returns (bool) {
+ require(users[id].exists, "User not found");
+ delete users[id];
+ return true;
+ }
+
+ function count() public view returns (uint256) {
+ return userIds.length;
+ }
+}
diff --git a/tests/benchmarks/resolution/fixtures/solidity/Service.sol b/tests/benchmarks/resolution/fixtures/solidity/Service.sol
new file mode 100644
index 00000000..2b98ec71
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/solidity/Service.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import "./Repository.sol";
+import "./Validators.sol";
+
+contract UserService {
+ UserRepository private repo;
+
+ constructor(UserRepository _repo) {
+ repo = _repo;
+ }
+
+ function createUser(string memory id, string memory name, string memory email) public returns (bool) {
+ Validators.validateUserInput(name, email);
+ repo.save(id, name, email);
+ return true;
+ }
+
+ function getUser(string memory id) public view returns (string memory, string memory, string memory) {
+ return repo.findById(id);
+ }
+
+ function removeUser(string memory id) public returns (bool) {
+ repo.findById(id);
+ return repo.remove(id);
+ }
+}
diff --git a/tests/benchmarks/resolution/fixtures/solidity/Validators.sol b/tests/benchmarks/resolution/fixtures/solidity/Validators.sol
new file mode 100644
index 00000000..1c10ab49
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/solidity/Validators.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+library Validators {
+ function validateEmail(string memory email) internal pure returns (bool) {
+ bytes memory b = bytes(email);
+ require(b.length > 3, "Email too short");
+ return true;
+ }
+
+ function validateName(string memory name) internal pure returns (bool) {
+ bytes memory b = bytes(name);
+ require(b.length >= 2, "Name too short");
+ return true;
+ }
+
+ function validateUserInput(string memory name, string memory email) internal pure returns (bool) {
+ validateName(name);
+ validateEmail(email);
+ return true;
+ }
+}
diff --git a/tests/benchmarks/resolution/fixtures/solidity/expected-edges.json b/tests/benchmarks/resolution/fixtures/solidity/expected-edges.json
new file mode 100644
index 00000000..31252ee2
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/solidity/expected-edges.json
@@ -0,0 +1,98 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "solidity",
+ "description": "Hand-annotated call edges for Solidity resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "Main.constructor", "file": "Main.sol" },
+ "target": { "name": "UserRepository", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "constructor",
+ "notes": "new UserRepository() — contract instantiation"
+ },
+ {
+ "source": { "name": "Main.constructor", "file": "Main.sol" },
+ "target": { "name": "UserService", "file": "Service.sol" },
+ "kind": "calls",
+ "mode": "constructor",
+ "notes": "new UserService(repo) — contract instantiation"
+ },
+ {
+ "source": { "name": "Main.run", "file": "Main.sol" },
+ "target": { "name": "UserService.createUser", "file": "Service.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "service.createUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "Main.run", "file": "Main.sol" },
+ "target": { "name": "UserService.getUser", "file": "Service.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "service.getUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "Main.run", "file": "Main.sol" },
+ "target": { "name": "UserService.removeUser", "file": "Service.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "service.removeUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "Main.run", "file": "Main.sol" },
+ "target": { "name": "UserRepository.count", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "repo.count() — method call on UserRepository instance"
+ },
+ {
+ "source": { "name": "UserService.createUser", "file": "Service.sol" },
+ "target": { "name": "Validators.validateUserInput", "file": "Validators.sol" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Validators.validateUserInput() — library function call"
+ },
+ {
+ "source": { "name": "UserService.createUser", "file": "Service.sol" },
+ "target": { "name": "UserRepository.save", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "repo.save() — method call on UserRepository instance"
+ },
+ {
+ "source": { "name": "UserService.getUser", "file": "Service.sol" },
+ "target": { "name": "UserRepository.findById", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "repo.findById() — method call on UserRepository instance"
+ },
+ {
+ "source": { "name": "UserService.removeUser", "file": "Service.sol" },
+ "target": { "name": "UserRepository.findById", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "repo.findById() — existence check before removal"
+ },
+ {
+ "source": { "name": "UserService.removeUser", "file": "Service.sol" },
+ "target": { "name": "UserRepository.remove", "file": "Repository.sol" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "repo.remove() — method call on UserRepository instance"
+ },
+ {
+ "source": { "name": "Validators.validateUserInput", "file": "Validators.sol" },
+ "target": { "name": "Validators.validateName", "file": "Validators.sol" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-library call within Validators"
+ },
+ {
+ "source": { "name": "Validators.validateUserInput", "file": "Validators.sol" },
+ "target": { "name": "Validators.validateEmail", "file": "Validators.sol" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-library call within Validators"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/tsx/App.tsx b/tests/benchmarks/resolution/fixtures/tsx/App.tsx
new file mode 100644
index 00000000..f85f3a5b
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/tsx/App.tsx
@@ -0,0 +1,30 @@
+import { createUser, getUser, removeUser, listUsers } from './service';
+import { validateUser, formatErrors } from './validators';
+import type { User, ValidationResult } from './types';
+
+function UserCard(props: { user: User }): string {
+ return `${props.user.name} (${props.user.email})
`;
+}
+
+function ErrorBanner(props: { message: string }): string {
+ return `${props.message}
`;
+}
+
+export function App(): string {
+ const check: ValidationResult = validateUser('Alice', 'alice@example.com');
+ if (!check.valid) {
+ const msg = formatErrors(check);
+ return ErrorBanner({ message: msg });
+ }
+
+ const user = createUser('Alice', 'alice@example.com');
+ const found = getUser(user.id);
+ if (!found) {
+ return ErrorBanner({ message: 'User not found' });
+ }
+
+ const card = UserCard({ user: found });
+ const all = listUsers();
+ removeUser(user.id);
+ return card;
+}
diff --git a/tests/benchmarks/resolution/fixtures/tsx/expected-edges.json b/tests/benchmarks/resolution/fixtures/tsx/expected-edges.json
new file mode 100644
index 00000000..a1291ada
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/tsx/expected-edges.json
@@ -0,0 +1,98 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "tsx",
+ "description": "Hand-annotated call edges for TSX resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "validateUser", "file": "validators.tsx" },
+ "target": { "name": "isValidName", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file helper call within validators"
+ },
+ {
+ "source": { "name": "validateUser", "file": "validators.tsx" },
+ "target": { "name": "isValidEmail", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file helper call within validators"
+ },
+ {
+ "source": { "name": "createUser", "file": "service.tsx" },
+ "target": { "name": "validateUser", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from validators"
+ },
+ {
+ "source": { "name": "createUser", "file": "service.tsx" },
+ "target": { "name": "formatErrors", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from validators"
+ },
+ {
+ "source": { "name": "createUser", "file": "service.tsx" },
+ "target": { "name": "generateId", "file": "service.tsx" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file helper call within service"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "validateUser", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from validators"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "formatErrors", "file": "validators.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from validators"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "ErrorBanner", "file": "App.tsx" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file component call within App"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "createUser", "file": "service.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from service"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "getUser", "file": "service.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from service"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "UserCard", "file": "App.tsx" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file component call within App"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "listUsers", "file": "service.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from service"
+ },
+ {
+ "source": { "name": "App", "file": "App.tsx" },
+ "target": { "name": "removeUser", "file": "service.tsx" },
+ "kind": "calls",
+ "mode": "static",
+ "notes": "Direct imported function call from service"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/tsx/service.tsx b/tests/benchmarks/resolution/fixtures/tsx/service.tsx
new file mode 100644
index 00000000..f63ccabc
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/tsx/service.tsx
@@ -0,0 +1,31 @@
+import type { User } from './types';
+import { formatErrors, validateUser } from './validators';
+
+const store: Map = new Map();
+
+function generateId(): string {
+ return Math.random().toString(36).slice(2);
+}
+
+export function createUser(name: string, email: string): User {
+ const result = validateUser(name, email);
+ if (!result.valid) {
+ throw new Error(formatErrors(result));
+ }
+ const id = generateId();
+ const user: User = { id, name, email };
+ store.set(id, user);
+ return user;
+}
+
+export function getUser(id: string): User | undefined {
+ return store.get(id);
+}
+
+export function removeUser(id: string): boolean {
+ return store.delete(id);
+}
+
+export function listUsers(): User[] {
+ return Array.from(store.values());
+}
diff --git a/tests/benchmarks/resolution/fixtures/tsx/types.tsx b/tests/benchmarks/resolution/fixtures/tsx/types.tsx
new file mode 100644
index 00000000..1582a21f
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/tsx/types.tsx
@@ -0,0 +1,10 @@
+export interface User {
+ id: string;
+ name: string;
+ email: string;
+}
+
+export interface ValidationResult {
+ valid: boolean;
+ errors: string[];
+}
diff --git a/tests/benchmarks/resolution/fixtures/tsx/validators.tsx b/tests/benchmarks/resolution/fixtures/tsx/validators.tsx
new file mode 100644
index 00000000..131e1310
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/tsx/validators.tsx
@@ -0,0 +1,24 @@
+import type { ValidationResult } from './types';
+
+function isValidEmail(email: string): boolean {
+ return email.includes('@') && email.includes('.');
+}
+
+function isValidName(name: string): boolean {
+ return name.length >= 2;
+}
+
+export function validateUser(name: string, email: string): ValidationResult {
+ const errors: string[] = [];
+ if (!isValidName(name)) {
+ errors.push('Name too short');
+ }
+ if (!isValidEmail(email)) {
+ errors.push('Invalid email');
+ }
+ return { valid: errors.length === 0, errors };
+}
+
+export function formatErrors(result: ValidationResult): string {
+ return result.errors.join(', ');
+}
diff --git a/tests/benchmarks/resolution/fixtures/zig/expected-edges.json b/tests/benchmarks/resolution/fixtures/zig/expected-edges.json
new file mode 100644
index 00000000..9a361005
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/zig/expected-edges.json
@@ -0,0 +1,112 @@
+{
+ "$schema": "../../expected-edges.schema.json",
+ "language": "zig",
+ "description": "Hand-annotated call edges for Zig resolution benchmark",
+ "edges": [
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserRepository.init", "file": "repository.zig" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "UserRepository.init() — struct init function via @import"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserService.init", "file": "service.zig" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "UserService.init() — struct init function via @import"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "validateEmail", "file": "validators.zig" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validateEmail() — function call via @import"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserService.createUser", "file": "service.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "svc.createUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserService.getUser", "file": "service.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "svc.getUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserService.removeUser", "file": "service.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "svc.removeUser() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "main", "file": "main.zig" },
+ "target": { "name": "UserService.summary", "file": "service.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "svc.summary() — method call on UserService instance"
+ },
+ {
+ "source": { "name": "UserService.createUser", "file": "service.zig" },
+ "target": { "name": "validateName", "file": "validators.zig" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validateName() — function call via @import"
+ },
+ {
+ "source": { "name": "UserService.createUser", "file": "service.zig" },
+ "target": { "name": "validateEmail", "file": "validators.zig" },
+ "kind": "calls",
+ "mode": "module-function",
+ "notes": "validators.validateEmail() — function call via @import"
+ },
+ {
+ "source": { "name": "UserService.createUser", "file": "service.zig" },
+ "target": { "name": "UserRepository.save", "file": "repository.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "self.repo.save() — method call on *UserRepository field"
+ },
+ {
+ "source": { "name": "UserService.getUser", "file": "service.zig" },
+ "target": { "name": "UserRepository.findById", "file": "repository.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "self.repo.findById() — method call on *UserRepository field"
+ },
+ {
+ "source": { "name": "UserService.removeUser", "file": "service.zig" },
+ "target": { "name": "UserRepository.delete", "file": "repository.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "self.repo.delete() — method call on *UserRepository field"
+ },
+ {
+ "source": { "name": "UserService.summary", "file": "service.zig" },
+ "target": { "name": "UserRepository.count", "file": "repository.zig" },
+ "kind": "calls",
+ "mode": "receiver-typed",
+ "notes": "self.repo.count() — method call on *UserRepository field"
+ },
+ {
+ "source": { "name": "validateName", "file": "validators.zig" },
+ "target": { "name": "isNotEmpty", "file": "validators.zig" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ },
+ {
+ "source": { "name": "validateEmail", "file": "validators.zig" },
+ "target": { "name": "isNotEmpty", "file": "validators.zig" },
+ "kind": "calls",
+ "mode": "same-file",
+ "notes": "Same-file call to private helper function"
+ }
+ ]
+}
diff --git a/tests/benchmarks/resolution/fixtures/zig/main.zig b/tests/benchmarks/resolution/fixtures/zig/main.zig
new file mode 100644
index 00000000..d086a4e7
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/zig/main.zig
@@ -0,0 +1,32 @@
+const std = @import("std");
+const repo_mod = @import("repository.zig");
+const svc_mod = @import("service.zig");
+const validators = @import("validators.zig");
+
+const UserRepository = repo_mod.UserRepository;
+const UserService = svc_mod.UserService;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ const allocator = gpa.allocator();
+
+ var repo = UserRepository.init(allocator);
+ var svc = UserService.init(&repo);
+
+ if (!validators.validateEmail("alice@example.com")) {
+ std.debug.print("invalid email\n", .{});
+ return;
+ }
+
+ _ = svc.createUser("1", "Alice", "alice@example.com");
+ _ = svc.createUser("2", "Bob", "bob@example.com");
+
+ if (svc.getUser("1")) |user| {
+ std.debug.print("found: {s} <{s}>\n", .{ user.name, user.email });
+ }
+
+ _ = svc.removeUser("2");
+
+ const total = svc.summary();
+ std.debug.print("repository contains {} users\n", .{total});
+}
diff --git a/tests/benchmarks/resolution/fixtures/zig/repository.zig b/tests/benchmarks/resolution/fixtures/zig/repository.zig
new file mode 100644
index 00000000..0929e246
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/zig/repository.zig
@@ -0,0 +1,36 @@
+const std = @import("std");
+
+pub const User = struct {
+ id: []const u8,
+ name: []const u8,
+ email: []const u8,
+};
+
+pub const UserRepository = struct {
+ users: std.StringHashMap(User),
+
+ pub fn init(allocator: std.mem.Allocator) UserRepository {
+ return UserRepository{
+ .users = std.StringHashMap(User).init(allocator),
+ };
+ }
+
+ pub fn save(self: *UserRepository, user: User) void {
+ self.users.put(user.id, user) catch {};
+ }
+
+ pub fn findById(self: *UserRepository, id: []const u8) ?User {
+ return self.users.get(id);
+ }
+
+ pub fn delete(self: *UserRepository, id: []const u8) bool {
+ if (self.users.fetchRemove(id)) |_| {
+ return true;
+ }
+ return false;
+ }
+
+ pub fn count(self: *UserRepository) usize {
+ return self.users.count();
+ }
+};
diff --git a/tests/benchmarks/resolution/fixtures/zig/service.zig b/tests/benchmarks/resolution/fixtures/zig/service.zig
new file mode 100644
index 00000000..5dc16c4f
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/zig/service.zig
@@ -0,0 +1,33 @@
+const repo_mod = @import("repository.zig");
+const validators = @import("validators.zig");
+
+const User = repo_mod.User;
+const UserRepository = repo_mod.UserRepository;
+
+pub const UserService = struct {
+ repo: *UserRepository,
+
+ pub fn init(repo: *UserRepository) UserService {
+ return UserService{ .repo = repo };
+ }
+
+ pub fn createUser(self: *UserService, id: []const u8, name: []const u8, email: []const u8) ?User {
+ if (!validators.validateName(name)) return null;
+ if (!validators.validateEmail(email)) return null;
+ const user = User{ .id = id, .name = name, .email = email };
+ self.repo.save(user);
+ return user;
+ }
+
+ pub fn getUser(self: *UserService, id: []const u8) ?User {
+ return self.repo.findById(id);
+ }
+
+ pub fn removeUser(self: *UserService, id: []const u8) bool {
+ return self.repo.delete(id);
+ }
+
+ pub fn summary(self: *UserService) usize {
+ return self.repo.count();
+ }
+};
diff --git a/tests/benchmarks/resolution/fixtures/zig/validators.zig b/tests/benchmarks/resolution/fixtures/zig/validators.zig
new file mode 100644
index 00000000..ed724730
--- /dev/null
+++ b/tests/benchmarks/resolution/fixtures/zig/validators.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+
+fn isNotEmpty(value: []const u8) bool {
+ return value.len > 0;
+}
+
+pub fn validateName(name: []const u8) bool {
+ if (!isNotEmpty(name)) return false;
+ return name.len >= 2;
+}
+
+pub fn validateEmail(email: []const u8) bool {
+ if (!isNotEmpty(email)) return false;
+ for (email) |c| {
+ if (c == '@') return true;
+ }
+ return false;
+}
diff --git a/tests/benchmarks/resolution/resolution-benchmark.test.ts b/tests/benchmarks/resolution/resolution-benchmark.test.ts
index 40019a37..f29f5ef7 100644
--- a/tests/benchmarks/resolution/resolution-benchmark.test.ts
+++ b/tests/benchmarks/resolution/resolution-benchmark.test.ts
@@ -65,24 +65,39 @@ const FIXTURES_DIR = path.join(import.meta.dirname, 'fixtures');
* while still tracking regressions.
*/
const THRESHOLDS: Record = {
- // Mature — high bars
+ // Mature — high bars (100% precision, high recall)
javascript: { precision: 0.85, recall: 0.5 },
typescript: { precision: 0.85, recall: 0.5 },
+ tsx: { precision: 0.85, recall: 0.8 },
+ bash: { precision: 0.85, recall: 0.8 },
+ ruby: { precision: 0.85, recall: 0.8 },
+ c: { precision: 0.6, recall: 0.2 },
// Established — medium bars
python: { precision: 0.7, recall: 0.3 },
go: { precision: 0.7, recall: 0.3 },
java: { precision: 0.7, recall: 0.3 },
csharp: { precision: 0.5, recall: 0.2 },
+ kotlin: { precision: 0.6, recall: 0.2 },
// Lower bars — resolution still maturing
rust: { precision: 0.6, recall: 0.2 },
- php: { precision: 0.6, recall: 0.2 },
- c: { precision: 0.6, recall: 0.2 },
cpp: { precision: 0.6, recall: 0.2 },
- kotlin: { precision: 0.6, recall: 0.2 },
swift: { precision: 0.5, recall: 0.15 },
- // Minimal — call resolution not yet implemented for these
- ruby: { precision: 0.0, recall: 0.0 },
+ haskell: { precision: 0.0, recall: 0.0 },
+ lua: { precision: 0.0, recall: 0.0 },
+ ocaml: { precision: 0.0, recall: 0.0 },
+ // Minimal — call resolution not yet implemented or grammar unavailable
scala: { precision: 0.0, recall: 0.0 },
+ php: { precision: 0.6, recall: 0.2 },
+ elixir: { precision: 0.0, recall: 0.0 },
+ dart: { precision: 0.0, recall: 0.0 },
+ zig: { precision: 0.0, recall: 0.0 },
+ fsharp: { precision: 0.0, recall: 0.0 },
+ gleam: { precision: 0.0, recall: 0.0 },
+ clojure: { precision: 0.0, recall: 0.0 },
+ julia: { precision: 0.0, recall: 0.0 },
+ r: { precision: 0.0, recall: 0.0 },
+ erlang: { precision: 0.0, recall: 0.0 },
+ solidity: { precision: 0.0, recall: 0.0 },
};
/** Default thresholds for languages not explicitly listed. */
From c1c60259a4a26918ef00d88cc7f2a2325db3aaaf Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:22:17 -0600
Subject: [PATCH 05/10] fix(bench): fix constructor tracing and docstring in
loader-hook (#878)
- Use return value of wrapClassMethods in instrumentExports so constructor
wrapping actually takes effect
- Convert wrappedClass from arrow function to regular function with
Reflect.construct so it works as a constructor target
- Replace false AsyncLocalStorage claim in docstring with accurate
description of the shared mutable call stack
---
.../resolution/tracer/loader-hook.mjs | 20 +++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/tests/benchmarks/resolution/tracer/loader-hook.mjs b/tests/benchmarks/resolution/tracer/loader-hook.mjs
index d249286c..74f3d43f 100644
--- a/tests/benchmarks/resolution/tracer/loader-hook.mjs
+++ b/tests/benchmarks/resolution/tracer/loader-hook.mjs
@@ -1,10 +1,14 @@
/**
* ESM loader hook that instruments function calls to capture dynamic call edges.
*
- * Uses AsyncLocalStorage to track the call stack across async boundaries.
+ * Maintains a module-scoped call stack to track caller→callee relationships.
* Patches module exports so that every function/method call is recorded as
* a { caller, callee } edge with file information.
*
+ * Note: the call stack is a shared mutable array, so concurrent async call
+ * chains may interleave. This is acceptable for the current sequential
+ * benchmark driver but would need AsyncLocalStorage for parallel execution.
+ *
* Usage:
* node --import ./loader-hook.mjs driver.mjs
*
@@ -97,23 +101,24 @@ function wrapClassMethods(cls, className, file) {
}
}
- // Also wrap the constructor to track instantiation calls
+ // Also wrap the constructor to track instantiation calls.
+ // Must use Reflect.construct so the wrapper is a valid constructor target.
const origConstructor = cls;
- const wrappedClass = (...args) => {
+ function wrappedClass(...args) {
if (callStack.length > 0) {
const caller = callStack[callStack.length - 1];
recordEdge(caller.name, caller.file, `${className}.constructor`, file);
}
callStack.push({ name: `${className}.constructor`, file });
try {
- const instance = new origConstructor(...args);
+ const instance = Reflect.construct(origConstructor, args, new.target || origConstructor);
callStack.pop();
return instance;
} catch (e) {
callStack.pop();
throw e;
}
- };
+ }
wrappedClass.prototype = origConstructor.prototype;
wrappedClass.__traced = true;
Object.defineProperty(wrappedClass, 'name', { value: className });
@@ -137,9 +142,8 @@ function instrumentExports(moduleExports, filePath) {
? Object.getOwnPropertyNames(value.prototype).filter((k) => k !== 'constructor')
: [];
if (protoKeys.length > 0 || /^[A-Z]/.test(key)) {
- // Treat as a class
- wrapClassMethods(value, key, file);
- instrumented[key] = value;
+ // Treat as a class — use return value so constructor wrapping takes effect
+ instrumented[key] = wrapClassMethods(value, key, file);
} else {
instrumented[key] = wrapFunction(value, key, file);
}
From 9f711760ca0af2d79c0fbdab21a68a96d3dbe725 Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:22:32 -0600
Subject: [PATCH 06/10] fix(bench): replace tautological assertion and add
threshold TODOs (#878)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Remove `toBeGreaterThanOrEqual(0)` which always passes (array length
is never negative) — replace with `Array.isArray` check
- Add TODO comments with tracking issue numbers (#872-#875) to all
zero-threshold languages so they don't get forgotten
---
tests/benchmarks/resolution/resolution-benchmark.test.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/tests/benchmarks/resolution/resolution-benchmark.test.ts b/tests/benchmarks/resolution/resolution-benchmark.test.ts
index f29f5ef7..ffb76b07 100644
--- a/tests/benchmarks/resolution/resolution-benchmark.test.ts
+++ b/tests/benchmarks/resolution/resolution-benchmark.test.ts
@@ -82,12 +82,17 @@ const THRESHOLDS: Record = {
rust: { precision: 0.6, recall: 0.2 },
cpp: { precision: 0.6, recall: 0.2 },
swift: { precision: 0.5, recall: 0.15 },
+ // TODO(#872): raise haskell thresholds once call resolution lands
haskell: { precision: 0.0, recall: 0.0 },
+ // TODO(#873): raise lua thresholds once call resolution lands
lua: { precision: 0.0, recall: 0.0 },
+ // TODO(#874): raise ocaml thresholds once call resolution lands
ocaml: { precision: 0.0, recall: 0.0 },
// Minimal — call resolution not yet implemented or grammar unavailable
+ // TODO(#875): raise scala thresholds once call resolution lands
scala: { precision: 0.0, recall: 0.0 },
php: { precision: 0.6, recall: 0.2 },
+ // TODO: raise thresholds below once call resolution is implemented for each language
elixir: { precision: 0.0, recall: 0.0 },
dart: { precision: 0.0, recall: 0.0 },
zig: { precision: 0.0, recall: 0.0 },
@@ -323,10 +328,10 @@ describe('Call Resolution Precision/Recall', () => {
test('builds graph successfully', () => {
expect(resolvedEdges).toBeDefined();
+ expect(Array.isArray(resolvedEdges)).toBe(true);
// Some languages may have 0 resolved call edges if resolution isn't
// implemented yet — that's okay, the precision/recall tests will
// catch it at the appropriate threshold level.
- expect(resolvedEdges.length).toBeGreaterThanOrEqual(0);
});
test('expected edges manifest is non-empty', () => {
From 0f1b50996f9a1c82a1698472172d29d71710510b Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:22:44 -0600
Subject: [PATCH 07/10] fix(bench): add type annotation to allModes object
(#878)
Type allModes as Record
to avoid implicit-any errors under strict TypeScript compilation.
---
scripts/update-benchmark-report.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/update-benchmark-report.ts b/scripts/update-benchmark-report.ts
index 054e8e0b..3b2f9dec 100644
--- a/scripts/update-benchmark-report.ts
+++ b/scripts/update-benchmark-report.ts
@@ -438,7 +438,7 @@ if (fs.existsSync(readmePath)) {
}
// Per-mode breakdown across all languages
- const allModes = {};
+ const allModes: Record = {};
for (const [, m] of langEntries) {
if (!m.byMode) continue;
for (const [mode, data] of Object.entries(m.byMode)) {
From e5d9533485fc4b0c43984d34b05bba7dca700ed0 Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:27:22 -0600
Subject: [PATCH 08/10] fix(build): gracefully skip uninstalled grammar
packages in WASM build
Move require.resolve() inside try/catch so build-wasm.ts skips
unavailable packages with a warning instead of crashing mid-build.
Also fix lint issues in tsx benchmark fixture.
---
scripts/build-wasm.ts | 9 ++++++++-
tests/benchmarks/resolution/fixtures/tsx/App.tsx | 8 ++++----
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/scripts/build-wasm.ts b/scripts/build-wasm.ts
index 32e82dba..07f617d3 100644
--- a/scripts/build-wasm.ts
+++ b/scripts/build-wasm.ts
@@ -128,7 +128,14 @@ let failed = 0;
let rejected = 0;
for (const g of grammars) {
- const pkgDir = dirname(require.resolve(`${g.pkg}/package.json`));
+ let pkgDir: string;
+ try {
+ pkgDir = dirname(require.resolve(`${g.pkg}/package.json`));
+ } catch {
+ failed++;
+ console.warn(` WARN: Skipping ${g.name}.wasm — package '${g.pkg}' not installed`);
+ continue;
+ }
const grammarDir = g.sub ? resolve(pkgDir, g.sub) : pkgDir;
console.log(`Building ${g.name}.wasm from ${grammarDir}...`);
diff --git a/tests/benchmarks/resolution/fixtures/tsx/App.tsx b/tests/benchmarks/resolution/fixtures/tsx/App.tsx
index f85f3a5b..35656642 100644
--- a/tests/benchmarks/resolution/fixtures/tsx/App.tsx
+++ b/tests/benchmarks/resolution/fixtures/tsx/App.tsx
@@ -1,6 +1,6 @@
-import { createUser, getUser, removeUser, listUsers } from './service';
-import { validateUser, formatErrors } from './validators';
+import { createUser, getUser, listUsers, removeUser } from './service';
import type { User, ValidationResult } from './types';
+import { formatErrors, validateUser } from './validators';
function UserCard(props: { user: User }): string {
return `${props.user.name} (${props.user.email})
`;
@@ -24,7 +24,7 @@ export function App(): string {
}
const card = UserCard({ user: found });
- const all = listUsers();
+ const users = listUsers();
removeUser(user.id);
- return card;
+ return `${card} (${users.length} total)`;
}
From ab52c8439a6b61b75c475313eb5a77fccb0a4bb3 Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:39:30 -0600
Subject: [PATCH 09/10] fix(bench): set bash and ruby thresholds to zero (#878)
Both bash (unsupported language) and ruby (0 resolved edges currently)
were misclassified as "Mature" with 0.85/0.8 thresholds, causing
deterministic CI test failures since computeMetrics returns precision=0
for empty resolved sets.
---
tests/benchmarks/resolution/resolution-benchmark.test.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/benchmarks/resolution/resolution-benchmark.test.ts b/tests/benchmarks/resolution/resolution-benchmark.test.ts
index ffb76b07..e2de4905 100644
--- a/tests/benchmarks/resolution/resolution-benchmark.test.ts
+++ b/tests/benchmarks/resolution/resolution-benchmark.test.ts
@@ -69,8 +69,10 @@ const THRESHOLDS: Record = {
javascript: { precision: 0.85, recall: 0.5 },
typescript: { precision: 0.85, recall: 0.5 },
tsx: { precision: 0.85, recall: 0.8 },
- bash: { precision: 0.85, recall: 0.8 },
- ruby: { precision: 0.85, recall: 0.8 },
+ // TODO: raise thresholds once bash call resolution is implemented
+ bash: { precision: 0.0, recall: 0.0 },
+ // TODO: raise thresholds once ruby call resolution is reliable
+ ruby: { precision: 0.0, recall: 0.0 },
c: { precision: 0.6, recall: 0.2 },
// Established — medium bars
python: { precision: 0.7, recall: 0.3 },
From fa04b84b92a1ac127ca59f0dcbfb24e6222aef7a Mon Sep 17 00:00:00 2001
From: carlos-alm <127798846+carlos-alm@users.noreply.github.com>
Date: Mon, 6 Apr 2026 01:54:38 -0600
Subject: [PATCH 10/10] fix(bench): acknowledge 3.9.1 1-file rebuild regression
in guard (#878)
The 3.9.1 benchmark data shows 1-file rebuild went from 562ms to 767ms
(+36%), same root cause as the 3.9.0 entry (native incremental path
re-runs graph-wide phases). This was blocking CI on main and all PRs.
---
tests/benchmarks/regression-guard.test.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tests/benchmarks/regression-guard.test.ts b/tests/benchmarks/regression-guard.test.ts
index 92b0780c..07e7a302 100644
--- a/tests/benchmarks/regression-guard.test.ts
+++ b/tests/benchmarks/regression-guard.test.ts
@@ -71,12 +71,17 @@ const SKIP_VERSIONS = new Set(['3.8.0']);
* benchmark workers measured native rusqlite open/close overhead (~27ms vs
* ~10ms with direct better-sqlite3). Fixed by wiring CODEGRAPH_ENGINE through
* openRepo(); v3.10.0 benchmarks will reflect the corrected measurements.
+ *
+ * - 3.9.1:1-file rebuild — continuation of the 3.9.0 regression; native
+ * incremental path still re-runs graph-wide phases on single-file rebuilds.
+ * Benchmark data shows 562 → 767ms (+36%). Same root cause as 3.9.0 entry.
*/
const KNOWN_REGRESSIONS = new Set([
'3.9.0:1-file rebuild',
'3.9.0:fnDeps depth 1',
'3.9.0:fnDeps depth 3',
'3.9.0:fnDeps depth 5',
+ '3.9.1:1-file rebuild',
]);
/**