Skip to content

The Drift Log · Software inventory you can trust

Indexing Internal Repositories by Function Signature Not Name

31 October 2026 · 3 min read · 749 words · established

A search beam fanning across a constellation of crystal nodes with one lit bright

Text search fails across monorepos because names diverge. Indexing by normalized function signatures reveals identical utility duplicates instantly.

An engineer needs to partition an array into fixed-size batches. Before writing it, they check the codebase. They run rg "chunkArray" or search the internal library wiki. The search returns zero matches.

They write chunk(), write unit tests, and commit it.

Three directories over, another team already uses batchItems(). In the legacy billing service, it is called splitList(). In a shared worker utility, it exists as sliceEvery(). Four functions with identical semantics, identical memory characteristics, and four different names.

Text search relies on developers sharing a vocabulary. In any repository older than eighteen months or larger than twenty contributors, they do not. When search fails, duplication is the rational response. Over time, your component registry becomes a graveyard of unlinked duplicates, and attempts to run a code reuse audit degenerate into string-matching exercises that miss the majority of prior art.

Normalising Functions to Type Shapes

Names are arbitrary labels. Types, contracts, and parameter positions are structural facts.

To find existing capability across disparate teams, index code by its normalised signature instead of its identifier. When an abstract syntax tree (AST) parser processes a utility function, strip the variable and function names. Retain the parameter types, their arity, return types, and whether the function is synchronous, async, or pure.

Consider three utilities found in the same monorepo:

// team-a/src/utils/arrays.ts
export function batchList<T>(items: T[], batchSize: number): T[][]

// team-b/src/helpers/collections.ts
export function chunk<Item>(arr: Item[], size: number): Item[][]

// shared/src/primitives.ts
export function paginateArray<V>(values: readonly V[], limit: number): V[][]

A text search treats these as three unrelated symbols. A signature indexer maps all three to the same canonical key:

([T[], number]) -> T[][] { pure: true }

When an engineer or an automated lint rule asks for ([T[], number]) -> T[][], the index returns all three locations immediately. Instead of claiming you have three distinct utilities, you reveal that you have one utility implemented three times, often with slight, untested variations in edge-case handling (such as what happens when size <= 0).

As we argue in A Component Count Is Not an Inventory, counting exported symbols gives you an inflated number, not a usable catalog. Grouping by canonical signature exposes what you actually own.

The Limits of Pure Signature Matching

Structural indexing is not a total solution. It runs into primitive blindness.

If you search for (string, string) -> boolean, your index will return string equality checks, URL slug matchers, path segment normalisers, and case-insensitive prefix validators. The signature is too broad to convey intent.

Attempting to fix primitive blindness with semantic text analysis puts you right back into the naming trap. A reliable index handles broad signatures through three constraints:

  1. Generic arity: Generic parameters (T, K extends keyof T) provide structural leverage. A function taking (T[], (item: T) => string) -> Record<string, T[]> has almost zero semantic ambiguity; it is groupBy.
  2. Purity evaluation: Functions that take arguments and return values without reading external state or returning void can be automatically executed against standard invariant test cases.
  3. Property probing: If two functions share a signature and both return identical outputs across five identical fuzz inputs, they are candidates for consolidation. If they diverge, they are distinct operations despite sharing primitive types.

Without validation, a structural catalog will surface false matches. With lightweight execution, it distinguishes genuine duplicates from dead code and accidental type collisions.

How SHPBL Discovers Capability

SHPBL approaches codebase harvesting structurally. When we extract reusable components, we do not rely on author-assigned names or loose descriptions.

We run repositories through a first-party discovery engine that evaluates functions against a 40-primitive structural matrix. Candidate code is evaluated directly in a certification harness, recording repeatable verdicts: CERTIFIED, PROVISIONAL (reproducible, with correctness not yet asserted), INCONCLUSIVE (the harness could not exercise the input contract), or FAILED.

The output is model-independent, computed rather than generated, and verified without runtime models. You can test your own codebase through our free repository evaluation or review the execution gauntlet on our live harness.

What to Do on Monday

Do not buy or build an indexing platform this week. Write a 50-line AST script using your language's native parser (such as ts-morph or Go's go/ast).

  1. Parse all exported utility files in your monorepo.
  2. Emit a flat CSV containing: File Path, Export Name, and Canonical Type Tuple (e.g., (string[], string) -> boolean).
  3. Run sort | uniq -c on the type tuple column.

Look at any signature with a count greater than three. You will immediately find identical functions hiding behind different names. Pick the best-tested implementation, point the other callers to it, and delete the rest.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.