Propagate template main into forked store repos
You may be running autonomously across many repos. Do not ask questions — decide, act, and
report. If one store fails, record it and move on to the next.
Architecture facts (do not rediscover these)
- Store repos are
yns-store-<subdomain> in the GitHub org yns-cx-tenants (staging /
yns.cx) or yns-store-tenants (production / yns.store). Access via gh.
- Store repos share no git history with this template. Provisioning copies template files
into a fresh repo without upstream parents (see the platform's
src/lib/sandbox/fork-builder.ts). Never attempt git rebase/git merge against the
template — this skill reconciles content, file by file.
- Stores may have been seeded from a
theme-* branch, and Elliot edits files freely
afterwards. The only files Elliot cannot touch are the managed paths
(proxy.ts, lib/track.tsx, instrumentation-client.ts — the platform's
MANAGED_STOREFRONT_PATHS): the platform's commit script restores those from the store
repo's own origin/main on every save, so landing the template's version on the store's
main IS the propagation mechanism for them.
- Pushing to a store repo's
main triggers a production redeploy of that tenant storefront,
and it is safe with respect to live builder sandboxes: their save script fetches and
rebases onto FETCH_HEAD, aborting cleanly on conflict.
.env* files are per-store secrets. Never read, modify, or commit them.
Inputs
- Optional: a list of store subdomains (e.g.
base celeste) and/or the org to target.
Default: staging org, all builder stores.
- Enumerate builder stores from the platform DB when available (source of truth):
SELECT subdomain FROM stores WHERE builder_config IS NOT NULL; — otherwise
gh repo list <org> --limit 200 --json name and strip the yns-store- prefix.
Per-store algorithm
Work from a checkout of this template repo with full history and all branches fetched
(git fetch origin "+refs/heads/*:refs/remotes/origin/*"). Clone each store repo into a
temp dir (gh repo clone <org>/yns-store-<subdomain> <tmp> -- --depth=1).
The decision rule for every path is "has the store ever customized this file?", answered
by blob-hash comparison against the template origin/main history:
# all historical blob hashes of a path on template main (cache per path):
git -C <template> log origin/main --format=%H -- <path> \
| while read sha; do git -C <template> rev-parse -q --verify "$sha:<path>"; done | sort -u
# the store's current blob:
git -C <store> hash-object <path>
Matching only a theme-* blob (not any main-history blob) counts as customized — theme
identity must never be flattened onto main's look.
Then, per path:
- Managed paths (
proxy.ts, lib/track.tsx, instrumentation-client.ts): overwrite
with template main's version unconditionally.
- Path exists in template main and the store's copy differs: if the store blob matches
some main-history blob → replace with template main's version. Otherwise → leave it,
record as
skipped (customized).
- Path exists in the store but not in template main (template deleted it): if the store
blob matches some main-history blob → delete it. Otherwise → leave, record as
skipped (customized, template deleted).
- Store-created paths (never existed on template main): untouched, not reported.
- Special cases — never blanket-copied:
package.json: reconcile dependencies with jq — for every key in the template's
dependencies/devDependencies, set the template's version; drop store keys absent
from template main whose version spec matches some main-history spec (i.e. the template
removed them); keep store-added deps. Leave scripts and the rest to the same
rule-2/rule-3 judgement at the whole-file level only if the file was never customized.
bun.lock: never copied — regenerate with bun install after the reconcile.
.env*, deploy_key.pem: never touched.
Targeted edits for the shell rollout
The performance/accessibility pass on template main (prerendered chrome, optimizer-served favicon,
AA colour tokens) is mostly self-propagating, but two paths need help:
New files land on their own under rule 4 — scripts/check-shell.sh, scripts/audit.sh,
lib/contrast.ts, lib/contrast.test.ts, app/palette.test.ts, lint-staged.config.mjs.
Nothing to do.
package.json scripts.build must be ported even when the file counts as customized,
because the build gate is the whole point of the change:
jq '.scripts.build = "bun next build && bash scripts/check-shell.sh"' package.json | sponge package.json
Add .scripts.audit = "bash scripts/audit.sh" the same way. Everything else in scripts stays as
the store has it.
app/layout.tsx is ~52 commits of churn on main and will almost always come back as
skipped (customized). Do not merge it. Put it in the "needs manual follow-up" list with these
three targeted edits spelled out, so a follow-up run (or a human) can apply them:
- remove the
<Suspense> around the children/chrome wrapper if everything it awaits is a
cached read — the boundary alone streams the chrome out of the prerendered shell; a store that
still awaits its cart cookie there must move that read into a CartBootstrapper below the
chrome first;
- replace the
icons block in the metadata with the optimizer version — one icon entry built
with getImageProps and no type, apple left on the original URL, no shortcut;
Geist_Mono({ …, preload: false }).
After such a follow-up, bun run build in the store repo is the check that says it worked.
Verify, commit, push
In each store repo, in order — a failure at any step means do not push; report instead:
bun install
bun next typegen (regenerates the route-type globals; deleted routes otherwise leave
stale validators)
bun tsc --noEmit
bun run lint if the script exists (biome, auto-fix mode is fine)
Commit everything applied as one commit — subject
chore: propagate template <short-sha of template main>, body listing counts
(updated/deleted/skipped) and the driving issue refs if the invoker supplied them. Plain
push to main, never force. Remember each push redeploys that tenant — when running
across a fleet, process a handful at a time rather than pushing dozens simultaneously.
Scale + report
One store = one agent task; run a few in parallel, each in its own temp dir. At the end,
produce a single table: store · updated · deleted · skipped(customized) · pushed?/error.
Anything skipped (customized) for a file the invoker's change actually needs (e.g. an
edit inside a customized app/layout.tsx) belongs in a "needs manual follow-up" list with
the store name and path — do not attempt semantic merges of customized files unless the
invoker explicitly described the targeted edit to make (e.g. "remove the AuthButton
import and its render line if present").
Live spot-check (optional, when the invoker names expectations)
After a store's push + redeploy, curl -sI https://<subdomain>.<yns.cx|yns.store><path>
against whatever the propagated change promises (e.g. a proxied path now answering with a
platform redirect instead of the template 404). Remember tenant 404s stream with HTTP 200 —
check content or headers, not just the status code.
1---2name: propagate-template3description: Propagate the template's current main into forked AI-store repos (yns-store-* in the tenants org), updating every file the store never customized and reporting the rest. Content-level reconcile — store repos share NO git history with the template. Use for "template rebase", "propagate template", "sync store repos", after landing template changes that existing stores must pick up. Takes an optional list of store subdomains; defaults to every builder store.4---56# Propagate template main into forked store repos78You may be running autonomously across many repos. Do not ask questions — decide, act, and9report. If one store fails, record it and move on to the next.1011## Architecture facts (do not rediscover these)1213- Store repos are `yns-store-<subdomain>` in the GitHub org **`yns-cx-tenants`** (staging /14 yns.cx) or **`yns-store-tenants`** (production / yns.store). Access via `gh`.15- **Store repos share no git history with this template.** Provisioning copies template files16 into a fresh repo without upstream parents (see the platform's17 `src/lib/sandbox/fork-builder.ts`). Never attempt `git rebase`/`git merge` against the18 template — this skill reconciles *content*, file by file.19- Stores may have been seeded from a `theme-*` branch, and Elliot edits files freely20 afterwards. The only files Elliot cannot touch are the **managed paths**21 (`proxy.ts`, `lib/track.tsx`, `instrumentation-client.ts` — the platform's22 `MANAGED_STOREFRONT_PATHS`): the platform's commit script restores those from the store23 repo's own `origin/main` on every save, so *landing the template's version on the store's24 main IS the propagation mechanism* for them.25- Pushing to a store repo's `main` triggers a production redeploy of that tenant storefront,26 and it is safe with respect to live builder sandboxes: their save script fetches and27 rebases onto `FETCH_HEAD`, aborting cleanly on conflict.28- `.env*` files are per-store secrets. Never read, modify, or commit them.2930## Inputs3132- Optional: a list of store subdomains (e.g. `base celeste`) and/or the org to target.33 Default: staging org, all builder stores.34- Enumerate builder stores from the platform DB when available (source of truth):35 `SELECT subdomain FROM stores WHERE builder_config IS NOT NULL;` — otherwise36 `gh repo list <org> --limit 200 --json name` and strip the `yns-store-` prefix.3738## Per-store algorithm3940Work from a checkout of this template repo with full history and all branches fetched41(`git fetch origin "+refs/heads/*:refs/remotes/origin/*"`). Clone each store repo into a42temp dir (`gh repo clone <org>/yns-store-<subdomain> <tmp> -- --depth=1`).4344The decision rule for every path is **"has the store ever customized this file?"**, answered45by blob-hash comparison against the template `origin/main` history:4647```48# all historical blob hashes of a path on template main (cache per path):49git -C <template> log origin/main --format=%H -- <path> \50 | while read sha; do git -C <template> rev-parse -q --verify "$sha:<path>"; done | sort -u51# the store's current blob:52git -C <store> hash-object <path>53```5455Matching only a `theme-*` blob (not any main-history blob) counts as **customized** — theme56identity must never be flattened onto main's look.5758Then, per path:59601. **Managed paths** (`proxy.ts`, `lib/track.tsx`, `instrumentation-client.ts`): overwrite61 with template main's version unconditionally.622. **Path exists in template main** and the store's copy differs: if the store blob matches63 some main-history blob → replace with template main's version. Otherwise → leave it,64 record as `skipped (customized)`.653. **Path exists in the store but not in template main** (template deleted it): if the store66 blob matches some main-history blob → delete it. Otherwise → leave, record as67 `skipped (customized, template deleted)`.684. **Store-created paths** (never existed on template main): untouched, not reported.695. **Special cases** — never blanket-copied:70 - `package.json`: reconcile dependencies with `jq` — for every key in the template's71 `dependencies`/`devDependencies`, set the template's version; drop store keys absent72 from template main whose version spec matches some main-history spec (i.e. the template73 removed them); keep store-added deps. Leave `scripts` and the rest to the same74 rule-2/rule-3 judgement at the whole-file level only if the file was never customized.75 - `bun.lock`: never copied — regenerate with `bun install` after the reconcile.76 - `.env*`, `deploy_key.pem`: never touched.7778## Targeted edits for the shell rollout7980The performance/accessibility pass on template main (prerendered chrome, optimizer-served favicon,81AA colour tokens) is mostly self-propagating, but two paths need help:8283- **New files land on their own** under rule 4 — `scripts/check-shell.sh`, `scripts/audit.sh`,84 `lib/contrast.ts`, `lib/contrast.test.ts`, `app/palette.test.ts`, `lint-staged.config.mjs`.85 Nothing to do.86- **`package.json` `scripts.build`** must be ported even when the file counts as customized,87 because the build gate is the whole point of the change:88 ```89 jq '.scripts.build = "bun next build && bash scripts/check-shell.sh"' package.json | sponge package.json90 ```91 Add `.scripts.audit = "bash scripts/audit.sh"` the same way. Everything else in `scripts` stays as92 the store has it.93- **`app/layout.tsx`** is ~52 commits of churn on main and will almost always come back as94 `skipped (customized)`. Do not merge it. Put it in the "needs manual follow-up" list with these95 three targeted edits spelled out, so a follow-up run (or a human) can apply them:96 1. remove the `<Suspense>` around the children/chrome wrapper **if** everything it awaits is a97 cached read — the boundary alone streams the chrome out of the prerendered shell; a store that98 still awaits its cart cookie there must move that read into a `CartBootstrapper` below the99 chrome first;100 2. replace the `icons` block in the metadata with the optimizer version — one `icon` entry built101 with `getImageProps` and **no** `type`, `apple` left on the original URL, no `shortcut`;102 3. `Geist_Mono({ …, preload: false })`.103104 After such a follow-up, `bun run build` in the store repo is the check that says it worked.105106## Verify, commit, push107108In each store repo, in order — a failure at any step means **do not push**; report instead:1091101. `bun install`1112. `bun next typegen` (regenerates the route-type globals; deleted routes otherwise leave112 stale validators)1133. `bun tsc --noEmit`1144. `bun run lint` if the script exists (biome, auto-fix mode is fine)115116Commit everything applied as one commit — subject117`chore: propagate template <short-sha of template main>`, body listing counts118(updated/deleted/skipped) and the driving issue refs if the invoker supplied them. Plain119push to `main`, **never force**. Remember each push redeploys that tenant — when running120across a fleet, process a handful at a time rather than pushing dozens simultaneously.121122## Scale + report123124One store = one agent task; run a few in parallel, each in its own temp dir. At the end,125produce a single table: store · updated · deleted · skipped(customized) · pushed?/error.126Anything `skipped (customized)` for a file the invoker's change actually needs (e.g. an127edit inside a customized `app/layout.tsx`) belongs in a "needs manual follow-up" list with128the store name and path — do not attempt semantic merges of customized files unless the129invoker explicitly described the targeted edit to make (e.g. "remove the `AuthButton`130import and its render line if present").131132## Live spot-check (optional, when the invoker names expectations)133134After a store's push + redeploy, `curl -sI https://<subdomain>.<yns.cx|yns.store><path>`135against whatever the propagated change promises (e.g. a proxied path now answering with a136platform redirect instead of the template 404). Remember tenant 404s stream with HTTP 200 —137check content or headers, not just the status code.