# Build and maintain a contributor directory from repository history

This is a portable task brief for an LLM coding agent working in an existing
repository, especially one assembled from several upstream projects. Give the
agent this document with access to the target repository. The deliverable is an
accurate contributor directory, an evidence-backed historical audit, and a PR
workflow that keeps attribution current.

Use it as task instructions or as a reference in your agent's repository
instructions. It does not require model fine-tuning. Examples below use fictional
identities and proposed commands; the target project's agent must implement or
adapt those commands before documenting them as available.

## How the Hermes implementation works

Hermes stores an email-to-GitHub-login lookup in `contributors/emails/`. For
example, a file named `contributors/emails/jane.doe@example.com` contains:

```text
janedoe
# Source PR or a short attribution note
```

The filename matches the email recorded in Git. The first nonempty,
non-comment line supplies the existing GitHub username. Several email files
may point to the same person. Different emails usually produce independent
file additions, reducing the merge conflicts caused by editing one shared
dictionary. Two additions for the same email still need reconciliation.

These are the relevant parts of Hermes, inspected at commit
`895b506ec93d759c6bb74efcd787a7ef627ce6e0`:

| Component | What it does |
| --- | --- |
| [Directory instructions](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/contributors/README.md) | Defines the filename and file-content convention. |
| [Add helper](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/scripts/add_contributor.py) | Creates a mapping; repeating the same mapping succeeds; conflicting usernames and filenames differing only in case are refused. |
| [Author resolver](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/scripts/releases/authors.py) | Combines a frozen legacy dictionary with directory entries, with directory entries taking precedence; recognizes both GitHub noreply formats; otherwise returns the Git author name. |
| [PR audit helper](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/scripts/audit_pr_attribution.py) | Checks branch-local author emails and can attempt to create mappings using GitHub information. |
| [Attribution workflow](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/.github/workflows/contributor-check.yml) | Fails for certain unmapped PR author emails and prints repair commands. |
| [CI caller](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/.github/workflows/ci.yaml) | Invokes the attribution workflow when changes are classified for the Python lane. |
| [Release generator](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/scripts/release.py) | Uses the resolver for authors and human coauthors when producing release credits. |
| [Release audit](https://github.com/NousResearch/hermes-agent/blob/895b506ec93d759c6bb74efcd787a7ef627ce6e0/scripts/contributor_audit.py) | Checks authors, coauthor trailers, and some salvage references against release credits. |

The directory records existing usernames. It does not create accounts. GitHub
itself associates command-line commits with accounts through their commit email;
adding a repository mapping does not change that association or rewrite past
commits. See [GitHub's commit-email instructions](https://docs.github.com/en/account-and-profile/how-tos/email-preferences/setting-your-commit-email-address).

Port the structure and purpose, then address the concrete gaps relevant to your
project. Hermes's PR gate checks non-merge commit authors, not coauthor trailers;
it has project-specific exclusions; and its noreply handling differs from its
release resolver. Its release audit also limits its merged-PR lookup to 300
results and does not resolve numeric salvage references. Those choices cannot
establish complete attribution for a large repository assembled from many
sources. Do not copy Hermes's contributor identities or its exclusions into an
unrelated repository.

## Task to give your LLM

> Work in this repository and implement the contributor-attribution system
> described below. Recover authors and human coauthors from the complete history
> reachable from the target revision. Trace imported, cherry-picked, squashed, or
> adapted work to its upstream commits and PRs where local history is incomplete.
> Create one email-to-existing-GitHub-username file per confirmed mapping, retain
> unresolved cases in an audit report, and preserve source-project credit.
> Implement a local PR check and a CI check using the same attribution rules.
> Update the contributor guide, repository agent instructions, and PR template so
> future humans and coding agents know how to add their identities. Validate the
> real workflow and report the remaining attribution gaps. Never invent an email,
> username, account association, or source relationship.

Start with these inputs. Discover missing values from the checkout where
possible; continue the local audit while reporting genuinely missing access.

| Input | How to determine it |
| --- | --- |
| Target repository | The current checkout and its hosting remote. |
| Target revision | The maintained branch's full commit SHA, recorded before scanning. Do not assume the branch is called `main`. |
| Upstream projects | Explicit source links in commit messages, PRs, existing credits, import notes, and repository documentation. |
| Hosting access | Existing read-only access to commit and PR metadata. No stored credentials in deliverables. |
| Project conventions | Existing agent instructions, contributor guide, test runner, and release tooling. |

Stay on the existing checkout and preserve unrelated edits. Creating branches,
rewriting history, committing, pushing, or opening PRs requires the operator's
instruction. This task should change attribution tooling and documentation,
without changing application behavior.

## Required output and formats

Use this small layout, adapting existing equivalent files where appropriate:

```text
contributors/
  README.md
  emails/
    jane.doe@example.com
    jane.work@example.org
  imports/
    source-owner--source-project--LOCAL_COMMIT_SHA.md
  audit.md
scripts/
  contributors.py
  add_contributor.py
  audit_contributors.py
  check_contributors.py
.github/
  workflows/contributor-check.yml
  PULL_REQUEST_TEMPLATE.md
CONTRIBUTING.md
AGENTS.md
```

`scripts/contributors.py` is the shared implementation used by the small CLI
entry points. Reuse existing tooling instead of creating parallel resolvers.
Python examples are a default for a Python project; another project can use its
existing scripting runtime and update the instructions consistently.

### Email mappings

Keep Hermes's simple format: the exact observed commit or coauthor email is the
filename; the existing GitHub login, without `@`, is the first content line.
Evidence goes in `#` comment lines. For example:

```text
janedoe
# Source: https://github.com/source-owner/source-project/pull/123
# Evidence: author association on upstream commit FULL_SOURCE_COMMIT_SHA
```

Use only confirmed mappings. Do not put `unknown`, a display name, a guessed
login, or an empty value in the login field. A GitHub account existing is not
enough evidence that a particular email belongs to it.

Preserve email spelling. Reject path separators, control characters, symlinks,
and filenames unsupported by the project's supported filesystems. Detect
case-folded filename collisions before writing. If real historical addresses
cannot safely use this format, record the affected cases and implement a
documented encoded-key format consistently in the writer, loader, and check.
Do not silently normalize two addresses into one identity.

The add helper must be repeatable: an existing identical mapping succeeds
without changing bytes; a different login for the same email fails with a
clear diagnostic. Correcting an existing association is an explicit, reviewed
edit with evidence. If the target already has a legacy author map, preserve it
or migrate it deliberately; do not import Hermes's legacy map.

### Records for imported work

For each identified import, add a small record tying the retained work to its
source. Use a filename containing the source project and local integration SHA
so independent imports can add independent files. Record:

| Field | Required content |
| --- | --- |
| Source | Repository URL and original commit SHA and/or PR URL. |
| Local integration | Full local commit SHA and affected paths or feature. |
| Relationship | Merge, cherry-pick, squash, copied code, or adaptation, with the evidence supporting that classification. |
| Original credit | Relevant authors and coauthors, with confirmed usernames where available. |
| Integration credit | The person adapting or integrating the work, when supported by the local history. |
| Attribution evidence | Links and a short explanation of how the imported changes match. |
| Existing notices | Paths or links to source license, notice, and copyright material; keep existing notices intact. |
| Open questions | Missing source history, unresolved identities, or an uncertain relationship. |

An email mapping answers who an email represents. An import record answers
where a contribution came from. A repository assembled from multiple projects
needs both answers. A confirmed source contributor with no known email can be
credited here without inventing an email file.

### Historical audit

`contributors/audit.md` must record the audited revision, scope, number of
commits visited, unique author and coauthor emails, resolutions, exclusions,
conflicts, unresolved identities, and source imports investigated. Each
unresolved row needs a name if known, observed email if any, commit or PR
reference, reason, and next useful step. Separate missing API access from a
completed lookup with no match.

Preserve people with no known GitHub account as named contributors in the
report or import record. Historical uncertainty must remain visible. Repeating
the audit at the same revision with the same evidence should produce the same
files; avoid run timestamps and unstable ordering in generated content.

## Historical backfill procedure

### 1. Establish the history available

Inspect status, remotes, existing attribution data, `.mailmap`, and source
references. Check whether the checkout is shallow:

```bash
git status --short
git rev-parse --is-shallow-repository
git rev-parse HEAD
```

Resolve and freeze the intended target revision, which may differ from `HEAD`.
Use `git rev-list TARGET_SHA` as the commit inventory. Obtain missing history
from known remotes if accessible without overwriting the checkout. Otherwise
record incomplete coverage and continue with available evidence.

Traverse every parent reachable from the target. Using only `--first-parent`,
current file blame, recent commits, or all local refs would produce a different
scope. The default is the project's integrated history, including contributions
whose code later changed or disappeared. Git documents reachability and revision
ranges in [git-log](https://git-scm.com/docs/git-log).

### 2. Collect identities before resolving them

For every inventoried commit, extract the SHA, raw author name and email, raw
committer name and email, parent SHAs, and complete message. Collect every
`Co-authored-by: Name <email>` trailer, including trailers on merge commits.
Use structured parsing and a format that preserves multiline messages, rather
than splitting commit records on ordinary newlines or a printable separator
that may appear in a name or message.

Git's `%an` and `%ae` preserve the recorded author; `%aN` and `%aE` apply
`.mailmap`. Keep raw identities for lookup, and use any existing mailmap as
additional evidence rather than discarding aliases. See the format definitions
in [git-log](https://git-scm.com/docs/git-log).

Distinguish the person who authored the work from the person who committed or
merged it. Keep the integration role in the evidence where relevant. Likewise,
the PR submitter may differ from a commit author. Do not assign every email in
a PR to its submitter.

Recognize human coauthors and explicitly identified automation. Use a documented
list of known bot identities or confirmed hosting account types. Do not exclude
humans because their names contain a maintainer's handle or their emails share
a vendor's domain. Every exclusion must appear in the audit with its reason.

### 3. Resolve emails using evidence

Use this order, checking for contradictory evidence at every step:

1. Reuse existing mappings supported by the target's attribution records.
2. Recognize an authentic GitHub noreply address. Both
   `ID+LOGIN@users.noreply.github.com` and
   `LOGIN@users.noreply.github.com` can provide a candidate login. Keep explicit
   mappings ahead of these patterns, and corroborate historical or conflicting
   candidates with the source commit or contributor record.
3. Look up the exact commit in the appropriate GitHub repository. Compare the
   response's raw author email with the observed email and use its author login
   when present. The committer login identifies a different role.
4. Examine the original PR's commits and explicit attribution. Recover each
   relevant author's association separately.
5. Search the exact public email, then check that the result actually matches.
6. Use an explicit public identity statement or contributor confirmation when
   available. Otherwise record the identity as unresolved.

Useful read-only examples, after setting the task-specific variables:

```bash
gh api "repos/${target_repo}/commits/${commit_sha}" \
  --jq '{sha, email: .commit.author.email, login: .author.login, committer: .committer.login}'

gh api -X GET search/users -f q="${commit_email} in:email" \
  --jq '.items[] | {login, html_url}'
```

The commit response distinguishes raw Git author metadata from GitHub's author
and committer accounts; see [Get a commit](https://docs.github.com/en/rest/commits/commits#get-a-commit).
Email search covers public profile emails, so an empty result is not proof
that the contributor lacks an account. See [Searching users](https://docs.github.com/en/search-github/searching-on-github/searching-users#search-by-account-name-full-name-or-public-email).

Never derive a login from a person's name or a normal email's local part.
Never take the first search hit without checking it. Use publicly recorded
commit identities or the contributor's chosen noreply address; do not seek
private email addresses. Fetch only evidence needed for unresolved cases,
cache duplicate lookups, and report API limits or access failures without
treating them as identity results.

### 4. Recover credit lost during imports

Look for source PR URLs, repository-qualified references, cherry-pick source
SHAs, and explicit wording such as “based on” or “adapted from.” A bare `#123`
needs the correct repository context. A mention of `@someone` is a lead to
investigate, rather than proof that their work was integrated.

When a merge retains upstream ancestry, inspect those commits directly. When a
squash or copied import erases ancestry, compare the local integration change
with the cited upstream changes. Use changed paths and matching patches as
evidence, then inspect the upstream authors and coauthors for that work.

For a partial import, credit contributors to the imported material. Do not
automatically import an upstream project's entire contributor roster. Preserve
credit for both the original work and later substantive adaptations. The goal
is to document the project's origins, regardless of which version is now used.

Paginate historical PR and commit queries and record coverage. GitHub's PR
commits endpoint returns at most 250 commits even with pagination; for larger
PRs, recover the relevant commits through fetched Git history or the documented
commits-list alternative. See [List commits on a pull request](https://docs.github.com/en/rest/pulls/pulls#list-commits-on-a-pull-request).

If an upstream repository is unavailable or no origin can be demonstrated,
retain the local credit and document the missing source evidence. Do not claim
the missing original authors have been recovered.

### 5. Write and review the backfill

Default the audit command to report-only mode. An explicit `--apply` mode may
create evidence-backed mapping and import files. It must leave uncertain cases
in the report and refuse to overwrite conflicting mappings.

Review the diff for unrelated data, accidental reassignment, duplicate identities,
case collisions, and unsupported filenames. A person using several confirmed
emails gets several mappings to the same login. Do not rewrite past commits
merely to change credits, and do not delete old contributors because current
blame no longer mentions them.

## Make future PR attribution automatic

Implement one shared parser and resolver for historical auditing, local PR
checks, CI, and any existing release-credit generator. New attribution files
must be validated even if their email is not used in a PR commit.

The proposed local check is:

```bash
python3 scripts/check_contributors.py --base BASE_SHA --head HEAD_SHA
```

Compute the merge base from those supplied revisions and scan commits reachable
from the PR head that are not reachable from that merge base. Inspect real PR
commits and their coauthors, including genuine merge commits. In CI, use the
event's actual base and head SHAs; do not count GitHub's generated test-merge
commit as a contributor. Validate mappings and import records from the proposed
PR tree so a new mapping in that same PR satisfies the check.

The check must fail for new unresolved human authors or coauthors, malformed
mappings, contradictory assignments, and filename collisions. It should print
the observed email, affected commit, and exact repair command. Noreply identities
handled consistently by the resolver and documented bot identities pass without
requiring email files.

A genuine contributor without a GitHub account needs name-based credit. If that
case occurs, document a maintainer-reviewed exception with its evidence and
validate it through the same check; do not create a fake login or broadly exempt
unresolved contributors. Unresolved historical cases remain in the backfill
report and must not repeatedly fail unrelated future PRs.

Run attribution CI on every PR, including documentation and frontend changes.
Fetch the history needed to compute the range. Use the ordinary `pull_request`
event and a read-only token; the check reports problems rather than pushing
fixes or posting messages. Fork PRs do not receive normal repository secrets,
and their token is read-only by default, as described in
[GitHub's workflow-event reference](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request).

Wire the check into the project's existing CI gate. Requiring the resulting
status through branch protection or a ruleset is what prevents merges with
missing attribution. If changing hosting settings is outside the task's
authorization, identify the exact status that maintainers must require; do not
claim enforcement is active just because a workflow file exists.

The automation detects missing credit and explains how to repair it. Creating
mappings automatically is appropriate only when supported by the evidence rules.

## Text to install in the target repository

Install these snippets after the commands exist and have been validated. Adjust
them to the target's runtime and existing documentation structure.

### Contributor guide and `contributors/README.md`

> Before requesting review, make sure every human author and coauthor in your
> PR can be credited. For an unmapped commit email, run:
>
> `python3 scripts/add_contributor.py 'YOUR_COMMIT_EMAIL' 'YOUR_GITHUB_USERNAME'`
>
> Use the exact email recorded in your commits or coauthor trailers. The command
> creates `contributors/emails/YOUR_COMMIT_EMAIL` containing your existing
> GitHub username. Include that file in the same PR. If a new email belongs to
> someone else, use their confirmed username. A supported GitHub noreply address
> or an existing mapping needs no duplicate file; a new email alias needs its
> own mapping. You can use GitHub's provided noreply address for future commits.
>
> Run `python3 scripts/check_contributors.py --base BASE_SHA --head HEAD_SHA`
> using your PR's base and head revisions before requesting review. When bringing
> in work from another repository, preserve its authors and coauthors and add
> a source record under `contributors/imports/`. Include the original commit or
> PR, affected code, and adaptation credit. Report uncertain identities rather
> than guessing them. Contact maintainers if a contributor has no GitHub account.

### Repository agent instructions (`AGENTS.md` or equivalent)

> Before completing a contribution, run the repository's contributor check for
> the intended PR range. Every new human author and coauthor must resolve through
> an existing mapping, a supported noreply address, or a documented exception.
> Add confirmed mappings in `contributors/emails/`; never guess usernames or
> replace another contributor's identity. When adapting external work, preserve
> original credit and add its source commit or PR under `contributors/imports/`.
> Keep unresolved cases visible. Consult `contributors/README.md` for commands
> and policy. Do not alter application behavior or rewrite history to update
> attribution.

### PR template

```markdown
### Attribution

- [ ] Every human author and coauthor is covered by the contributor resolver.
- [ ] I included mappings for newly used emails, or existing/noreply mappings apply.
- [ ] I ran the contributor check for this PR's base and head revisions.
- [ ] Imported or adapted work retains original credit and a source record, or N/A.

Source repository, original PR/commit, and original contributors, if applicable:
```

When making future commits with multiple human authors, use proper
`Co-authored-by: Name <email>` trailers and each person's chosen account-linked
email or noreply address. Preserve these trailers in the final squash message
if the project uses squash merges. See
[GitHub's coauthor instructions](https://docs.github.com/en/pull-requests/how-tos/commit-changes/creating-a-commit-with-multiple-authors).

## Validation and completion

Exercise the real CLI commands against temporary Git repositories using the
target project's approved test runner. Two focused integration scenarios can
cover the important relationships:

1. **Historical recovery:** create a merged branch with an original author, a
   different integrator, a human coauthor, and two confirmed emails for one
   person. Include a squash/import with recorded upstream evidence and an
   unresolved identity. Assert correct roles and mappings, retained source
   credit, explicit uncertainty, and an unchanged second application. Supply
   recorded API fixtures at the network boundary while using real Git history
   and real CLI entry points.
2. **PR maintenance:** use a base branch whose name is not `main`. Assert that
   an unmapped human coauthor fails, a valid mapping added in the PR makes it
   pass, a supported noreply identity passes, and a conflicting or malformed
   mapping fails. Include a merge-message coauthor and a PR changing only
   documentation so neither is silently skipped.

Verify that every inventoried commit was visited, and every collected identity
has a recorded disposition: mapped, noreply-resolved, explicitly excluded,
conflicted, unresolved, or credited without an account. Do not freeze the
contributor count in a test; assert coverage and attribution relationships.

At handoff, provide the audited revision, coverage totals, created file locations,
tests run and results, unresolved identities or imports, and CI enforcement
status. Report usernames deduplicated across email aliases. Confirm that the
contributor guide, agent instructions, and PR template explain the same rules.

Finish with working attribution tooling and documentation, confirmed historical
credits, and an honest account of missing evidence. A directory populated with
guessed accounts is not a completed backfill.
