21STARK
All posts
12 min read views

I archived 12 of my 45 repos in 21 days. On purpose.

A quarter of a five-month-old fleet, archived inside a 21-day window. Why AI-assisted velocity makes repo-killing routine, and the checklist (successor first, verified bundles, a Kill Ledger) that makes it safe.

Twelve of the 45 repos on my personal GitHub org are archived. I verified the count this morning: gh repo list --limit 200, 2026-07-18. The limit flag matters. More on that later.

All 12 archive events landed inside a 21-day window: 2026-06-20 to 2026-07-11.

Here is the part that reframes it. The whole fleet is about five months old. The oldest repo was created 2026-02-20. This was not a legacy cleanup. There is no legacy. Nothing in that org existed in January.

A quarter of a five-month-old fleet, killed in three weeks. On purpose, with a checklist, behind gates that actually fired. This post is the why, and the exact mechanism.

Dead repos are wrong answers

AI-assisted pace breaks the repo lifecycle you knew. Numbers from my own morgue:

  • stark-night-watch: 0 to 3,123 commits, 57 branches, 274 release tags, in 86 days.
  • stark-team: 2,823 commits, 422 tags, March 26 to July 3.
  • stark-2nd-brain-hub: 2 days as a standalone repo. 65 commits in those 2 days. Then the monorepo won.
  • stark-mcp: a full MCP server, born and superseded in a 19-day dev life.

At this pace a repo is born, peaks, and gets superseded inside a quarter. Sometimes inside a week. The old instinct says keep everything around, just in case. Storage is free, right?

Wrong framing. A dead repo is not neutral. It is a wrong answer waiting to be picked. My fleet map, REPOS.md, answers one question: which repo do I reach for. Agents read it more than I do. An agent has no tribal memory. Nothing whispers "that one's dead" when it lists the org and finds two automation platforms, one of them a corpse. Every stale repo degrades every routing decision made after it.

So the real choice is not keep versus delete. It is curate versus lie.

A small delivery robot pauses on a sunny street between two identical storefronts, one open and warmly lit, the other boarded up and covered in cobwebs.

The kill checklist

The kills accumulated over three weeks; the deletion run that closed them out ran off a 346-line scripted plan: journaled, restartable, gated. The checklist behind all of it has five steps. Every step leaves an artifact.

1. Successor first, archive second

Never archive on a promise to rebuild. The successor exists, runs, and is synced before the predecessor dies.

The biggest kill: stark-night-watch and stark-team, together 5,946 commits, replaced by meridian. The order of events is the whole lesson. meridian was created 2026-06-29. The predecessors were archived 2026-07-03. In the 4-day overlap, a final upstream sync ran: meridian PR #83 ported night-watch #1474, the only functional commit that had landed after the earlier sync passes. Nothing fell through the gap. Only then did the archive flag flip.

Honesty clause: the named-successor path covers 6 of the 12. meridian took two. The 2nd-brain monorepo absorbed two more, full history preserved. The MCP server was reborn inside stark-visual as an in-repo binary, a new visual-only set of 14 tools; the image work carried over, but the old server's 37 tools, catalog and data included, died with the repo. stark-slack-indexer continues the Slack work in Go. The other 6 died heirless: a decommissioned insights service, a Terraform sandbox, a monitor for a subnet that no longer exists, a Python data layer, an empty smoke-test repo, and a dormant ClickUp client (its job resurfaced inside another tool's clickup/ package, but no doc names it heir). Not every kill has an heir. Some things just end. The checklist is the same either way.

One more pattern hides in the morgue: language policy. The org mix is Go 15, TypeScript 10, Python 7. The archived split: 5 Go, 2 TS, and 3 of the 7 Python repos. Only one of those was my "no new Python" policy executing directly: the Slack work, rebuilt in Go. The other two died for their own reasons and happened to die Python. Still, the Python-heavy morgue is where the policy shows up as a number instead of a slogan.

2. Bundle, verify, hash. Outside the tree

Nothing gets deleted from disk without a full-history bundle. Not a promise of one. A verified one. From ~/Code/docs/plans/code-reorg-2026-07-11.md, phase 0.6:

bundle_repo() {  # $1 = repo path
  local name; name="$(basename "$1")"
  local out="$RUN_DIR/bundles/$name.bundle"
  mkdir -p "$RUN_DIR/bundles"
  git -C "$1" bundle create "$out" --all
  git -C "$1" bundle verify "$out"
  case "$out" in "$CODE_ROOT"/*) echo "FATAL: bundle landed inside CODE_ROOT"; exit 1;; esac
  shasum -a256 "$out" >> "$RUN_DIR/bundles/SHA256SUMS"
}

Four deliberate moves in eight lines:

  • --all: every branch, every tag. Not just HEAD.
  • bundle verify: an unverified backup is a hope, not a backup.
  • The case guard: a bundle inside the tree you are about to delete is not a backup. Fail fatal.
  • The hash ledger: every bundle gets a SHA256 line, appended to one file.

Watercolor of hands placing a wax-sealed parcel with a blank tag on a sunlit shelf while a tree is felled in a distant garden outside the window.

The result: 9 bundles, 57 MB, 6,614 commits preserved. From bundles/SHA256SUMS, real hashes on my disk:

e6af4a42320d60d8991fdbd141b630f5002f8e04427d082eb36b825c79e4b32a  stark-night-watch.bundle
5269b0e6f814987f58836814e571fdeb1f5f7ad172d7141b1ac88650762e5d69  stark-team.bundle
5c9cb969d502af0e8f523dbdb82764883d77e62c6474444f5de27e751364b6ec  vault-keeper.bundle

That third line is the interesting one. vault-keeper never had a remote. Four hours before bundling, the 2nd-brain monorepo imported all 15 of its commits, rewritten SHAs, so the work itself lives on GitHub. But the 111 KB bundle is the only copy of the original repo: original SHAs, original branch layout. The plan flagged it in writing: no remote, treat the bundle as the only copy. That sentence in a plan is worth more than a terabyte of unlabeled backups.

Second honesty clause: 8 of the 12 archived repos have bundles, not 12. The other 4 had no local clone at reorg time. For them, GitHub's archive state is the preservation. The invariant is exact and narrow: everything deleted from disk has a verified, hashed bundle. Nothing was deleted on trust. (The ninth bundle is vault-keeper, whose history moved into the monorepo; the bundle keeps the original.)

3. WIP is content, not filenames

git bundle --all has a hole. It captures refs, not your working tree. Uncommitted changes and detached worktree HEADs vanish.

So every dirty worktree got a capture_worktree pass before bundling: a tracked.patch for modified files, an untracked.tgz for everything else, and a durable reorg-wip/* tag created first, so --all sweeps the state in as a real ref. 15 WIP captures exist in the archive, 6 of them from stark-team worktrees alone.

The principle: preserve the work, not the directory layout. A patch, a tarball, and a tag beat an old-stuff-2/ folder every single time.

4. Freeze, archive, write the row

GitHub archive gives you two things: read-only, and Actions frozen. Both are load-bearing. The Actions half is not paranoia; wait for the war stories.

Then the tombstone goes into the fleet map, in the same change. One row per corpse. From ~/Code/REPOS.md, the archived table:

| **stark-night-watch** | Go automation platform (webhooks + cron jobs). | Archived 2026-07-03; clone deleted, bundle kept. Live continuation: **meridian**. |

Date. Disposition. Successor. One line answers every future "wait, where did night-watch go", from me or from an agent, forever.

5. Only then, delete

The deletion run itself: 2026-07-11, 11:45 to 12:16. 31 minutes. 68 of 68 manifest checks green. 11 deletion targets removed. 4.7 GB in the archive. As a side quest, 259 Claude project directories renamed and 366 config references rewritten by a fixture-tested, longest-prefix, collision-aborting mapping function. Boring by design.

Except for seventeen seconds. From run-log.md:

2026-07-11T11:50:02 PHASE 0 EXIT GATE: FAILED
2026-07-11T11:50:19 PHASE 0 EXIT GATE: ALL GREEN

The gate blocked all deletions because its own harness had a bug: it ran a bare git bundle verify outside any repo. The artifacts were always good. The gate blocked deletions on its own bug, and that is exactly right. A fail-closed gate that has never fired red is a gate you cannot trust. This one fired, got fixed, went green, and only then were deletions unblocked.

A Lego minifigure inspects the hinge of a lowered red and white barrier gate with a magnifying glass while a Lego crane with a wrecking ball waits behind it.

What fought back

Five things I did not expect. Each one is now a rule.

Archiving is not append-only. infra-pulse was already archived when the org-wide authorship rewrite needed to force-push it. The sequence: unarchive, rewrite, transfer, re-archive on the new org. Plan for resurrection as a step, not an exception.

Frozen Actions prevented a mass deploy. The authorship rewrite updated 274 and 414 tags on the two big repos. With Actions live, every tag update fires release.yml, deploy.yml, cd.yml. Hundreds of releases and deploys, triggered by a history rewrite. Actions were disabled first, deliberately. Check what your tags trigger before you touch history.

The self-referential CI deadlock. stark-team's CI pool was hard-gated on attribute.repository == "GetEvinced/stark-team". The terraform apply that could widen that gate authenticates through that very pool. Transfer the repo first and the credential that fixes the lock dies with the old name. The escape order: widen first (PR #1159, applied via tag v0.35.29, because apply was tag-gated, not merge-gated), then transfer, then re-tighten (PR #1160, v0.35.30, the first release cut on the new org). Map your identity dependencies before you rename anything.

"Docs-only" wasn't. A spec-only directory, no code, no remote, turned out to contain .gcloud credential caches. It got tarred at mode 0600 into the run dir and flagged in the completion note. Never trust a directory's self-description. Scan before you archive, and definitely before you share.

The near-miss that set the paranoia level. Weeks earlier, before deleting a different repo, the completeness check ran gh issue list and saw 30 open issues. The default page size is 30. There were 47. All 47 got recreated on the successor with provenance footers, but only because I counted twice. The rule since: pass --limit 200 before trusting any completeness check. A deletion gate is only as good as its pagination.

And one piece of rot the archive simply absorbs: archived stark-mcp pins a dependency by commit pseudo-version, and the authorship rewrite changed that SHA. The pin no longer resolves. Nobody cares. It is archived. Dead consumers are allowed to stay broken. That is half the point of a clean kill: the blast radius of every future change shrinks to the living.

The Kill Ledger

The pattern deserves a name, so you can steal it: the Kill Ledger.

A Kill Ledger is the archived-repos table in your fleet map. One row per dead repo: what it was, when it died, where the bundle lives, who continues the work (or "nothing, decommissioned"). Mine states the invariant right in the section header: no local clones remain; full-history bundles live under ~/Archive/code-reorg-2026-07-11/run-*/bundles/.

The ledger is what turns deletion into a decision. Without a row, an archived repo is a mystery and a deleted one is a rumor. With a row, every future reader gets the routing answer in one line: reach for meridian, night-watch is archived.

Three properties make a ledger real:

  1. It lives in the docs, not in your head. Same repo as the fleet map. Versioned, reviewed, greppable.
  2. Every row has artifacts. A bundle path, a hash, a successor PR. Claims you can check, not vibes.
  3. It is written at kill time, in the same change. Backfill it a month later and you are doing archaeology, guessing at successors from commit dates.

The 2026-specific reason this matters: your docs are your agents' memory. I run Claude against this workspace every day. When the docs carry explicit tombstones, agents stop resurrecting corpses. They stop proposing PRs against archived repos, stop importing dead packages, stop citing retired APIs. The Kill Ledger is context engineering applied to endings, and endings are the context nobody writes down.

What to take

The checklist, compressed. Six steps, each with an artifact:

  1. Successor synced first. Overlap the lifetimes. Run a final sync. Port the stragglers.
  2. Bundle, verify, hash, outside the tree. git bundle create --all, then bundle verify, then SHA256, with a fatal guard if the bundle lands inside what you are deleting.
  3. Capture WIP as content. Patch plus tarball plus durable tag, before bundling. --all misses uncommitted work unless you tag it in.
  4. Archive read-only with Actions frozen. Know what your tags trigger first.
  5. Write the Kill Ledger row. Date, disposition, successor. Same commit as the kill.
  6. Then delete. Behind a fail-closed gate that has proven it can fire.

And three rules I would tattoo on the process:

  • A backup inside the tree you are deleting is not a backup.
  • A completeness check with default pagination is not a check.
  • A repo without a tombstone is a wrong answer waiting to be picked.

The empty repo got a tombstone too

My favorite row in the ledger belongs to the dumbest repo in the fleet. snyk-wiring-slice3-smoke: size 0. Empty. Created 06:46 UTC on 2026-06-26, archived 06:49. The repo lived under three minutes, a smoke test for a CI wiring slice.

It was archived, not deleted. Because the org holds a stricter invariant than tidiness: zero repos deleted without trace. The single true deletion in the fleet's history happened only after tree-hash-verified replication and full issue recreation on the successor. All 47 issues, provenance footers included.

Repos used to feel precious because they took years to fill. That era is over. night-watch put 3,123 commits and 274 releases into 86 days. At that pace repos are cheap and getting cheaper. What stays expensive is trust: knowing that what is live is real, what is dead is marked, and what is deleted is recoverable.

So read your own archive count as a velocity gauge. Twelve dead repos in five months does not mean I failed twelve times. It means twelve times something better won, and the loser got a verified bundle, a hash, and a one-line tombstone instead of a haunting.

Kill fast. Bury well. Write the row.

Get in touch

I write about AI-first engineering on LinkedIn. Specs in, production out, nobody types code. Follow along there, or send a note.

hi@21stark.com · LinkedIn opens my profile, message me from there


Or send it from here