Skip to content

Swift: no client-side Route or HTTP_CALLS emitted, so cross-repo-intelligence returns 0 edges (URLSession is already in the pattern table) #1892

Description

@CaptainMittens

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edgesux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions