# Rename Cost-Out Spike — repo + PyPI + npm (#177)

**Status:** SPIKE / plan only. **No rename is executed under this issue.** Name shortlist is
explicitly out of scope (owner picks separately) — this is the migration plan, irreversibility
analysis, and effort estimate the owner asked for before committing to a new name.
**Author:** Nia Rossi (Tech Lead). **Wave:** Phase 6 Wave 3, S7.

Current identity across the three surfaces, all currently sharing one string:

| Surface | Current value | File |
|---|---|---|
| GitHub repo | `parametrization/2real-team-framework` | (remote) |
| PyPI dist name | `2real-team-framework` | `python/pyproject.toml:2` |
| PyPI console script | `2real-team` → `real_team.cli:app` | `python/pyproject.toml:42` |
| Python **import** module | `real_team` | `python/src/real_team/` |
| npm package name | `2real-team-framework` | `node/package.json:2` |
| npm `bin` | `2real-team` → `./dist/index.js` | `node/package.json:33` |

**Key decoupling fact (per the issue's own note):** the PyPI/npm **distribution name**
(`2real-team-framework`) and the Python **import name** (`real_team`) are already independent
strings today. A rename can move the dist/repo name only and leave `import real_team` untouched,
or move both — these are separately-scoped decisions (see § Version-continuity & scope decision).

---

## 1. Surface inventory (every load-bearing occurrence)

### 1a. GitHub repo / remote
- Remote: `parametrization/2real-team-framework` (org `parametrization`, repo
  `2real-team-framework`). GitHub Actions badge URLs, clone instructions, and every
  `git remote` on every contributor's existing clone reference this pair.

### 1b. PyPI (`python/pyproject.toml`)
- `[project] name = "2real-team-framework"` (line 2), `version = "0.7.0"` (line 3)
- Author email `2real-team@users.noreply.github.com` (line 9, cosmetic)
- `[project.scripts] "2real-team" = "real_team.cli:app"` (line 42) — the CLI entry point name
- `[project.urls]` Homepage/Repository/Bug Tracker/Documentation (lines 45-48) — all
  `https://github.com/parametrization/2real-team-framework[...]`
- `[tool.hatch.build.targets.wheel] packages = ["src/real_team"]` (line 58) and
  `[tool.coverage.run] source = ["real_team"]` — bind the **import name**, not the dist name

### 1c. npm (`node/package.json` + lockfile)
- `"name": "2real-team-framework"` (line 2), `"version": "0.7.0"` (line 3)
- `"bin": { "2real-team": "./dist/index.js" }` (line 33)
- `repository.url`, `bugs.url`, `homepage`, `author.url` (lines 10, 14, 17, 19) — all point at
  the GitHub repo URL
- `node/package-lock.json` — top-level `name` (line 2) and root `packages[""].name` (line 8);
  2 occurrences, regenerated by `npm install` after the `package.json` name changes

### 1d. Python import module `real_team`
- Package directory `python/src/real_team/` (`__init__.py`, `bootstrap.py`, `cli.py`,
  `framework_install.py`, `models.py`, `personas.py`, `presets.py`, `templates.py`, `py.typed`)
- Bundling target: `python/hatch_build.py`'s `BundleSharedDataHook` writes
  `real_team/_bundled/framework` — the mechanism `init --with-hooks` subprocesses (`--no-team`)
  to run the bundled bootstrap (see project memory `reference_cli_framework_bundling.md`). Any
  import-name rename must keep this build hook's target path in lockstep with the package dir.

### 1e. CLI entry point
- Console-script name `2real-team` (PyPI) / `bin` key `2real-team` (npm) — the command every
  user types. Distinct from both the dist name and the import name; can in principle move
  independently, but user-facing docs (`README.md`, `node/README.md`, `CONTRIBUTING.md`) all
  hardcode `2real-team ...` invocation examples that must update in lockstep with it.

### 1f. Config `project.name`
- `framework/config/framework.config.schema.json` already carries a **display-name** field
  (`identity.name`, "Human-facing identity of the project this config governs") that drives
  `{{project_name}}` substitution into installed charter templates (e.g.
  `framework/assets/team/charter/charter.md:1` → `# Team Charter — {{project_name}}`). This is
  **already config-driven and per-repo** — it is NOT the framework's own package/repo name and
  needs no migration; it is the mechanism other repos already use to avoid hardcoding a name.

### 1g. Roster / charter / docs
- `README.md` (title, 3 badges, 5 install/run command lines, 1 cross-reference to an unrelated
  case-study repo `parametrization/isnad-graph` at line 438 — **not** part of this rename)
- `node/README.md` (title + 4 install/run command lines)
- `CONTRIBUTING.md` (title + directory-tree example + bridge-command mention, 4 lines)
- `framework/config/framework.config.schema.json:3` — `$id` self-reference:
  `https://github.com/parametrization/2real-team-framework/framework/config/...schema.json`

### 1h. `framework/assets/**` canonical strings
- Scanned for literal `2real-team-framework`, `real_team`, `real-team` (case-insensitive):
  **zero** hits for the first two; **one** hit for `real-team` — the worked CLI example in
  `framework/assets/team/charter/pull-requests.md` (`real-team init --with-hooks --force`).
  Everything else that would otherwise hardcode the project's own name already runs through
  `{{project_name}}` / `{{default_branch}}` / config substitution (per the dual-deploy design —
  see `reference_config_driven_architecture.md`). **This is the good news of the whole spike:**
  the canonical asset tree was already built to be name-agnostic for *consumer* repos: the one
  literal `real-team` reference is this framework describing itself, and is the only canonical
  string this rename touches.

### 1i. Install manifest
- `framework/install/golden-manifest.json` (checked via `manifest.py --check`): **zero**
  occurrences of any name variant — the manifest enumerates installed *paths*, not the package
  name, so a rename does not touch it.

### 1j. OIDC trusted-publisher configs (both registries — see § 3, highest risk)
- `.github/workflows/publish-pypi.yml`: `environment: pypi`, `id-token: write`, publishes via
  `pypa/gh-action-pypi-publish@release/v1` to a PyPI **Trusted Publisher** entry keyed on
  `(PyPI project name, GitHub owner, GitHub repo, workflow filename, environment)`.
- `.github/workflows/publish-npm.yml`: `id-token: write`, `npm publish --provenance`, keyed on
  npm's **Trusted Publisher** entry — `(npm package name, GitHub owner, GitHub repo, workflow
  filename)` (no environment gate configured on the npm side today, unlike PyPI).

---

## 2. Irreversibility analysis per surface

| Surface | Is it a true "rename"? | What actually happens |
|---|---|---|
| **PyPI** | **No.** PyPI has no project rename. | Publish a **brand-new project** under the new name; the old project is never reclaimable by this org even after deprecation — yank is not deletion, and the old name stays squatted by history. Old project should be marked `Development Status :: 7 - Inactive` and its README/description replaced with a pointer to the new name. Existing pinned installs (`pip install 2real-team-framework==0.7.0`) keep working forever; they just stop receiving updates. |
| **npm** | **No**, same shape as PyPI. | `npm deprecate 2real-team-framework "renamed to <new>"` on the old package (this *is* reversible — deprecation messages can be cleared), then publish the new package fresh. Unscoped → unscoped is the simplest case; moving to a scoped name (`@org/name`) also changes the `bin`/install invocation shape and is a bigger user-facing change than the bare rename. |
| **GitHub repo** | **Partially** — GitHub auto-installs a redirect from the old `owner/repo` to the new one for HTTP(S) clone/push and web links. | The redirect is not permanent-proof: (1) a **second repo later created at the old name** breaks the redirect immediately and silently; (2) the redirect does not cover the **OIDC `sub` claim** used by trusted-publisher configs (§ 3) — those are keyed on the literal repo string at config time on PyPI/npm's side, external to GitHub, and do not follow the redirect; (3) any **hardcoded URL** in docs, badges, or third-party links that isn't itself using the redirect-following host still resolves today but is a latent break if the redirect is ever removed; (4) existing local clones keep working via the redirect but `git remote -v` still shows the old URL until a contributor manually updates it — a hygiene item, not a hard break. |

**Net:** GitHub is the "softest" rename of the three (auto-redirect, in-place), PyPI and npm are
both "publish new + deprecate old" — never reclaim, never true renames.

---

## 3. OIDC continuity runbook (highest-risk step)

Both registries' trusted-publisher configs are **external** to this repo — configured on
pypi.org / npmjs.com's own UI, keyed off the (owner, repo, workflow filename, [environment])
tuple. A repo rename changes that tuple; a **misconfigured or stale entry means the very next
release CI run fails to publish keyless**, with no local way to catch it before the release.

Ordered runbook (do this **before** the first post-rename release, not after):

1. **GitHub repo rename first.** Rename via GitHub settings (org `parametrization`, repo
   `2real-team-framework` → new name). GitHub's redirect now covers clone/push/web traffic.
2. **New PyPI project + Trusted Publisher entry.** Create the new PyPI project (first release
   must be `twine`/manual-token-authenticated once, since Trusted Publisher entries can only be
   attached to a project that already exists — a well-known PyPI chicken-and-egg). Immediately
   after that bootstrap publish, configure the new project's Trusted Publisher: owner
   `parametrization`, **new** repo name, workflow filename `publish-pypi.yml` (keep the filename
   stable across the rename to minimize the number of moving parts), environment `pypi`.
3. **New npm Trusted Publisher entry.** Same bootstrap problem: npm also requires the package to
   exist before a Trusted Publisher entry can be attached, so the first publish under the new name
   is either a manual `npm publish` (classic token) or the maintainer temporarily re-adds
   `NODE_AUTH_TOKEN` for exactly one release. Then configure npm's OIDC trusted publisher for the
   new package name against the renamed repo + `publish-npm.yml`.
4. **Update both workflow files' comments/environment gates** if the environment name or any
   repo-relative reference changed (neither workflow hardcodes the repo string today — they rely
   on `actions/checkout@v4`'s implicit `${{ github.repository }}` context — so no workflow YAML
   edit is mechanically required beyond the trusted-publisher UI config; the risk is entirely in
   the **external** registry-side config, not the workflow file).
5. **Deprecate old projects last**, only after the first new-name release has published
   successfully via OIDC on both registries. Do not deprecate old projects preemptively — a
   failed first OIDC publish under the new name with the old project already deprecated leaves
   users with no working install path during the gap.
6. **Smoke-test before trusting it:** the first post-rename release should be a low-stakes patch
   (e.g. a version bump only) specifically to prove the OIDC path end-to-end before shipping real
   changes under the new identity.

**This step is the spike's single highest-risk item** — everything else in this plan is textual
substitution; this is the one step where a misconfiguration produces a silent failure discovered
only when a release CI run doesn't publish.

---

## 4. Version-continuity recommendation

**Recommendation: continue the current `0.x` series under the new name (do not reset to `0.1.0`
or `1.0.0` as part of the rename itself).** Rationale:
- The rename is an identity change, not a functionality reset — resetting the version alongside
  it conflates two unrelated signals and makes changelogs/release notes harder to cross-reference
  against the pre-rename history.
- A version reset on a **brand-new PyPI/npm project** (§ 2) has no continuity requirement to
  satisfy anyway — the new project starts however it starts; there is no "downgrade" risk since
  it's a distinct project ID with its own version history from zero installs.
- Existing installed users on the **old** name are unaffected either way — they keep whatever
  version they have pinned; the old project simply stops receiving new releases post-deprecation.
- If/when a `1.0.0` milestone is warranted on its own merits (API stability commitment), do it as
  a separate, deliberate decision — not bundled into the rename so the two signals don't get
  conflated in the release history.

---

## 5. Effort estimate + ordered runbook

**Effort estimate: M (medium)** — no new logic, but touches three external, irreversible
registry states (PyPI, npm, OIDC) that cannot be dry-run locally, plus doc/badge sweep. Not S
because of the OIDC bootstrap chicken-and-egg (step 2-3 above) and the need for careful sequencing
to avoid a broken interim state; not L because the actual text-substitution surface is small
(single-digit file count outside docs) and `framework/assets/**` needs almost no change (§ 1h).

Ordered runbook (avoids a broken interim state — each step is independently revertable up to the
point marked irreversible):

1. **Decide scope**: dist/repo name only, or also the Python import name (`real_team`) and CLI
   command (`2real-team`)? (Name-shortlist and this scope decision are explicitly out of scope for
   this spike — flagging as the first decision gate before execution starts.)
2. Update `python/pyproject.toml` (`name`, `[project.scripts]`, `[project.urls]`) and
   `node/package.json` (`name`, `bin`, `repository`/`bugs`/`homepage`/`author.url`) on a branch —
   **no publish yet**. If import-name is in scope: `git mv python/src/real_team python/src/<new>`
   + update `[tool.hatch.build.targets.wheel] packages` + `[tool.coverage.run] source` +
   `hatch_build.py`'s bundle target path + every `from real_team...` / `import real_team` call
   site (a mechanical rename, but touches the CLI's own source tree, not just config).
3. Sweep docs/badges: `README.md`, `node/README.md`, `CONTRIBUTING.md`,
   `framework/config/framework.config.schema.json`'s `$id`. Update the one canonical
   `framework/assets/team/charter/pull-requests.md` `real-team init` example (§ 1h).
4. **Dual-deploy (#116):** since the one canonical string touched (§ 1h) lives under
   `framework/assets/**`, edit it there first, then run
   `python3 framework/install/reinstall.py` and confirm `--check` exits 0 so this repo's own
   `.claude/team/charter/pull-requests.md` doesn't drift from the canonical copy. Also re-run
   `python3 framework/install/manifest.py --check` (name is not manifest-tracked per § 1i, so this
   is a no-drift confirmation, not an expected change).
5. Run `ENVIRONMENT=test python -m pytest framework/tests/` and `ruff check framework/` — the
   `test_charter_install.py::test_generalized_no_org_specific_content` test already asserts the
   **installed template** carries no `2real-team-framework` token (§ 6c), so a canonical-asset
   rename step is partially self-checking.
6. **GitHub repo rename** (§ 3 step 1) — first genuinely external, redirect-covered step.
7. **PyPI**: create new project, bootstrap-publish once (manual auth), then configure Trusted
   Publisher (§ 3 step 2). **Irreversible from here** — old PyPI name can never be reclaimed.
8. **npm**: create new package, bootstrap-publish once, then configure Trusted Publisher (§ 3
   step 3). Irreversible in the same sense.
9. Smoke-test release (§ 3 step 6) to prove OIDC end-to-end under the new name.
10. Deprecate old PyPI + npm projects (§ 3 step 5), pointing their README/description at the new
    name.
11. Update this repo's own local git remote (`git remote set-url origin <new-url>`) and notify
    any other active worktrees/agents to do the same — the redirect makes this a hygiene step, not
    a hard requirement, but stale remotes are a recurring source of "why didn't my push show up"
    confusion.

---

## 6. Dual-deploy (#116) implications

- **Canonical assets are already name-agnostic** (§ 1h) — the rename's dual-deploy surface is the
  smallest part of this spike: one literal string in one canonical charter file.
- **Config-driven identity already exists** (§ 1f, `identity.name` / `{{project_name}}`) and is
  unaffected — that mechanism is *for consumer repos naming themselves*, orthogonal to this
  framework's own package identity. No schema change is implied by this rename.
- **Reinstall/manifest checks are cheap regression coverage for the rename**, not extra work the
  rename creates: `reinstall.py --check` and `manifest.py --check` (run today, § 5 step 4) already
  exist as the standing gates that would catch canonical/live drift if the one touched string were
  edited in only one place.
- No new `_MANAGED_TREES` entries or schema fields are required by this rename.

---

## 7. Naming constraints discovered (name-shortlist itself is OUT OF SCOPE)

While inventorying surfaces, these **constraints** on any future candidate name surfaced (not
candidates themselves):

- **PyPI + npm availability must be checked independently** — they are separate namespaces; a
  name free on one registry says nothing about the other. Both must be confirmed available (or a
  scoped npm name `@org/name` used as a fallback) before the OIDC bootstrap runbook (§ 3) starts,
  since that runbook assumes the target name is actually publishable.
- **CLI command collision**: the console-script/`bin` name (currently `2real-team`) should be
  checked against common `$PATH` collisions (existing well-known CLIs) independent of the package
  name — the two do not have to match, but user docs currently assume they do (§ 1e), so keeping
  them matched is the lower-friction choice.
- **Import-name legality (PEP 8 / Python identifier rules)**: `real_team` → any new import name
  must be a valid Python identifier (`^[A-Za-z_][A-Za-z0-9_]*$`, no hyphens) — this is *already*
  why the import name (`real_team`) diverges from the hyphenated dist name
  (`2real-team-framework`) today; any new import name has the same constraint regardless of what
  the dist/repo name becomes.
- **GitHub repo name character set**: alphanumeric + hyphens only, no leading digit issues to
  worry about specifically, but organization-level uniqueness (`parametrization/<name>`) must be
  checked; a repo rename to a name that collides with an existing repo in the org fails outright.
- **Trademark**: not evaluated in this spike (no search performed) — flagged as a gap the owner
  should close (a basic USPTO/common-law search) before committing to a shortlist, since this
  spike found no existing signal either way.

---

## Summary for the owner

- Renaming is **feasible** and the framework's own canonical-asset design (§ 1h) means the
  dual-deploy blast radius is tiny — the real cost is entirely in the **three external,
  irreversible registry operations** (§ 2, § 3) and the discipline of sequencing them so there is
  never a broken interim state.
- **Highest risk, and the one to rehearse first:** OIDC trusted-publisher reconfiguration on both
  registries (§ 3) — a misconfigured entry means the next release silently fails to publish.
- **Recommend:** keep the `0.x` version series continuous through the rename (§ 4); decide
  up front whether the Python import name (`real_team`) and CLI command (`2real-team`) move too,
  since that changes the effort from "config + doc sweep" to "config + doc sweep + source-tree
  rename" (§ 5 step 1-2).
- **Effort: M.** Ordered runbook in § 5; do not deprecate old registry projects until the new
  name's first OIDC release is proven (§ 3 step 5-6).
