.agents/skills/pr/SKILL.md
canary (development branch, cloud production)main is the release branch — never PR directly to maingit branch --show-current — current branch namegit status --short — uncommitted changesgit rev-parse --abbrev-ref @{u} 2>/dev/null — remote tracking statusgit log --oneline origin/canary..HEAD — unpushed commitsgh pr list --head "$(git branch --show-current)" --json number,title,state,url — existing PRgit diff --stat --stat-count=20 origin/canary..HEAD — change summaryIf current branch is canary (or main) AND there are uncommitted changes:
git diff) to understand the changes<type>/<short-description> (e.g. fix/i18n-cjk-spacing)git checkout -b <branch-name>git add <files> (prefer explicit file paths over git add .)If current branch is canary/main but there are NO uncommitted changes and no unpushed commits, abort — nothing to create a PR for.
git push -u origin $(git branch --show-current)git push origin $(git branch --show-current)gh issue list --search "<keywords>" --state all --limit 10gh pr create --base canary<gitmoji> <type>(<scope>): <description>.github/PULL_REQUEST_TEMPLATE.md), fill checkboxesFixes #123, Closes #123)Fixes LOBE-xxx)gh pr view --web
Use .github/PULL_REQUEST_TEMPLATE.md as the body structure. Key sections:
The steps above create one PR for the current branch. When a single branch lands across layers — packages/database schema/model → a shared packages/* lib → src/server TRPC → apps/desktop + apps/cli callers → src/features UI — shipping it as one PR can't merge safely: the clients call an endpoint that doesn't exist on the trunk until the same PR merges, so any partial/rollback or independent review breaks. Split it into ordered PRs, lower layer first.
A PR may only merge after every layer it calls is already on the trunk.
canary right now, would it build and behave?" If no, it belongs in a later PR.The non-obvious calls:
listDevices now returns platform: string | null), the component consuming it must change in the same PR — otherwise the server PR breaks the build on its own. Contract + its in-repo consumers ship together.@lobechat/* package imported only by desktop/CLI ships in the client PR. Don't carry an unused package in the lower PR.package.json workspace:*, pnpm-workspace.yaml) travel with the code that imports the package.Starting point: one branch (feat/x) with a single commit <FULL> containing everything, already pushed (so it's also safe on the remote).
# 1. Safety nets — make the full work unloseable before rewriting anything
git branch backup/x-full <FULL> # local ref to the full commit
git branch feat/x-clients <FULL> # the higher-layer branch starts here
# 2. Rewrite the lower-layer branch to lower-layer files only
git checkout feat/x # this becomes the SERVER PR
git reset --hard origin/canary
git checkout <FULL> -- <server/db files…> # stages just those paths
git commit -m "✨ feat(...): <server half>"
git push --force-with-lease origin feat/x # never --force; never push to canary
# 3. Build the higher-layer branch STACKED on the lower branch
git checkout feat/x-clients
git reset --hard feat/x # base = the just-rewritten server HEAD
git checkout backup/x-full -- <client/ui files…> # only the remaining paths
git commit -m "✨ feat(...): <client half>"
git push -u origin feat/x-clients
Then open the higher PR based on the lower branch, not the trunk:
gh pr create --base feat/x --head feat/x-clients --title "…" --body "…"
--base feat/x keeps the diff client-only (no server files leak in) and makes it physically impossible to merge the clients before the server. After the server PR merges to canary, retarget the client PR's base to canary (GitHub usually auto-retargets when the base branch merges; note it in the PR body so a human confirms).
The whole point is the higher layer needs the lower one. Prove it: on the stacked higher branch, type-check the caller and confirm the symbol the lower layer introduced resolves.
cd apps/cli && bun run type-check 2>&1 | grep -iE "connect\.ts|device\.register"
# empty (re: your change) = the stacked base supplies device.register ✓
Filter to your touched files — this repo's standalone type-check emits pre-existing env noise (__ELECTRON__, @/types/llm, unbuilt @lobechat/types) that isn't yours.
Closes LOBE-<server>. Client PR: Closes LOBE-<pkg> / <desktop> / <cli>. Don't let one PR's body claim another layer's issue.Part of LOBE-<parent>.linear skill.canary. A split branch cut with git checkout -b feat/x origin/canary tracks origin/canary, so a bare git push targets canary. Always git push origin feat/x with the explicit branch name.--force-with-lease, not --force when rewriting the lower branch — it aborts if the remote moved under you.reset --hard. Step 1's backup/x-full + the pushed remote branch mean the full commit is referenced by ≥3 refs before you rewrite anything. Verify with git branch --contains <FULL>.pnpm-lock.yaml, so a new workspace:* dep needs no lockfile churn. In a repo that does commit one, regenerate it on each branch after the split.