claude-seo is mirrored across two GitHub remotes. This document is the canonical reference for how work flows between them.
LOCAL CHECKOUT
(single source of truth)
│
┌──────────────┼──────────────┐
│ │
▼ ▼
origin (public) aimh (private)
AgriciDaniel/claude-seo AI-Marketing-Hub/claude-seo
- Release destination - Daily development
- main = released history - main = synced with public
- Tags = release history - v2 = active development
- Users discover here - Dependabot + CI run here
Both remotes share git history because they were initialized from the same local repository. Neither is a GitHub fork of the other.
# Work on the active development branch
git checkout v2
# ...make changes, run tests...
git add <files>
git commit -m "feat: ..."
# Push to the PRIVATE remote (default for in-progress work)
git push aimh v2
The private repo runs Dependabot, GitHub Actions CI, and any pre-release test gates. No need to touch the public remote for routine work.
When v2 (or whatever branch holds the next release) is ready to go public:
Locally: merge into main and tag.
bash
git checkout main
git merge --ff-only v2 # fast-forward only — no merge commits
git tag -a v2.0.1 -m "release: v2.0.1"
Push to private first (private should never lag public).
bash
git push aimh main
git push aimh v2.0.1
Push to public in tag-before-merge order (avoids the curl|bash
outage window where users pull a tag that doesn't yet point at code
on main).
bash
git push origin v2.0.1 # tag first
git push origin main # then branch
Create the GitHub release on the public repo only.
bash
gh release create v2.0.1 \
--repo AgriciDaniel/claude-seo \
--notes-from-tag \
--verify-tag
Publish the release blog post.
/release-blog
# Confirm both remotes are wired up
git remote -v
# Confirm both remotes' main heads.
# NOTE: origin/main is aimh/main PLUS one public-branding commit (see below),
# so the SHAs intentionally differ by exactly that commit. Do NOT force-sync them.
git ls-remote --heads aimh main
git ls-remote --heads origin main
# List tags on each (private will lead during pre-release work)
git ls-remote --tags aimh | grep -v '\^{}' | awk '{print $2}'
git ls-remote --tags origin | grep -v '\^{}' | awk '{print $2}'
# Confirm private has a v2 branch ahead of release
git fetch aimh
git log --oneline aimh/main..aimh/v2
Since v2.0.0 the two repos are not byte-identical. They differ by exactly one file on one commit:
| File | aimh (private) |
origin (public) |
|---|---|---|
.claude-plugin/marketplace.json name |
ai-marketing-hub-claude-seo |
agricidaniel-claude-seo |
.claude-plugin/marketplace.json owner.name |
AI Marketing Hub |
AgriciDaniel |
Everything else (including README.md) is shared and public-first: the
install block defaults to AgriciDaniel/claude-seo, with a Pro swap-note that
names the private slug. This keeps the public repo free of ai-marketing-hub
slugs while keeping the README a single shared file.
How the divergence is maintained. The public-only branding lives as the
tip commit of origin/main, carried on a local public-main branch:
# public-main = main + one commit that rebrands marketplace.json
git checkout main && git merge --ff-only v2 # shared canonical
git checkout public-main && git rebase main # replay branding onto new base
git push origin public-main:main # public gets canonical + branding
git push aimh main # private gets canonical only
So origin/main = aimh/main + 1 commit, by design. The release tag points at
each repo's own HEAD (origin tag includes branding; aimh tag does not).
Public is for users. Private is for the work that becomes users' next upgrade.
| Pitfall | Avoid by |
|---|---|
Pushing a tag to origin before its commit reaches origin/main |
Always tag-before-merge: push tag first, then branch |
git push --tags without specifying remote |
Be explicit: git push aimh --tags or git push origin v2.0.1 |
| Force-pushing to either remote | Don't, except with explicit per-operation authorization |
Letting aimh/main lag behind origin/main |
Always push to aimh first, then origin on release |
Confusing aimh/v2 with origin/v2 |
origin should never have an unreleased v2 branch |
v2.2.4.aimh/main = shared canonical (public-first README, private marketplace name).origin/main = aimh/main + the public-branding commit (public marketplace name).v2.2.4 tag points at the reviewed maintenance release on origin/main.aimh/v2 tracks the shared canonical for ongoing v2.x work.Two very old tags (v1.2.0, v1.4.0) could not be pushed to the
private repo because the underlying commits use a private email address
that GitHub now blocks. These tags remain available on origin only.
Not a regression — those releases shipped on public and are reachable
there.