# Naming

> **Conformance:** L1 (every Magogi-foundation project)
> **Audience:** Maintainers picking a name for a new sibling, repo,
> or local dev directory; reviewers checking that a new project
> followed the convention; AI agents scaffolding new federation
> packages.
> **Propagation (DOC-18):** changing a rule here ripples — walk DOCUMENTATION.md §10.4 and update its dependents in the same commit (README *Index of MUSTs*, the `mgf-fed standards` / `agents` CLI, and any parallel copies / pins the matrix names).

> **Scope.** The federation-wide identity convention — what a
> Magogi-foundation project is called across the four domains
> where the name appears (PyPI distribution, Python import,
> Codeberg/git slug, local dev directory). The rules here are
> deliberately small but **L1-binding** because identity drift is
> a long-tail tax: once a sibling ships under one form and a
> consumer pins to another, the inconsistency outlives the people
> who introduced it.

---

## Rules box (read this first)

| ID | Rule |
|---|---|
| <a id="NM-01"></a>**NM-01** | **Distribution names MUST use hyphens, never underscores.** Canonical form: `mgf-common`, `mgf-fastapi`, `mgf-http`. PEP 503 normalises `_` and `-` to be equivalent at the index, but the community-canonical form is hyphens (compare `scikit-learn`, `python-dateutil`, `google-cloud-storage`); publishing under `mgf_common` would still normalise to `mgf-common` at the index and only create lookup confusion for consumers reading dependency lists. This holds registry-agnostically: `mgf-*` packages publish to and resolve from the self-hosted devpi index (the canonical channel — see [PACKAGE_DISTRIBUTION.md](PACKAGE_DISTRIBUTION.md) PKG-01/PKG-10), and devpi normalises names per PEP 503 exactly as `pypi.org` does, so the hyphen rule is unchanged by the cutover. |
| <a id="NM-02"></a>**NM-02** | **Python imports MUST use the dotted namespace `mgf.<sibling>`.** Canonical form: `import mgf.common`, `from mgf.fastapi import …`. Every federation sibling is an [implicit namespace package](https://peps.python.org/pep-0420/) under the shared `mgf` namespace — no top-level `__init__.py` at `src/mgf/`. Flat names (`import mgf_common`, `import mgf_fastapi`) are forbidden because they prevent the federation from sharing a single namespace and break the discoverability contract that lets a consumer reason about "what `mgf.*` things are wired." |
| <a id="NM-03"></a>**NM-03** | **Codeberg repo slugs MUST use hyphens** (matching NM-01). Canonical form: `mgf-common`, `mgf-fastapi`. The federation completed a one-time cutover on 2026-05-24 that renamed the two legacy underscore slugs (`mgf_common`, `mgf_apiprobe`) to their hyphen form — no grandfathered slugs remain (see §4 for the cutover history and the Forgejo-non-redirect lesson). Any future rename MUST land as a coordinated federation-wide push, not a standalone PR, because Forgejo does NOT auto-redirect renamed slugs. |
| <a id="NM-04"></a>**NM-04** | **Local dev directories SHOULD match the Codeberg slug.** Canonical form: clone `mgf-common` into `~/PycharmProjects/mgf-common` (hyphen). This is a SHOULD, not a MUST, because local layout is operator-chosen and never appears in any artifact a consumer reads. The recommendation exists so that scripts that walk sibling repos by name (federation-sync, cross-repo greps, doctor-style audits) see one convention. A legacy `mgf_common` clone is fine; new clones SHOULD follow NM-03's slug. |
| <a id="NM-05"></a>**NM-05** | **Shared/utility modules SHOULD be named for a domain, never as a junk drawer.** Canonical form: `encoding.py`, `hashing.py`, `time.py`, `ids.py`. The names `utils`, `misc`, `helpers`, `helper`, `common`, `util` (and `core` used as a grab-bag) are banned — a junk drawer is how a shared surface dies: nothing has a clear home, unrelated behavior accretes, and "one behavior, one home" silently fails. A SHOULD (judgment over bureaucracy; a one-off private `_helpers` inside a single module's file is fine), enforced by review + the shared-code discipline. Full discipline: [`docs/recipes/shared-code.md`](../recipes/shared-code.md) (PAPER-107). |

---

## 1. The four domains where the name appears

A Magogi-foundation project's name shows up in four distinct
domains, each with its own technical constraints. The table is
the canonical mapping — refer to it whenever you're naming a new
sibling.

| Domain | Convention | Example | Why this form |
|---|---|---|---|
| **Distribution name** | hyphens | `mgf-common` | PEP 503 + Python-community canonical form; renders identically with `_` but `-` is what every modern package uses. Goes in `pyproject.toml::[project] name`, in every consumer's `dependencies`, and on the devpi package page at `https://195.15.203.250.sslip.io/magogi/prod/<name>` — the canonical registry is the self-hosted devpi index, not `pypi.org` (see [PACKAGE_DISTRIBUTION.md](PACKAGE_DISTRIBUTION.md) PKG-10). |
| **Python import path** | dotted namespace | `mgf.common` | PEP 420 implicit namespace packages; `-` is illegal in Python identifiers; the dot enables sharing the `mgf` namespace across every sibling. Goes in `import mgf.common`, `from mgf.fastapi import …`, and in every consumer's source. |
| **Codeberg / git repo slug** | hyphens | `mgf-common` | URL-friendly + matches NM-01 + lowercase. Goes in `https://codeberg.org/magogi-admin/<slug>` and every `[project.urls]` entry. |
| **Local dev directory** | hyphens (recommended) | `~/PycharmProjects/mgf-common` | Matches the repo slug so federation-walking scripts (cross-repo greps, sibling-pin audits, `mgf-fed federation-sync`) see one form. |

The four-line mnemonic — **"hyphen-everywhere-except-the-import,
which is a dotted-namespace"** — is the whole standard.

---

## 2. Why hyphens for distribution names (NM-01)

PEP 503 §"Normalized Names" says:

> For comparison purposes, a project's name MUST be normalized
> by replacing runs of the characters `-`, `_`, and `.` with a
> single `-`, and then lowercasing.

So `mgf_common` and `mgf-common` resolve to the same project at
the index. This is registry-agnostic: `mgf-*` packages publish
to and resolve from the **self-hosted devpi index** (the canonical
channel — see [PACKAGE_DISTRIBUTION.md](PACKAGE_DISTRIBUTION.md)
PKG-01/PKG-10), and devpi applies the same PEP 503 normalisation
as `pypi.org`. The distinction matters because:

1. **Display drift.** The registry displays whatever name the
   upload used. Once you publish `mgf-common`, every subsequent
   release MUST also use that form (mixing `mgf_common` releases
   under the same project produces visibly inconsistent release
   names in the package's release history on the devpi index).
2. **Consumer dependency lists.** A consumer writing
   `mgf_common>=0.39,<0.40` works, but every audit, every
   `pip-compile` lockfile re-render, every `uv sync` produces
   a re-normalised hyphen form. Pre-normalised entries don't
   churn; underscore-form entries churn forever.
3. **Community convention.** Compare `scikit-learn`,
   `python-dateutil`, `google-cloud-storage`,
   `azure-storage-blob`, `pytest-asyncio` — every modern
   multi-word package uses hyphens. Underscore-form names
   (`google_cloud`, `azure_storage`) read as "this project
   doesn't follow the Python community packaging convention,"
   which costs trust.

---

## 3. Why dots for imports (NM-02)

Python identifiers cannot contain hyphens (`-` is the subtraction
operator). So the import has to be either:

- **Flat:** `import mgf_common` — underscore-joined.
- **Namespaced:** `import mgf.common` — dot-separated.

The federation uses the namespaced form because every sibling
ships under the shared `mgf` namespace via [PEP 420 implicit
namespace packages](https://peps.python.org/pep-0420/) — there is
NO `src/mgf/__init__.py`. The directory layout is:

```
mgf-common/
  src/
    mgf/                ← no __init__.py here (PEP 420)
      common/
        __init__.py    ← this sibling's package root
        ...
mgf-fastapi/
  src/
    mgf/                ← no __init__.py here (PEP 420)
      fastapi/
        __init__.py
        ...
```

When a consumer installs `mgf-common` + `mgf-fastapi`, the two
packages cooperatively populate the `mgf` namespace, and
`import mgf.common`, `import mgf.fastapi` both resolve. Flat
imports (`mgf_common`, `mgf_fastapi`) would force a new top-level
package per sibling and break the federation's discoverability
contract — `dir(mgf)` is the canonical "what's wired?" probe.

---

## 4. Slug cutover history (2026-05-24)

Two Codeberg repos predated this standard with underscore slugs
(`mgf_common`, `mgf_apiprobe`). Both were renamed to the
canonical hyphen form on 2026-05-24 in a federation-wide
cutover:

- `magogi-admin/mgf_common` → `magogi-admin/mgf-common`
- `magogi-admin/mgf_apiprobe` → `magogi-admin/mgf-apiprobe`

There are now **no grandfathered slugs in the federation** —
NM-03 applies uniformly.

### Critical federation knowledge — Forgejo does NOT redirect

Empirically verified during the 2026-05-24 cutover: **Forgejo /
Codeberg does NOT auto-redirect renamed slugs.** The old URL
returns `Forgejo: Cannot find repository` over git+ssh and HTTP
404 over web traffic. The federation initially planned this
cutover assuming redirects would carry old URLs gracefully;
that assumption was wrong.

The practical consequence for any future rename:

1. **Every reference to the old slug breaks the instant the
   rename completes.** There is no grace period.
2. **Plan all reference updates as part of the rename event**,
   not as a follow-up cleanup. Federation-wide grep + sed +
   per-repo commit + push, in one coordinated push.
3. **Update git remotes immediately in every consumer that
   clones the affected repo** — `git remote set-url origin
   <new-url>`. Stale remotes silently fail their next push.
4. **The CI / Woodpecker pipelines that clone via the old URL
   stop working immediately.** Update them in the same window.

The 2026-05-24 cutover touched several hundred individual URL
references across the federation — every sibling's `[project.urls]`,
every consumer git remote, every CHANGELOG cross-link, every CI
clone URL — all swept in one coordinated push. The durable lesson,
not the frozen count, is what matters: budget a future rename for a
federation-wide mechanical sweep, not a follow-up cleanup.

### Asymmetric cost — why `mgf_apiprobe` rehearsed first

`mgf_common` is referenced by every sibling's `[project.urls]`,
every consumer's git remote, every CHANGELOG cross-link, and
every standards doc that cites source by URL — a federation-
wide event. `mgf_apiprobe` is referenced narrowly (no sibling
reverse-dependency). Renaming `mgf_apiprobe` first was the
low-risk rehearsal that confirmed the Forgejo-non-redirect
behaviour above before the cornerstone slug-rename was attempted.

**Forward-looking rule:** for any future rename, run a small
test rename first (a deliberately-low-impact repo or a fresh
disposable repo) to re-verify the redirect behaviour. Forgejo
versions evolve; what's true today may not be true at the next
cutover.

---

## 5. Worked example: scaffolding a new sibling

When `mgf-fed init-project` (or the operator) creates a new
federation sibling — say, an Anthropic-Claude SDK adapter — the
four-domain mapping is:

| Domain | Value |
|---|---|
| PyPI distribution name | `mgf-claude` |
| Python import path | `mgf.claude` |
| Codeberg slug | `mgf-claude` |
| Local dev directory | `~/PycharmProjects/mgf-claude` |
| `pyproject.toml::[project] name` | `"mgf-claude"` |
| `src/` layout | `src/mgf/claude/__init__.py` (no `src/mgf/__init__.py`) |
| Import in consumer code | `from mgf.claude import Client` |

All four artifacts read consistently. There is one form to
remember per project — the hyphenated one — and the import path
is mechanically derivable from it.

---

## 6. Self-review checklist

Before merging the first commit of a new sibling, confirm:

- [ ] PyPI name in `pyproject.toml::[project] name` uses hyphens (NM-01).
- [ ] No `src/mgf/__init__.py` exists — only `src/mgf/<sibling>/__init__.py` (NM-02).
- [ ] Consumer-facing docs (README, STARTHERE) use `from mgf.<sibling> import …` consistently (NM-02).
- [ ] Codeberg slug uses hyphens (NM-03).
- [ ] `[project.urls]` entries cite the hyphen-form slug (NM-03).
- [ ] (SHOULD) Local clone directory matches the slug (NM-04).

For an existing project (post-2026-05-24 cutover, all federation
slugs conform to NM-03):

- [ ] PyPI distribution name uses hyphens (NM-01).
- [ ] Python import shape is `mgf.<sibling>` namespace, no top-level
      `src/mgf/__init__.py` (NM-02).
- [ ] Codeberg slug uses hyphens (NM-03) — verify against
      `git remote get-url origin`.
- [ ] (SHOULD) Local clone directory matches the slug (NM-04).

If a future rename is needed (sibling renamed, sibling-domain
restructure), see §4 — the Forgejo-non-redirect behaviour
demands all reference updates land in one coordinated push, not
a follow-up cleanup.

---

## 7. Mechanical enforcement

| Rule | Enforcement |
|---|---|
| NM-01 | `mgf-fed app-doctor` rejects a `pyproject.toml::[project] name` containing `_` (lint-style). Manual review confirms on first publish. |
| NM-02 | `mgf-fed catch-up` audit step checks that the package layout under `src/` matches the `mgf.<sibling>` shape (no top-level `__init__.py` at `src/mgf/`). |
| NM-03 | New-repo creation is a manual federation-admin step; the operator reviews the slug at creation time. No mechanical enforcement (Codeberg API is not in the audit loop). |
| NM-04 | Not enforced. Local layout is operator-chosen. |

The audits land in the doctor / catch-up bundles as they are
implemented; the rules themselves are L1-binding regardless of
the mechanical-enforcement status.
