Repository navigation
Description
Version
dev build from source at 4da8f877 (tag v0.10.8 lineage). I am 19 commits behind origin/main; none of those 19 touch internal/cbm/service_patterns.c, src/pipeline/pass_cross_repo.c, or any Swift front-end file, and my own local commits do not touch them either. So this should reproduce on stock main.
Platform
macOS (Apple Silicon) — macOS 26.6.2, arm64
Install channel
Built from source
Binary variant
standard
What happened, and what did you expect?
A Swift client that calls an HTTP endpoint produces no HTTP_CALLS edge and no client-side Route node, so mode=cross-repo-intelligence can never link it to the server that serves that path. A TypeScript client making the byte-identical call links correctly.
Swift is not missing from the pattern table. internal/cbm/service_patterns.c lines 109-112 already carry:
/* Swift */
{"Alamofire", CBM_SVC_HTTP, NULL},
{"Moya", CBM_SVC_HTTP, NULL},
{"URLSession", CBM_SVC_HTTP, NULL},The gap is one step earlier: the Swift front end never emits the client-side Route node + HTTP_CALLS edge carrying a url_path that the cross-repo matcher joins on.
I expected the Swift client to behave like the TypeScript one — an HTTP_CALLS edge with url_path=/api/v1/widgets, and a CROSS_HTTP_CALLS edge naming the Go handler.
Note on #523, which asks "what is the minimum a call and a route need to share to be linked?" — the TypeScript half of this repro answers that, and shows the matcher itself is working. This report is the opposite failure: the matcher is fine, one language front end never feeds it. Not a duplicate of #523, #678, #706 (those are matcher/extractor bugs where the call side does produce an edge), nor of #551 (SwiftPM manifest resolution for package-graph edges, a different pass).
Reproduction
Three tiny repos, all declaring the same path /api/v1/widgets. The only variable is the client language. Script attached below; it writes the fixtures and prints the five MCP calls to make.
Results:
| Repo | Language | Route nodes |
HTTP_CALLS |
CROSS_HTTP_CALLS to server |
|---|---|---|---|---|
srv-go |
Go + chi | 2 | — | — |
cli-ts |
TypeScript + axios | 2 | 2 | 2 |
cli-swift |
Swift + URLSession | 0 | 0 | 0 |
TypeScript, working — MATCH ()-[r:CROSS_HTTP_CALLS]->() RETURN ...:
url_path target_project target_file target_function transport
/api/v1/widgets xtest-srv-go main.go listWidgets GET
/api/v1/widgets xtest-srv-go main.go createWidget POST
Swift, same path: cross_http_calls: 0, total_cross_edges: 0.
Graph schema for the Swift fixture shows no Route label and no HTTP_CALLS edge type. Its only edge for the URLSession call is a bare USAGE — no CALLS edge is emitted at all.
repro-swift-http.sh
#!/usr/bin/env bash
# Reproduce: a Swift client produces no HTTP_CALLS, so cross-repo-intelligence
# finds no edges. A TypeScript client with the identical path produces both.
# Builds three tiny repos, then index each and run cross-repo-intelligence.
set -euo pipefail
B="${1:-/tmp/xrepo}"
rm -rf "$B"; mkdir -p "$B"/srv-go "$B"/cli-ts "$B"/cli-swift
cat > "$B/srv-go/go.mod" <<'EOF'
module example.com/srv
go 1.22
EOF
cat > "$B/srv-go/main.go" <<'EOF'
package main
import "github.com/go-chi/chi/v5"
func main() {
r := chi.NewRouter()
r.Get("/api/v1/widgets", listWidgets)
r.Post("/api/v1/widgets", createWidget)
http.ListenAndServe(":8080", r)
}
func listWidgets(w http.ResponseWriter, r *http.Request) {}
func createWidget(w http.ResponseWriter, r *http.Request) {}
EOF
cat > "$B/cli-ts/package.json" <<'EOF'
{ "name": "cli-ts", "version": "1.0.0", "dependencies": { "axios": "^1.6.0" } }
EOF
cat > "$B/cli-ts/client.ts" <<'EOF'
import axios from "axios";
export async function listWidgets() {
return axios.get("/api/v1/widgets");
}
export async function createWidget(body: unknown) {
return axios.post("/api/v1/widgets", body);
}
EOF
cat > "$B/cli-swift/Package.swift" <<'EOF'
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "CliSwift",
targets: [.target(name: "CliSwift", path: "Sources/CliSwift")]
)
EOF
mkdir -p "$B/cli-swift/Sources/CliSwift"
cat > "$B/cli-swift/Sources/CliSwift/Client.swift" <<'EOF'
import Foundation
final class Client {
func listWidgets() async throws -> Data {
let url = URL(string: "https://example.com/api/v1/widgets")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func createWidget(body: Data) async throws -> Data {
var request = URLRequest(url: URL(string: "https://example.com/api/v1/widgets")!)
request.httpMethod = "POST"
let (data, _) = try await URLSession.shared.upload(for: request, from: body)
return data
}
}
EOF
for d in srv-go cli-ts cli-swift; do
(cd "$B/$d" && git init -q . && git add -A \
&& git -c user.email=t@t -c user.name=t commit -qm init)
done
echo "Fixtures built in $B"
echo
echo "Now, through the MCP server:"
echo " index_repository repo_path=$B/srv-go name=xtest-srv-go mode=full"
echo " index_repository repo_path=$B/cli-ts name=xtest-cli-ts mode=full"
echo " index_repository repo_path=$B/cli-swift name=xtest-cli-swift mode=full"
echo " index_repository repo_path=$B/cli-ts name=xtest-cli-ts mode=cross-repo-intelligence target_projects=[\"xtest-srv-go\"]"
echo " index_repository repo_path=$B/cli-swift name=xtest-cli-swift mode=cross-repo-intelligence target_projects=[\"xtest-srv-go\"]"On a real project
Same shape at scale, on a 462-file / 64k-line Swift app talking to a Go backend:
| Go + TS backend repo | Swift client repo | |
|---|---|---|
Route nodes |
332 | label absent |
HTTP_CALLS edges |
215 | edge type absent |
Method nodes |
2,877 | 3,766 |
CALLS edges |
20,650 | 12,017 |
Swift parsing itself is healthy — 3,766 methods, 12,017 call edges. Only the HTTP layer is missing. On the backend side the 215 HTTP_CALLS include its TypeScript frontend (frontend/lib/api/classes/items.ts alone contributes 20), so TS client extraction works in a real tree too.
In the real Swift repo the failure takes a slightly different route to the same place. A CALLS edge for the URLSession call does exist, but resolves onto a local property:
callee: URLSession.shared.data
strategy: suffix_match confidence: 0.21
resolved: <project>.HomeboxKit.Sources.HomeboxUI.PickedFile.data
file: HomeboxKit/Sources/HomeboxFeatures/Chores/ChoresDetailViewModel.swift:105
Because cbm_service_pattern_* matches the library identifier in the resolved qualified name, and the resolved name is a local PickedFile.data, the check cannot fire. The Swift graph also has no Package label at all, so no framework identifier can ever appear in a resolved name — the Go/TS project has 215 Package nodes with an external flag.
That suffix_match at 0.21 may be worth a look on its own: Swift calls into system frameworks are being resolved onto same-named local members, which affects more than the HTTP case.
Project scale (if relevant)
Repro fixtures: 6 files. Real project: 462 Swift files / 64,101 lines against a 1,485-file Go+TS backend.
Activity
CaptainMittens commentedon Aug 29, 2026
Follow-up on the two gaps #1896 deliberately left open. Both change what gets
extracted, which CONTRIBUTING puts behind prior design discussion, so I am
asking before opening a PR rather than after.
1. A URL built by a constructor — code written and ready, not submitted
#1896 makes a bare string argument reachable. Real Swift almost never writes
one, because Swift has no URL literal. It writes this instead:
The literal then sits two levels below the argument list. From the vendored
grammar, dumped rather than guessed:
extract_url_or_topic_argsees only the outer node and gives up, so the URLnever reaches the service-pattern table and no
Routenode forms.I have written and tested the fix, on
fix/swift-nested-url-constructor,stacked on #1896. I have not opened a PR for it. It steps past the
postfix_expressionand into the constructor's own argument list, and unwrapsonly
URL,URLComponentsandURLRequest, so any other constructor keeps itsown meaning. A non-literal argument such as
URL(string: base + path)fallsthrough to the ordinary handling unchanged.
Reproduce-first, on
extractionandpipeline: the two behaviour tests failwithout it, the guard test (a non-URL constructor must not have its string
borrowed by the outer call) passes either way. With it, 569 passed, 0 failed,
and a
Routenode forms for/api/v1/widgetsfrom the nested shape.Happy to open it, drop it, or rework it — your call. If the answer is that a
nested-constructor unwrap does not belong in the extractor at all, that is a
useful answer too and I will close the branch.
2. Alamofire's
AF.shorthand — I do not think this should be fixed as-isAF.request(…)is Alamofire's documented everyday form, and it does not matchthe pattern table. The table holds
Alamofire, andAF.requestcontains nosuch text.
The obvious fix is to add
"AF."tohttp_libraries. I think that would be amistake, because
match_qnis an unanchoredstrstr:A two-character token then also matches
LEAF.request,PDF.parse,CSAF.get— anything ending inAFbefore a dot. I checked the three repos Ihave indexed and found no such identifier, so the risk is real but unmeasured
rather than demonstrated.
Making it safe means anchoring the match on a segment boundary, and that changes
behaviour for every pattern in every language — a much bigger change than this
issue, and one I would not make without you asking for it. Flagging it here so
it is on the record rather than proposing it.
fix(grammar): stop the Swift scanner shifting past the width of an int
fix(grammar): stop the Swift scanner shifting past the width of an int
docs(vendored): record the Swift scanner shift patch in MANIFEST.md
fix(grammar): stop the Swift scanner shifting past the width of an int
fix(grammar): stop the Swift try-bang entry shifting past its own type
docs(vendored): record the Swift scanner shift patch in MANIFEST.md
fix(extraction): reach Swift call arguments through call_suffix
fix(extraction): reach a Swift URL built by a constructor
fix(grammar): stop the Swift scanner shifting past the width of an int
fix(grammar): stop the Swift try-bang entry shifting past its own type
docs(vendored): record the Swift scanner shift patch in MANIFEST.md
docs(vendored): record the Swift scanner shift patch in MANIFEST.md
fix(extraction): reach Swift call arguments through call_suffix
fix(extraction): reach a Swift URL built by a constructor
DeusData commentedon Sep 3, 2026
Fixed on main by
5802ccdd(PR #1896, CaptainMittens): Swift calls now reach their argument list throughcall_suffix, soURLSessioncalls yield their URL string and theHTTP_CALLSedges are emitted. Thank you for the report — the reproduction is what made the root cause quick to confirm.fix(extraction): reach a Swift URL built by a constructor
fix(extraction): reach a Swift URL built by a constructor
fix(extraction): reach a Swift URL built by a constructor