Shepherd an emule PR to mergeable
The job: take an open PR and get it ready to land — review it against the repo
ethos, confirm every review comment is actually resolved, fix what's still
wrong, rebase onto origin/main locally, and hand the developer a
change+verification summary (with a drafted per-point comment) to review and
push. The PR author's later commits usually claim to have addressed the review
("Address review …"); an automated reviewer (Copilot) and a human reviewer have
usually weighed in. None of that is trustworthy on its own.
Human-in-the-loop for the push. The skill rebases and prepares, but it does not force-push the author's branch or post the comment on its own — force pushing a shared branch rewrites someone else's history and is a human decision. The deliverable is a rebased local branch plus a summary the developer signs off on before pushing.
The governing principle, learned across three PRs: verify by building and reading, not by trusting an "addressed in commit X" claim or a green CI label — and always run your own ethos pass, because the reviewers miss the emule-specific bug classes. In #237/#238/#239, the human + Copilot reviewers each missed a hard JIT-compile bug that a 5-line compile-probe surfaces immediately.
The ethos you are enforcing (from .claude/CLAUDE.md): faithful to the canonical
silicon implementation, a single code path (no divergent duplicate), the
minimal jit_hw surface, surface caller bugs rather than mask them, and be
skeptical of automated-reviewer suggestions — judge each against these rules,
don't apply reflexively.
When to invoke
- You're asked to review an open PR, address its comments, and prepare it to land (rebase locally, then hand off a summary for the developer to push/comment).
- A PR is
CONFLICTINGor behind main and needs a rebase plus a review pass.
Not this skill:
- A pin bump turned the regression red (device-open crash, broad JIT-compile
drift, hang) →
/uplift. - Reviewing your own uncommitted working diff for quality →
/code-review(built-in). Generic GitHub PR review with no landing →/review(built-in). - This skill is for shepherding a feature / shim PR to merge against review +
ethos, and it borrows
/uplift's prove-by-build discipline and rebase mechanics.
Step 0 — Gather the full picture
Read every comment surface — reviews live in three different API buckets and
gh pr view only shows one:
gh pr view <n> --json number,title,state,headRefName,baseRefName,author,mergeable,body,files,commits,statusCheckRollup- Issue comments:
gh pr view <n> --json comments - Review bodies (CHANGES_REQUESTED / APPROVED verdicts):
gh api repos/<owner>/tt-emule/pulls/<n>/reviews - Inline (line-level) comments:
gh api repos/<owner>/tt-emule/pulls/<n>/comments
Note who requested changes vs approved, and reconcile: a reviewer may approve
after leaving unresolved concerns. Read the CI artifact, not the label — a
"pass" can hide smoke/JIT failures, and a FAILURE row named "All checks passed"
is a required-check quirk, not the signal. Beware the rtk wrapper summarizing
git status to ok; get real status with git status --porcelain | wc -l.
Step 1 — Check out and measure the rebase
git fetch origin <branch> && git checkout <branch>
git merge-base --is-ancestor origin/main HEAD && echo up-to-date || echo REBASE NEEDED
git log --oneline origin/main..HEAD # commits to replay
git merge-tree $(git merge-base origin/main HEAD) origin/main HEAD | grep -i conflict
The branch may carry force-pushed "agent-storm" history (transient symbols,
edits, repeated docs: refresh structure index). Work from the actual tip
(git ls-remote origin refs/heads/<branch>), not the commit-message narrative.
Step 2 — Verify each review point against current code, in the ethos lens
For every comment, confirm the fix in the tree — a commit titled "Address review" often only partially fixes, or fixes one call site. Categorize each point:
| Verdict | Meaning |
|---|---|
| resolved | fixed in the tree; confirm it compiles / matches the ethos |
| partial | one instance fixed, another remains (e.g. one requires converted, two left) |
| open | not addressed; you must fix or register it |
| moot | the flagged file/line no longer exists after a force-push |
Judge against the ethos, not just "does it compile" — a change can compile and still be a divergent duplicate or a silent silicon divergence.
Step 3 — Own ethos pass (the catalog reviewers miss)
Sweep the diff for these recurring emule bug classes:
- Dangling refactored symbol. A bare
__emule_dst(the DST file moved into__emule_compute_ctx().dstin #223), or any symbol agit grepfinds only in comments plus the new code. A non-dependent name in a plaininline/non-template function errors at parse time, so any TU that#includes the header fails to compile — even if the function is never instantiated. Bit #238 (gelu_tanh_tile) and #239 (transpose_wh_dest). Fix: route through the current accessor, match the sibling ops in the same file, add the__emule_dst_checkthose siblings use. - Divergent-duplicate / ODR. The same symbol defined in two headers under one
#ifdefguard with non-identical bodies (#239sigmoid_tile_packforwarded to two differentsigmoid_tileoverloads); or a header thatinline-defines a symbol the tt-metal runner also defines (#237asan_cb_state.hvsemulated_program_runner.cpp). Fix: one definition site; the other path defers to it. - Silent silicon divergence. A core-op special-case that masks an upstream bug
rather than reproducing silicon — e.g. skipping zero-multiplier lanes so
inf*0doesn't becomeNaN, when silicon FP does produce NaN (#239). Not faithful. Fix: fix the upstream inf source, or — when that's blocked — register it in/workaroundswith the root cause + removal path and a code-siteWORKAROUNDcomment. Never ship it silently. - C++ standard mismatch. A
requires-expression / C++20 construct in a JIT header. Confirm the JIT-std: it isTT_EMULE_CXX_STANDARD= 20 (documented indocs/metal-integration.md), so C++20 is fine on the real path; a doc still claiming C++17 is stale and a separate cleanup, not a blocker (#237). - Superseded-by-main. Main independently solved the PR's problem a different
way. Check current
origin/mainand the pinned tt-metal runner for the symbols the branch reworks. If so, a mechanical rebase re-introduces a parallel path — trim the PR to what's still needed instead (#237: ASAN restructure was dead; runner already defined the symbols and the log moved to the fiber ctx).
Step 4 — Prove by build (the JIT compile-probe)
The load-bearing verification. It catches classes 1, 2, and 4 in seconds without a
full build. Reconstruct the JIT flags from
tt_metal/impl/emulation/emulated_program_runner.cpp (-std=c++<TT_EMULE_CXX_STANDARD>,
-I jit_inc, -I parent_inc, -DTT_EMULE_USE_L1_POOL):
# probe.cpp — include the umbrella FIRST so the compute ctx / VectorMode / ALWI
# exist, THEN the header under test. A jit_hw header alone usually can't be the
# first include.
cat > probe.cpp <<'EOF'
#include "jit_hw/api/compute/compute_kernel_api.h"
#include "jit_hw/<header under review>" // e.g. api/compute/eltwise_unary/gelu.h
EOF
clang++-20 -std=c++20 -fsyntax-only \
-DTT_EMULE_USE_L1_POOL -DARCH_WORMHOLE -DNUM_DRAM_BANKS=12 -DNUM_L1_BANKS=64 \
-I <tt-emule>/include/jit_hw -I <tt-emule>/include \
-I <tt-metal>/tt_metal/hostdevcommon/api probe.cpp
- For a header on the matmul AVX2 path, add
-DEMULE_MATMUL_USE_AVX2 -mavx2 -mfma. - For the fused-activation aggregator, define
SFPU_ACTIVATIONand includecompute_kernel_hw_startup.hafter the umbrella. - To decide a C++20-vs-17 concern, run the probe at both
-std=c++20and-std=c++17— clean at 20 / error at 17 proves it's std-only, and the JIT is 20. - Grep-corroborate class 1:
git grep '<symbol>'should find a real definition, not only comments;emule_thread_ctx.hrecords what a symbol "was".
This is a targeted probe of the changed headers, not the full suite — see the Step 8 caveat.
Step 5 — Make changes as needed
- Faithful fix. Match the file's existing idiom and sibling ops; single code
path; no defensive divergence (
/verify-mock,/memory-debugfor residuals). - Register a workaround. When the faithful fix is blocked, add a
/workaroundsentry (what it is, why it bends a rule, real root cause, removal path, tracking issue) and aWORKAROUND (WA-n)code-site comment. Don't leave a silent divergence. - Trim superseded work. Drop the parts main already provides; keep the genuinely new contribution.
- Surface scope-changing calls to the user before putting them in the handoff — trimming a large chunk (#237) or keeping-vs-reverting a workaround (#239) is the reviewer's decision, not yours. Use AskUserQuestion with a concrete recommendation.
Step 6 — Rebase onto origin/main (supersession-aware)
git fetch origin main && git rebase origin/main
- Resolve each conflict by asking "does main + the pinned runner already provide
this?" —
git grepthe tt-metal runner (.../impl/emulation/) for the symbols. Favor main's evolved model over a superseded branch approach. structure.yamlconflicts: resolve crudely (take either side), finish the rebase, then regenerate against the final tree:python3 scripts/gen_structure.py --write, and confirmpython3 scripts/gen_structure.py --check+git diff --check.- Trimming pattern: after the rebase, a single self-documenting cleanup commit
is cleaner than commit surgery (interactive rebase is unavailable here) —
git checkout origin/main -- <superseded files>,git rm <orphaned files>, regeneratestructure.yaml, commit with the rationale. The net tree is what ships. - Keep the branch's own PR ref intact — detach/rebase, don't reset a shared branch onto something unexpected.
- The rebase stays local. Pushing the rewritten history is Step 7's handoff, performed by the developer — the skill never pushes it.
Step 7 — Hand the developer a summary (don't push)
Stop at the rebased local branch. Do not git push — force-pushing a
shared branch rewrites the author's history and is the developer's call, not the
skill's. Instead present a handoff the developer can act on:
What changed and why — per fix: the review point / ethos-class it resolves, the mechanism (not just "done"), and the file:line.
Verification evidence — the compile-probe command(s) you ran and the result (the actual
use of undeclared identifierline, or a clean exit), plus the honest caveat (a targeted header probe is notrun_regression.sh).The drafted per-point PR comment (Step 8) for the developer to post.
The exact commands to run, so the human performs the branch mutation:
git push --force-with-lease origin <branch> # rebase rewrote history; --force-with-lease # guards a concurrent update to the tip you based on gh pr comment <n> --body-file <file>
The developer reviews the summary, then pushes and posts (or tells you to). If the user explicitly directs you to push a specific branch, that direction is the authorization for that branch only — don't extend it to others, and still surface the summary first.
Step 8 — Draft the per-point comment (developer posts it)
Draft the comment as part of the handoff — gh pr comment <n> --body-file <file>
is the command the developer runs (Step 7). Include:
- Per-point verdict for every review comment: resolved / fixed-here / registered (WA-n) / trimmed / moot — with the mechanism, not just "done".
- Any extra bug you found, with the compile-error proof pasted (the
use of undeclared identifierline, etc.) so it's not an assertion. - The honest verification caveat: targeted compile-probes are not a full
run_regression.sh(there is usually nobuild_emulein-workspace) → recommend a per-arch regression before merge. - The alternative you didn't take (e.g. "revert vs register the workaround — say the word") so the reviewer can redirect.
Anti-patterns (consolidated)
- Trusting "addressed in commit X" without reading the resulting tree.
- Trusting a green CI label — or rtk's filtered
git statusok. Read the artifact; get--porcelainstatus. - Skipping the own-ethos pass. Reviewers missed a hard compile bug in all three reference PRs; the compile-probe finds it in seconds.
- Blindly rebasing a superseded PR — re-introducing a divergent parallel path main already replaced.
- Pushing the author's branch yourself. The skill rebases locally and
hands off; the developer reviews the summary and runs the
--force-with-leasepush (and posts the comment). Never force-push a shared branch on your own — it rewrites someone else's history. This is doubly true for a scope-changing reconciliation (a trim, a revert), which needs explicit sign-off besides. - Claiming verification you didn't do — a targeted header compile is not the regression suite; say which you ran.
- Applying Copilot/automated suggestions reflexively — judge each against the ethos (a suggested defensive clamp is usually the wrong fix).
References
/uplift— prove-by-build discipline, rebase/pin & cross-repo push mechanics, reading the CI artifact over the label./workarounds— how to register a deliberate divergence (Step 5 / class 3)./memory-debug,/verify-mock— residual triage and mock-completeness checks..claude/CLAUDE.md— the ethos rules this skill enforces.scripts/gen_structure.py(--write/--check),scripts/find_symbol.py(--supports/--shadowsto probe whether a symbol/include is modelable).