> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/clyrisai/gitresolve/llms.txt
> Use this file to discover all available pages before exploring further.

# Disambiguation: Identifying the Candidate's Git Identity

> Understand how GitResolve's resolveOwnerAndCategorize algorithm resolves ambiguous link sets into a single owner profile with a confidence score.

A developer's portfolio page typically contains far more git links than just their own profile and repositories. Project pages link to upstream libraries, open-source work references maintainers, collaboration sections mention teammates, and blog posts embed links to third-party repos. When GitResolve scrapes a portfolio or parses a resume PDF, it collects all of these links together — and then must figure out which one of them is actually the candidate. The `resolveOwnerAndCategorize` function solves this problem by cross-referencing profile links against repo links and applying a four-case decision algorithm that produces both an `ownerProfile` and a `confidence` level explaining how certain the resolution is.

## The knownOwnerProfile bypass

When the input is already a `git_profile` or `repo_url`, the owner is known before disambiguation runs. The resolver extracts the username directly from the URL, constructs a `knownOwnerProfile`, and passes it to `resolveOwnerAndCategorize`. This skips the four-case algorithm entirely.

```typescript theme={null}
// When you pass a known owner, the algorithm is bypassed
resolveOwnerAndCategorize(links, "portfolio", knownOwnerProfile);
// → confidence: 'high', ownerProfile set directly from knownOwnerProfile
// → warning added: 'Owner strictly determined by profile URL input: <username>'
```

<Tip>
  If you already know the candidate's GitHub username — for example, they applied through a form that collected it — pass it as `knownOwnerProfile` to guarantee `confidence: 'high'` regardless of what links the portfolio contains.
</Tip>

## The four disambiguation cases

`resolveOwnerAndCategorize` inspects the full set of `ExtractedGitLink` objects found in a source and applies one of four resolution strategies depending on what is present.

<Steps>
  <Step title="Case 1 — Single unique profile username: high confidence">
    If exactly one distinct git profile username appears across all profile-type links (after deduplication by username, case-insensitive), that user is unambiguously the owner.

    **Result:** `ownerProfile` set to that profile link, `confidence: 'high'`.

    This is the most common case for a personal portfolio that links only to the candidate's own GitHub profile in the header or footer.

    ```typescript theme={null}
    // Portfolio HTML contains only one profile link: github.com/janedoe
    // → ownerProfile.username = 'janedoe', confidence = 'high'
    ```
  </Step>

  <Step title="Case 2 — Multiple distinct profile usernames: cross-reference with repos">
    When two or more distinct profile usernames are found, GitResolve cannot immediately determine which is the candidate. Instead it counts how many repo links belong to each username and uses that score to pick the owner.

    A warning is always added: `'Multiple profile links found: alice, bob, charlie'`.

    The outcome depends on the repo scores:

    | Scenario                                                                 | Confidence | Behaviour                                                                      |
    | ------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------ |
    | One profile has strictly more matching repos than all others             | `high`     | That profile is the owner. A warning lists the repo scores per username.       |
    | Two or more profiles are tied for the highest repo count (and count > 0) | `medium`   | First tied profile is chosen. Warning flags the tie.                           |
    | No repo links match any profile username                                 | `low`      | First profile in the list is chosen. Warning notes the mismatch.               |
    | Multiple profiles found but zero repo links exist                        | `low`      | First profile is chosen. Warning notes no repos available for cross-reference. |

    ```typescript theme={null}
    // Profile links: github.com/janedoe, github.com/alice-mentor
    // Repo links: 4 repos owned by janedoe, 1 owned by alice-mentor
    // → ownerProfile.username = 'janedoe', confidence = 'high'
    // → warning: 'Owner determined by repo cross-reference: janedoe(4 repos), alice-mentor(1 repos)'
    ```
  </Step>

  <Step title="Case 3 — No profile links, only repo links: infer from username frequency">
    Some resumes and older portfolios list repository links but no direct profile URLs. In this case, GitResolve groups all repo links by their `username` field and picks the username that appears most often.

    A synthetic profile link is constructed at `https://{host}/{username}` (e.g. `https://github.com/janedoe`) and set as `ownerProfile`.

    | Scenario                                              | Confidence |
    | ----------------------------------------------------- | ---------- |
    | All repos belong to a single username                 | `medium`   |
    | One username owns more repos than all others combined | `medium`   |
    | Two or more usernames are tied for the most repos     | `low`      |

    ```typescript theme={null}
    // No profile links found
    // Repo links: janedoe/api(×3), janedoe/frontend(×1), third-party/lib(×1)
    // → synthetic ownerProfile.url = 'https://github.com/janedoe'
    // → confidence = 'medium'
    // → warning: 'No profile link found — inferred from repo majority: janedoe(4), third-party(1)'
    ```
  </Step>

  <Step title="Case 4 — No links at all: confidence none">
    If the input yielded zero `ExtractedGitLink` objects — the page could not be fetched, the PDF contained no recognisable git URLs, or all links were filtered as reserved paths — disambiguation cannot produce any result.

    **Result:** `ownerProfile: null`, `confidence: 'none'`, warning: `'No git links found'`.
  </Step>
</Steps>

## Confidence level reference

| Level    | Meaning                                                 | Typical source                                                                                                                            |
| -------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `high`   | Owner identity is certain                               | Direct `git_profile`/`repo_url` input, or one clear profile found, or one profile dominates repos                                         |
| `medium` | Owner identity is a strong inference but not guaranteed | Multiple profiles tied for top repo count (Case 2), or owner inferred from repo-username frequency with no explicit profile link (Case 3) |
| `low`    | Owner identity is a best guess                          | Multiple tied profiles or repos; no supporting evidence for a clear winner                                                                |
| `none`   | Owner identity could not be determined                  | No git links found at all, or LinkedIn-only source                                                                                        |

## Repo categorization

Once `ownerUsername` is established, every link in the input set is assigned to one of three buckets. Deduplication by URL (case-insensitive) is applied across all three buckets before the result is returned.

| Bucket          | Population rule                                                                           |
| --------------- | ----------------------------------------------------------------------------------------- |
| `ownedRepos`    | `link.type === 'repo'` **and** `link.username` matches `ownerUsername` (case-insensitive) |
| `contributions` | `link.type === 'pull_request'` **or** `link.type === 'issue'`                             |
| `externalRepos` | `link.type === 'repo'` **and** `link.username` does **not** match `ownerUsername`         |

Profile links and gist links are not placed in any bucket — they appear only in `allLinks`.

<Info>
  Username matching is always case-insensitive. A portfolio linking to `github.com/JaneDoe` and a repo owned by `janedoe` will be correctly matched to the same person.
</Info>

## Calling resolveOwnerAndCategorize directly

While the function is called internally by `scrapePortfolio` and `parseResume`, you can invoke it directly when you have a pre-built set of `ExtractedGitLink` objects.

<Note>
  The return type of `resolveOwnerAndCategorize` is the internal `OwnerResolution` interface, which is **not exported** from `@clyrisai/gitresolve`. TypeScript will infer the type structurally — you can access all fields on the return value without importing the type name.
</Note>

```typescript theme={null}
import { resolveOwnerAndCategorize } from "@clyrisai/gitresolve";
import type { ExtractedGitLink } from "@clyrisai/gitresolve";

const links: ExtractedGitLink[] = [
  {
    url: "https://github.com/janedoe",
    provider: "github",
    type: "profile",
    username: "janedoe",
  },
  {
    url: "https://github.com/janedoe/api-server",
    provider: "github",
    type: "repo",
    username: "janedoe",
    repo: "api-server",
  },
  {
    url: "https://github.com/alice/some-library",
    provider: "github",
    type: "repo",
    username: "alice",
    repo: "some-library",
  },
  {
    url: "https://github.com/torvalds/linux/pull/99",
    provider: "github",
    type: "pull_request",
    username: "torvalds",
    repo: "linux",
    number: "99",
  },
];

const resolution = resolveOwnerAndCategorize(links);

console.log(resolution.ownerProfile?.username); // 'janedoe'
console.log(resolution.confidence);             // 'high'
console.log(resolution.ownedRepos.length);      // 1  (janedoe/api-server)
console.log(resolution.externalRepos.length);   // 1  (alice/some-library)
console.log(resolution.contributions.length);   // 1  (torvalds/linux#99)
```

### Using the knownOwnerProfile parameter

```typescript theme={null}
const knownOwner: ExtractedGitLink = {
  url: "https://github.com/janedoe",
  provider: "github",
  type: "profile",
  username: "janedoe",
};

// All links categorized against janedoe; algorithm skipped
const resolution = resolveOwnerAndCategorize(links, "portfolio", knownOwner);
// → confidence: 'high', ownerProfile: knownOwner
```

<CardGroup cols={2}>
  <Card title="Input Classification" icon="filter" href="/concepts/input-classification">
    How inputs are categorized before disambiguation runs
  </Card>

  <Card title="Result Structure" icon="brackets-curly" href="/concepts/result-structure">
    The full shape of ResolverResult and AggregatedResult
  </Card>
</CardGroup>
