Skip to main content

Discovery System

The Discovery phase is Phase 1 of the pipeline: it finds every Swift type that should become a root node in the graph. ModelGraphGenerator supports two complementary marker strategies — macro annotations (@ChimeraSchema) and protocol conformance (RootModel) — and combines results from both.

Architecture

MacroDiscovery — Three-Strategy Approach

Because macro indexing in Xcode's IndexStoreDB is not always complete (it depends on whether the project has been built), MacroDiscovery tries three strategies in order, merging unique results:

Query the symbol index for occurrences of @ChimeraSchema used as a reference. From each reference location, search for the struct / class definition within ±3 lines.

@ChimeraSchema         ← reference occurrence at line 12
struct Product { ← definition at line 13 ✓

Limitation: Requires an up-to-date index build.

Strategy 2 — Definition Scan + Source Check

Iterate every struct / class definition in the index, read the actual source file, and check lines [nearLine-5 … nearLine+1] for the @ChimeraSchema token.

Advantage: Works even when macro references aren't indexed, because it reads source files directly.

Strategy 3 — File-System Regex (Primary)

Walk every .swift file under --source-path and apply a regex:

@ChimeraSchema\s*(\([^)]*\))?\s*\n?\s*(public\s+|internal\s+|...)?
(struct|class|enum)\s+(\w+)

This is the most reliable fallback — it finds @ChimeraSchema annotations regardless of index freshness or build state.

ProtocolDiscovery — Three-Step Approach

Step 1 — Find Protocol USR

indexStore.forEachCanonicalSymbolOccurrence(
containing: "RootModel", anchorStart: true, anchorEnd: true
) { occurrence in
if occurrence.symbol.kind == .protocol { capture USR }
}

The USR (Unified Symbol Resolution) is the stable identifier Xcode assigns to every declaration — e.g. s:7MyApp9RootModelP.

Step 2 — Query .baseOf Relations

indexStore.forEachSymbolOccurrence(byUSR: protocolUSR, roles: .baseOf) {
// occurrence.location = where "struct Product: RootModel" is written
findTypeAtLocation(file, line) → IndexedSymbol
}

The .baseOf relationship maps a protocol to all types that conform to it.

Step 3 — Regex Fallback

If the index doesn't have conformance data (e.g. fresh checkout, never built):

let pattern = #"(struct|class)\s+(\w+)\s*:\s*[^{]*\bRootModel\b"#
// Matches: struct User: RootModel
// class Org: Codable, RootModel

Deduplication

Results from both services are merged by DiscoveryCoordinator. A symbol is considered a duplicate if its USR matches an already-seen symbol, or (for synthetic symbols without USR) if the name + file pair matches.

IndexedSymbol

The output of discovery — a lightweight handle pointing to a Swift declaration:

struct IndexedSymbol {
let name: String // "Product"
let usr: String // "s:7MyApp7ProductV" (empty for synthetic)
let path: String // "/src/Models/Product.swift"
let line: Int // 14
let kind: Kind // .struct | .class | .enum
}

Why Two Strategies?

SituationMacro works?Protocol works?
Project never built✓ (regex)✓ (regex)
Project built once✓ (all 3)✓ (all 3)
Index stale✓ (regex)✓ (regex)
Large monorepo, partial build✓ (regex)~ partial

Using both strategies ensures the widest coverage regardless of build state.