| 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
git diff --name-only --diff-filter=U
-
Work 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-run git status until no U* entries remain.
-
Verify before committing the merge. Run pnpm typecheck && pnpm pre-push. Run pnpm test for the affected packages. If the merge touched Rust, also run pnpm build:rust and pnpm test:rust.
-
Only then:
git commit --no-edit
-
If you get lost, bail and retry:
git merge --abort
This restores pre-merge state. Never git reset --hard or git checkout -- without confirming nothing is staged you care about — the merge carries uncommitted resolutions.
Prefer merge over rebase for 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.json version field: never resolve by hand. Run pnpm sync-versions to re-derive from the root version. See leanspec-development "Publishing & Releases".
pnpm-lock.yaml: never hand-edit. After resolving the source package.json conflicts, run pnpm install to regenerate the lockfile, then git add pnpm-lock.yaml.
Cargo.lock: never hand-edit. After resolving Rust Cargo.toml conflicts, run pnpm build:rust and let cargo regenerate.
locales/en.json and locales/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 to en and the other added only to zh-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) or pnpm typecheck to confirm the generated TypeScript types still compile.
- Generated
packages/**/dist/: don't merge. Delete the conflict and rerun pnpm build.
specs/ legacy directory: don't author new files here post-migration. If a conflict exists in specs/ because both branches added a new specs/NNN-slug/, the correct fix is usually to delete both new directories and migrate them to GitHub issues via issue-spec. If they pre-date migration, take both arms.
2. Build / typecheck / test
Run, in order:
pnpm typecheck
pnpm pre-push
pnpm test
pnpm format:rust:check
pnpm test:rust
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.json and locales/zh-CN.json (and any per-package equivalents — see I18N.md).
- For every key added in
en.json, confirm the same key exists in zh-CN.json. Translation can be a placeholder + a // TODO(i18n) comment, but the key must exist — CI will fail if zh-CN is 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 spec label on codervisor/leanspec:
mcp__github__list_issues repo=codervisor/leanspec labels=[spec] state=open
Or read your commit messages (git log origin/main..HEAD) for a #N reference.
If you can't find one, stop and create one via issue-spec (or triage whether this is truly trivial).
-
Confirm any open questions on the spec are resolved. If the spec's ### Open questions subsection 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 #N if this PR delivers the full spec.
Part of #N if it's one slice of a multi-PR spec.
Fixes #N for a defect referenced by a bug spec.
Also draft a ## Delivers subsection 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 -u
For each #N returned, decide deliberately:
- PR delivers that issue's acceptance → add
Closes #N to the body. Multi-issue Closes lines are fine (Closes #27, Closes #30, Closes #33). Auto-close doesn't fire for issues that are only mentioned in commit subjects — without an explicit Closes line, those issues stay open after merge.
- PR only touches that issue → use
Refs #N so it cross-links without claiming closure.
- False positive (issue number inside a code identifier, commit hash, etc.) → ignore.
-
If this is genuinely trivial (typo, doc-only, one-line obvious fix), skip the spec-link substeps above and plan to apply the trivial label to the PR immediately after mcp__github__create_pull_request.
5. Provider-impact and i18n evidence check
If the spec issue this PR closes is labeled provider-impact:
- Confirm
## Provider impact is filled in on the spec.
- Confirm the PR body mirrors the relevant subset.
- If
Breaking change? yes on the spec, confirm a CHANGELOG entry is staged for this PR under the next [Unreleased] heading. See leanspec-development "Changelog".
If the spec issue is labeled i18n:
- Confirm both
locales/en.json and locales/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 typecheck if 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