name: leanspec-pre-push description: Run before pushing code to the lean-spec repo to catch what reviewers and CI will catch later, and confirm the branch has a linked spec issue in a valid state. Reproduces the merge-preview environment, walks through this repo's common merge-conflict patterns, and runs the project's typecheck / clippy / test gates. Triggers include "before push", "ready to push", "pre-push check", "push readiness", "prep for PR", "resolve merge conflict", "merge conflict", "branch has conflicts", "sync with main", or proactively before any git push on a lean-spec branch. metadata: internal: true
leanspec-pre-push
Mechanical checklist that catches the reviewer / CI failures this repo has actually had, plus a spec-link check that enforces the SDD loop locally.
This is lean-spec's analogue of onsager-pre-push / duhem-pre-push. The discipline is the same; the toolchain checks are lean-spec's (pnpm + cargo). The repo-specific patterns below come from this monorepo's structure (TypeScript packages, Rust crates, i18n locale files, schemas).
Why
CI on pull_request checks out a merge of origin/main + the PR branch, not the branch alone. Local pnpm typecheck without that merge is insufficient.
The spec-link step enforces "no PR without a spec or a trivial label" at push time, before the PR is open — so the author sees the problem locally instead of hearing about it from a reviewer.
Steps
Run all of these from the repo root.
1. Sync main into the branch
git fetch origin main
git merge origin/main --no-edit
Resolve conflicts locally, before push — never on the PR "Resolve conflicts" web editor (it bypasses any local validation). If the merge aborts cleanly, skip to step 2.
Resolving conflicts
Inventory what conflicted:
git status --short # U* lines = unresolved paths git diff --name-only --diff-filter=UWork by pattern, not by file. A single logical conflict often spans several files. Match what you see against the patterns below before touching conflict markers — the right fix is often "take main's version and re-apply your change on top", not a line-by-line merge.
Resolve, then stage each resolved path with
git add <path>. Re-rungit statusuntil noU*entries remain.Verify before committing the merge. Run
pnpm typecheck && pnpm pre-push. Runpnpm testfor the affected packages. If the merge touched Rust, also runpnpm build:rustandpnpm test:rust.Only then:
git commit --no-edit # default "Merge branch 'main' ..." messageIf you get lost, bail and retry:
git merge --abortThis restores pre-merge state. Never
git reset --hardorgit checkout --without confirming nothing is staged you care about — the merge carries uncommitted resolutions.Prefer
mergeoverrebasefor syncing main here: the branch is likely already pushed, rebase rewrites history, and force-push is a destructive action.
Common collision patterns to watch for
CHANGELOG.md: both branches added entries under the same[Unreleased]heading. Both should land — concatenate alphabetically by category (Added, Changed, Fixed, …).package.jsonversion field: never resolve by hand. Runpnpm sync-versionsto re-derive from the root version. Seeleanspec-development"Publishing & Releases".pnpm-lock.yaml: never hand-edit. After resolving the sourcepackage.jsonconflicts, runpnpm installto regenerate the lockfile, thengit add pnpm-lock.yaml.Cargo.lock: never hand-edit. After resolving RustCargo.tomlconflicts, runpnpm build:rustand let cargo regenerate.locales/en.jsonandlocales/zh-CN.json: both branches added i18n keys. Both should land; verify each new key exists in both files. If one branch added a key only toenand the other added only tozh-CN, that's a process bug — fix the missing parity before committing the merge.schemas/*.json: JSON schema files. Both branches added fields. Merge by key; verify the schema still validates by running the validator (when wired) orpnpm typecheckto confirm the generated TypeScript types still compile.- Generated
packages/**/dist/: don't merge. Delete the conflict and rerunpnpm build. specs/legacy directory: don't author new files here post-migration. If a conflict exists inspecs/because both branches added a newspecs/NNN-slug/, the correct fix is usually to delete both new directories and migrate them to GitHub issues viaissue-spec. If they pre-date migration, take both arms.
2. Build / typecheck / test
Run, in order:
pnpm typecheck # mandatory before marking work complete
pnpm pre-push # typecheck + clippy
pnpm test # full Vitest suite
pnpm format:rust:check # if Rust changed
pnpm test:rust # if Rust changed
If the change touches the desktop bundle (packages/desktop/), also run pnpm dev:desktop smoke check.
Treat any warning as a blocker. Do not #[allow(dead_code)], #[allow(unused)], or @ts-ignore your way past it; fix the root cause. Rust clippy is -D warnings — warnings are errors.
3. i18n parity check
If the diff touches user-visible strings:
- Locale files live under
locales/en.jsonandlocales/zh-CN.json(and any per-package equivalents — seeI18N.md). - For every key added in
en.json, confirm the same key exists inzh-CN.json. Translation can be a placeholder + a// TODO(i18n)comment, but the key must exist — CI will fail ifzh-CNis missing keys. - If you added a new locale file, add it to whatever locale-loader registers them.
4. Spec-issue link check
Before pushing, confirm this branch corresponds to a known spec issue (or is explicitly trivial). This is the local enforcement of the SDD loop's spec-link rule.
Find the spec issue. Search open issues with the
speclabel oncodervisor/leanspec:mcp__github__list_issues repo=codervisor/leanspec labels=[spec] state=openOr read your commit messages (
git log origin/main..HEAD) for a#Nreference.If you can't find one, stop and create one via
issue-spec(or triage whether this is trulytrivial).Confirm any open questions on the spec are resolved. If the spec's
### Open questionssubsection still has unanswered items, stop and resolve them in the issue thread first — the design isn't pinned yet.Draft the PR body linking line so you can paste it in:
Closes #Nif this PR delivers the full spec.Part of #Nif it's one slice of a multi-PR spec.Fixes #Nfor a defect referenced by a bug spec.
Also draft a
## Deliverssubsection listing the exact Plan items you tick with this PR.Scan the branch's commit messages for implicit issue references (advisory, not blocking):
git log --format='%s%n%b' origin/main..HEAD | grep -oE '#[0-9]+' | sort -uFor each
#Nreturned, decide deliberately:- PR delivers that issue's acceptance → add
Closes #Nto the body. Multi-issueCloseslines are fine (Closes #27, Closes #30, Closes #33). Auto-close doesn't fire for issues that are only mentioned in commit subjects — without an explicitClosesline, those issues stay open after merge. - PR only touches that issue → use
Refs #Nso it cross-links without claiming closure. - False positive (issue number inside a code identifier, commit hash, etc.) → ignore.
- PR delivers that issue's acceptance → add
If this is genuinely trivial (typo, doc-only, one-line obvious fix), skip the spec-link substeps above and plan to apply the
triviallabel to the PR immediately aftermcp__github__create_pull_request.
5. Provider-impact and i18n evidence check
If the spec issue this PR closes is labeled provider-impact:
- Confirm
## Provider impactis filled in on the spec. - Confirm the PR body mirrors the relevant subset.
- If
Breaking change? yeson the spec, confirm a CHANGELOG entry is staged for this PR under the next[Unreleased]heading. Seeleanspec-development"Changelog".
If the spec issue is labeled i18n:
- Confirm both
locales/en.jsonandlocales/zh-CN.json(and any per-package locale files) contain the new keys.
6. Push
git push -u origin <branch>
Retry up to 4 times with exponential backoff (2s, 4s, 8s, 16s) on transient network errors. Never use --force on main or long-lived branches without explicit ask.
After push, open the PR with the spec-link line in its body (or apply the trivial label). The spec issue stays open until the PR closes it — no status labels to flip.
Fast path
If nothing under tracked source paths changed (e.g. docs-only edits):
- Step 2 reduces to
pnpm typecheckif any docs touch typed config; otherwise it's a no-op. - Step 1 (sync main) is not skippable — main may have moved.
- Step 4 (spec-link) is not skippable for non-trivial PRs.
What this skill does NOT cover
- Writing the spec issue — see
issue-spec(installed globally fromonsager-ai/dev-skills). - Opening or managing the PR — see
leanspec-pr-lifecycle. - The end-to-end dev loop — see
leanspec-dev-process. - Commands, CI workflows, publishing pipelines — see
leanspec-development. - GitHub CLI in cloud sessions — see
github-integration(installed globally fromonsager-ai/dev-skills).