# de-Claude the repo + git history (beta-prep #2) — Design / Runbook

**Date:** 2026-06-15 · **Branch:** `feat/v1-foundation` · **Status:** approved, execution DEFERRED to
beta/staging cut · Beta-prep #2 (the final, irreversible roadmap item)

Strip every trace of AI-assisted development from the repository **and from its entire git history**,
so the codebase that ships to beta/staging looks like an ordinary, human-authored product repo. This
is a **history rewrite + force-push** — irreversible, and it rewrites the shared branch. It runs
**once, last**, after all content is complete and tagged, and **only on the operator's explicit go**
for the force-push.

> **Timing:** the operator scoped this to beta/staging time ("in der dev Variante ist das nicht so
> schlimm … später wenn es in beta oder staging muss das weg sein"). During dev the AI traces stay.
> This document is the prepared runbook; it is **not** executed now.

## Operator-approved decisions
- **File scope — purge ALL internal process docs from every commit:** `CLAUDE.md`, `rules.md`,
  `handoff.md`, `docs/session-handoff.md`, and the whole `docs/superpowers/` tree. Product code,
  `README.md`, `CHANGELOG.md`, `install.sh`, `docker/`, etc. stay.
- **Keep on disk, gitignore:** after purging from git, the files remain on disk (dev keeps using
  `rules.md`/`CLAUDE.md` as references); they are added to `.gitignore` so they never re-enter git.
- **Message scrub — trailers only:** strip the 167 `Co-Authored-By: Claude …` trailer lines. Keep all
  descriptive subjects/bodies (they describe real changes). The 23 "Codex" / 19 "spec" message
  mentions are **left as-is** (operator's call). Commits that only added a now-purged doc become empty
  and are pruned automatically.

## Ground truth (scanned 2026-06-15)
- **170 commits, single author** `boban <boban@git.bave.dev>` — no "Claude" author/committer identity
  to fix.
- **167/170** commits carry a `Co-Authored-By: Claude …` trailer; **0** "Generated with Claude Code"
  lines.
- Tracked artifacts to purge: `CLAUDE.md`, `rules.md`, `handoff.md`, `docs/session-handoff.md`,
  `docs/superpowers/**` (16 spec/plan files).
- **No** `.claude/`, `.cursor/`, `AGENTS.md`, `.mcp.json` anywhere in history.
- **One branch** (`feat/v1-foundation`) + tags `v0.1.0 … v0.9.2`. No `main`.
- `git-filter-repo` is **not** installed; no `pip3`; `python3` 3.13 is present → use the single-file
  script via `python3` (no sudo needed).

## Tool: git-filter-repo (single-file, no install)
`git filter-repo` is the modern, git-recommended history-rewrite tool (correct tag rewriting, empty-
commit pruning, ref-safe — unlike the deprecated `filter-branch`). It is one self-contained Python
script. Fetch it once into the repo-adjacent tooling dir and run via `python3` — no `pip`, no `sudo`:

```bash
# from a machine with internet; the script is pure Python, ~250 KB, MIT-licensed
curl -fsSL https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo \
  -o /tmp/git-filter-repo && chmod +x /tmp/git-filter-repo
python3 /tmp/git-filter-repo --version    # sanity
```
(Alternative if preferred and sudo is available interactively: `sudo apt-get install git-filter-repo`.)

## Runbook (execute top-to-bottom AT BETA TIME)

### Phase 0 — preconditions (abort if any fails)
- This is the **last** operation. All feature work merged, all releases tagged, `CHANGELOG` current.
- **Working tree clean** — `git status --short` empty. (In particular the parallel timing-fix
  refinement to `app/Livewire/Auth/ForgotPassword.php` must already be committed.)
- Full suite green; app boots (R12).
- No open Gitea PRs against the branch (a force-push invalidates them). Solo repo → expected none.

### Phase 1 — backup (the safety net for an irreversible op)
```bash
TS=$(date +%Y%m%d-%H%M%S)
# 1a. complete mirror of all refs (commits, branches, tags) — the real recovery artifact
git bundle create "$HOME/clusev-pre-declaude-$TS.bundle" --all
# 1b. on-disk copies of the to-be-kept files (filter-repo will delete them from the work tree)
mkdir -p "$HOME/clusev-keep-$TS"
cp -a CLAUDE.md rules.md handoff.md docs/session-handoff.md "$HOME/clusev-keep-$TS/"
mkdir -p "$HOME/clusev-keep-$TS/superpowers" && cp -a docs/superpowers/. "$HOME/clusev-keep-$TS/superpowers/"
# verify the bundle is restorable BEFORE proceeding
git bundle verify "$HOME/clusev-pre-declaude-$TS.bundle"
```
Do **not** delete these backups until Phase 6 confirms a fresh clone of the rewritten remote builds.

### Phase 2 — rewrite history (in-place, --force; backups exist)
```bash
python3 /tmp/git-filter-repo \
  --path CLAUDE.md \
  --path rules.md \
  --path handoff.md \
  --path docs/session-handoff.md \
  --path docs/superpowers \
  --invert-paths \
  --message-callback '
    import re
    return re.sub(rb"(?im)^\s*Co-authored-by:\s*Claude.*\n?", b"", message).rstrip() + b"\n"
  ' \
  --force
```
- `--invert-paths` deletes the listed paths from **every** commit; commits that become empty are
  pruned. Tags pointing at rewritten commits are rewritten automatically.
- The `--message-callback` strips any `Co-authored-by: Claude …` line (case-insensitive) and tidies the
  trailing blank line. Bodies/subjects are otherwise untouched.
- filter-repo **removes the `origin` remote** afterward (a deliberate guard) and `reset --hard`s the
  work tree to the rewritten HEAD — so `CLAUDE.md` etc. now vanish from disk too (restored in Phase 4).

### Phase 3 — verify the rewrite
```bash
git log --all -i --grep='co-authored-by: claude' --oneline | wc -l      # expect 0
for f in CLAUDE.md rules.md handoff.md docs/session-handoff.md; do \
  echo -n "$f in history: "; git log --all --oneline -- "$f" | wc -l; done   # expect 0 each
git log --all --oneline -- docs/superpowers | wc -l                      # expect 0
git rev-list --count HEAD        # < 170 (empty doc-only commits pruned)
git log -1 --format='%an <%ae>'  # still boban — author identity unchanged
```

### Phase 4 — restore kept files on disk + gitignore them
```bash
# Phase 1 copied these in by basename, so $KEEP holds: CLAUDE.md, rules.md, handoff.md,
# session-handoff.md, and superpowers/. Restore to their repo paths:
KEEP="$HOME/clusev-keep-$TS"
cp -a "$KEEP/CLAUDE.md" "$KEEP/rules.md" "$KEEP/handoff.md" .
mkdir -p docs && cp -a "$KEEP/session-handoff.md" docs/session-handoff.md
mkdir -p docs/superpowers && cp -a "$KEEP/superpowers/." docs/superpowers/
```
Append to `.gitignore` (idempotently):
```
# Internal AI-assisted-dev docs — kept locally, never committed (de-Claude'd for beta)
/CLAUDE.md
/rules.md
/handoff.md
/docs/session-handoff.md
/docs/superpowers/
```
Commit just the ignore change (and any tidy), **without** a Co-Authored-By trailer:
```bash
git add .gitignore && git commit -m "chore: ignore internal development docs"
git status --short    # clean — the kept files are now invisible to git
```

### Phase 5 — re-add origin, rebuild, GATED force-push
```bash
git remote add origin https://git.bave.dev/boban/clusev.git   # filter-repo dropped it
```
Rebuild + run the suite on the rewritten tree (R12 — app boots, tests green) before pushing.

**>>> EXPLICIT-CONFIRMATION GATE <<<** — the force-push is irreversible and rewrites the shared
branch/tags. Do not run it without the operator's clear, in-the-moment "yes, force-push". Token is
read inline from `~/.env.gitea` and **all** output is sanitized (never echo the token):
```bash
TOKEN=$(grep -E '^GIT_ACCESS_TOKEN=' "$HOME/.env.gitea" | head -1 | cut -d= -f2- | tr -d '\r\n')
URL="https://boban:${TOKEN}@git.bave.dev/boban/clusev.git"
# branch — plain --force to the explicit URL (filter-repo dropped origin and the local
# origin/* tracking ref is stale from earlier explicit-URL pushes, so --force-with-lease
# has no reliable lease to check here; the Phase-1 bundle is the safety net instead)
git push "$URL" --force feat/v1-foundation 2>&1 | sed -E 's#https://[^@]*@#https://<redacted>@#g; s/[0-9a-f]{40,}/<redacted>/g'
# tags moved to new commits — force-update them too
git push "$URL" --force --tags         2>&1 | sed -E 's#https://[^@]*@#https://<redacted>@#g; s/[0-9a-f]{40,}/<redacted>/g'
unset TOKEN URL
```
(Enumerate any additional remote branches first with `git ls-remote --heads "$URL"` — currently only
`feat/v1-foundation` exists. Do **not** use `--mirror`: it would prune refs unexpectedly.)

### Phase 6 — post-checks, then clean up
- Fresh-clone check: clone the remote into a temp dir, confirm it builds and contains **no**
  `Co-Authored-By: Claude`, no `CLAUDE.md`/`rules.md`/`handoff.md`/`docs/superpowers`.
- Gitea: releases follow the force-updated tags; old commit SHAs in any links/PRs break (expected,
  solo repo).
- Only **after** the fresh clone verifies: the Phase-1 backups (`bundle` + keep dir) may be archived
  or removed.

## Going forward (post-execution rule)
Once de-Claude has run, **stop appending the `Co-Authored-By: Claude` trailer** to new commits, and do
not re-add the gitignored docs to git — otherwise the traces re-enter the history this rewrite cleaned.
(This overrides the default commit-footer convention, per the operator's beta requirement.)

## Recovery (if something goes wrong)
The remote is force-pushed, but the Phase-1 `--all` bundle holds the complete pre-rewrite state:
```bash
git clone "$HOME/clusev-pre-declaude-<TS>.bundle" clusev-restored   # full history back
# or, in-place: git fetch "$HOME/…bundle" '+refs/*:refs/*' then reset/force-push the old refs
```

## Security
- Token read only at push time from `~/.env.gitea`; never echoed/committed; push output sanitized.
- The rewrite never touches `.env*` (gitignored, not in history) or any secret.
- filter-repo's `origin`-drop + `--force` requirement are guards; the mandatory pre-backup is the
  recovery path for the one irreversible step (the force-push).

## Notes / self-reference
- This spec lives under `docs/superpowers/` — the rewrite purges it from git but Phase 4 restores it on
  disk (gitignored), so it survives as the executed-runbook reference.
- 170 commits is small; filter-repo completes in seconds. `filter-branch` is a zero-dependency
  fallback but slower and deprecated — prefer filter-repo.

## Out of scope
- Re-writing commit-message bodies beyond the Co-Authored-By trailer (operator chose trailers-only).
- Squashing/reordering history (we preserve the real commit-by-commit story, minus traces).
- Anything in beta-prep #3 (README, done), #4 (installer, done), #5 (MOTD, done).
