This repository separates upstream history, private changes, temporary review branches, and the runnable build:
pingdotgg/t3code:main
└── fork/tim selected Tim Smart PRs
└── fork/candidates selected open upstream PRs
└── fork/changes our private changes
├── ordinary feature PRs
├── registered draft overlays
└── fork/integration changes + overlays, tested/deployed
main mirrors pingdotgg/t3code:main. fork/tim is a linear provenance layer with one commit per
selected Tim Smart PR and a permanently open PR against main. fork/candidates is a temporary
upstream-provenance layer with one commit per selected open upstream PR and a permanently open PR
against fork/tim. fork/changes is the GitHub default branch and canonical private layer, with a
permanently open PR against fork/candidates.
fork/integration is generated from the reviewed layers plus registered integration overlays and
is used by running instances.
An upstreamable feature may remain as an open PR instead of being merged into fork/changes.
Register it under integrationOverlays in .github/pr-stack.json. Every overlay remains a
parallel draft PR based on fork/changes; overlays are never based on each other. The stack
workflow rebases overlays when fork/changes moves and composes their commits, in manifest order,
only in fork/integration.
Draft state is the merge lock. Normal Fork CI continues to run and can remain green, so health and merge permission remain separate signals. A trusted workflow automatically returns managed PRs (#1, #27, #2, and registered overlays) to draft if they are accidentally marked ready.
pnpm fork:stack overlay-add 10
pnpm fork:stack overlay-start 10 feature/deep-link-follow-up
pnpm fork:stack overlay-promote 10 upstream/desktop-deep-linksTo change an overlay, commit directly to its branch or create a child PR with the overlay branch as
its base and merge the child into the overlay PR. Do not put the same change into fork/changes.
Landing an overlay is deliberate: remove its manifest entry in the same reviewed change that lands
the implementation in fork/changes, then verify that the resulting fork/integration tree is
unchanged.
Do not use GitHub's Sync fork button, create a PR into this repository's main, or push main
manually. A GitHub PR merge would rewrite upstream commits, while an ordinary push is correctly
blocked by the Protect upstream main ruleset.
The Rebase fork PR stack workflow is the sole synchronization path. It runs every six hours and
can also be started with:
gh workflow run rebase-pr-stack.yml --repo patroza/t3code --ref fork/changesThe workflow fetches pingdotgg/t3code:main, verifies that the existing mirror has not diverged,
and atomically updates main, fork/tim, fork/candidates, fork/changes, and
fork/integration with
force-with-lease. A repository-scoped write deploy key stored as FORK_STACK_DEPLOY_KEY is the only
automation actor allowed to bypass main's PR and status-check requirements. It cannot access other
repositories. Never expose or reuse it.
Upstream's .github/workflows/ci.yml and .github/workflows/deploy-relay.yml remain present on the
exact main mirror but are disabled in this repository. Fork PR and integration checks use
.github/workflows/fork-ci.yml; the stack workflow dispatches that workflow for the exact generated
integration SHA. This avoids redundant CI and prevents an upstream-mirror update from being treated
as a fork product or relay deployment.
The helper starts an independent branch from fork/changes:
pnpm fork:stack start feature/my-changeCommit and push normally, then open the PR against fork/changes (never against main). Updating
that branch updates the same PR and reruns PR CI. Ordinary feature and import PRs are deliberately
not registered in the stack manifest, so multiple independent PRs may be open concurrently without
editing central metadata.
Feature branches drift when fork/changes moves (upstream mirror sync or merged siblings). Agents
must leave PRs mergeable at handoff:
# Current branch + its open PR
pnpm fork:stack update --push
# Explicit PR (checks out the head branch, updates, pushes)
pnpm fork:stack update --push 48
# Plan only (no push)
pnpm fork:stack updateupdate will:
- fetch latest
origin/fork/changesand the durable base-history ref (refs/t3/stack/base-history/fork-changes); - rebase when the branch already descends from the new tip but is behind;
- when history diverged (normal after a stack rewrite): recover the old base tip this PR was
built on — the newest recorded historical
fork/changestip that is still an ancestor of HEAD — thengit rebase --onto newBase oldBase. Feature commits are exactlyoldBase..HEAD(the commits that were on top of the old base), not a file-count guess and not the full GitHub PR commit list; - retarget the PR base to
fork/changesif it still points atmainor another wrong branch; - force-with-lease push when
--pushis set; - print
gh pr viewmergeability JSON.
The stack cascade records each fork/changes tip into that base-history ref before rebasing open
feature PRs the same way (rebase --onto from the recovered old base).
Do not use GitHub “Update branch” merge commits for these feature PRs; prefer this rebase/replay path so history stays linear and reviewable.
When the stack workflow rewrites fork/changes, it also force-with-lease rebases every open feature
PR that targets fork/changes (conflicts are reported in the job summary and skipped). After that
remote rewrite, update your local checkouts with:
# On the feature branch (or fork/changes / any tracking branch)
pnpm fork:stack pullpull fetches the remote tip and uses git cherry patch-ids:
- if every local commit is patch-equivalent to something already on the remote → hard reset to remote (safe when the only difference is a rewritten history you already pushed);
- if you have unique unpushed patches → rebase those onto the remote tip.
Require a clean working tree. This is the low-pain path after automation rebases open PRs.
After review, merge the PR into fork/changes. That push automatically runs the stack synchronizer:
feature PR merged into fork/changes
→ rebase-pr-stack workflow
→ fork/integration updated atomically
→ CI dispatched for the exact integration SHA
→ successful CI classifies the tree diff
→ runtime-affecting changes trigger fleet deployment
→ test, documentation, and automation-only changes stop after CIDeployment classification compares complete tested integration trees rather than only the latest commit. Unknown paths are runtime-affecting by default. This preserves safe deployment when a PR contains mixed changes or a new source directory appears, while avoiding fleet rebuilds and mobile OTA updates for tests, snapshots, documentation, agent instructions, and GitHub-only metadata.
Runtime-affecting integrations also publish both mobile release tracks from the exact tested SHA.
Both tracks use Expo Fingerprint: they publish an OTA update when a compatible build already
exists, and start a new build when native runtime inputs changed. A new production build is
submitted to TestFlight automatically, so an installed tester build stays current without a manual
dispatch. Manual runs of Mobile EAS Production can still force build or update; manual runs of
Mobile EAS Development may target iOS, Android, or both. Automatic integration publishing targets
iOS, because Android has no signing keystore configured.
The manifest contains the permanent fork/tim, fork/candidates, and fork/changes PRs. The
synchronizer rebases that provenance chain onto the latest upstream main and rebuilds
fork/integration. Other open repository PRs are ignored. Temporary state is retained after a
conflict and can be resumed with the command printed in the error.
register is used during the one-time cutover and only when intentionally building an advanced,
dependent integration chain:
pnpm fork:stack register 201The permanent fork/tim, fork/candidates, and fork/changes PRs are never merged while this
model is active.
Independent changes use parallel branches and PRs, all based on fork/changes. They can be reviewed
and merged in any order; run pnpm fork:stack update --push on a remaining branch if an earlier
merge overlaps it or the PR becomes CONFLICTING.
Related changes may use one cohesive PR. If separate review is valuable, chain only those PRs by
basing the dependent PR on the preceding feature branch. Merge the chain from bottom to top into
fork/changes. Do not place unrelated features in one dependency chain.
Use the PR title, branch name, affected-area field in the PR template, and GitHub's open/merged PR
history to find prior work. Agents must check gh pr status and verify a PR's state before deciding
whether to update its branch or create a new PR.
Search by feature words instead of remembering PR numbers:
pnpm fork:stack find "board pagination"
pnpm fork:stack find-upstream "worktree cleanup"External forks are source remotes, not branches to merge wholesale. For Tim Smart, start an import
branch from fork/tim, port only the wanted source PR, and open it against fork/tim:
git fetch tim
git switch -c import/tim-pr-17 origin/fork/tim
git cherry-pick <unchanged-commit>
git cherry-pick --no-commit <commit-to-adapt>
# keep Tim's imported behavior in one commit; test and open against fork/timDo not merge an external branch wholesale. For every import PR, document:
- imported unchanged;
- adapted to local behavior;
- intentionally excluded;
- provenance using fully qualified links such as
tim-smart/t3code#17.
Merge the import with squash so fork/tim gains exactly one provenance commit. Adjustments for our
environment use a separate normal PR against fork/changes; never hide private policy inside the
Tim layer. A later Tim update is compared against both the prior provenance commit and our
adjustment, and automation never overwrites local decisions.
An upstream PR may be production-worthy before pingdotgg/t3code accepts it. Import it from
fork/candidates, never from main, fork/tim, or fork/changes:
git fetch origin fork/candidates
git fetch upstream refs/pull/<number>/head:refs/remotes/upstream/pr/<number>
git switch -c import/upstream-pr-<number> origin/fork/candidates
git cherry-pick --no-commit upstream/pr/<number>
# retain only the reviewed source PR behavior, update .github/upstream-candidates.json,
# test, commit once, push, and open against fork/candidatesEach candidate PR must become exactly one provenance commit and document the upstream PR URL,
source SHA, imported behavior, local adaptations, and exclusions. The registry
.github/upstream-candidates.json records the same source SHA and lifecycle state. Product-specific
follow-ups belong in fork/changes, not in the candidate commit.
Before updating the upstream mirror, inspect every active candidate:
- unchanged and open: retain it;
- updated upstream: review and replace its provenance commit through a new candidate PR;
- merged with equivalent behavior: remove the candidate commit while rebasing the layer;
- merged differently or closed: stop automatic synchronization and reconcile deliberately.
After reconciliation, compare the old and rebuilt fork/integration trees. Removing an accepted
candidate must not remove adaptations that belong to fork/changes.
Every feature lands in fork/changes; upstreamability is a clean projection, not an alternative
home. Closing or rejecting an upstream PR therefore never removes the private implementation.
After the private PR merges, promote it onto real upstream history:
pnpm fork:stack promote <private-pr-number> upstream/portable-feature
# remove private assumptions from the staged extraction, test, and commitThe command creates a branch from upstream main and stages the private PR's commits without
committing, allowing the projection to be simplified before opening it to pingdotgg/t3code:main:
gh pr create \
--repo pingdotgg/t3code \
--base main \
--head patroza:upstream/portable-featureFor work that began upstream-first, adopt its clean branch into the private fork:
pnpm fork:stack adopt upstream/portable-feature adopt/portable-feature
# push and open adopt/portable-feature against fork/changesIf the upstream proposal is withdrawn, demotion closes only the projection and cross-links the private source:
pnpm fork:stack demote <upstream-pr-number> <private-pr-number>Never rebase the private branch onto main. Promotion creates an independently reviewable upstream
implementation while fork/changes remains canonical. Select main in T3, or use
start-upstream, only for deliberately upstream-first work.
The registered chain is ordered from upstream toward deployment. Its final PR must always use
fork/changes; earlier permanent layers describe provenance such as fork/tim and
fork/candidates. Add another layer only when it has durable ownership and update the manifest, PR
bases, and documentation together.
The pre-provenance woven graph is preserved locally and remotely at:
archive/fork-changes-woven-2026-07-24archive/fork-integration-woven-2026-07-24- matching annotated tags prefixed with
archive-
The clean rebuild preserves the exact archived fork/changes tree while replacing its ancestry
with main → fork/tim → fork/candidates → fork/changes. Never delete or force-update the archive
refs.