Detects shared stack membership and iterates a command across all PRs in base-to-tip order. Use when a command supports --stack flag for multi-PR iteration.
npx skills add https://github.com/athola/claude-night-market --skill stack-mode
Shared contract for commands that need to operate across a
whole stack of dependent PRs in one invocation.
Used by /pr-review --stack and /fix-pr --stack.
The goal is one simple thing: when a PR is part of a stack
targeting a common base branch, loop the command's normal
workflow across every PR in the stack in base-to-tip order
and emit one consolidated summary on the stack root.
Load this skill when a command accepts a --stack flag
(or auto-detects stack membership) and needs to iterate
its normal workflow across multiple PRs.
Do NOT load this skill for single-PR workflows. The
per-PR workflow stays unchanged; stack mode wraps it.
sanctum:stack-create)sanctum:stack-rebase)A calling command MUST:
--stack boolean flag AND a --base <branch>override (default: master).
running its main workflow.
links, etc.).
using the format in Step 4.
thread resolution and issue tracking).
A calling command MAY:
than requiring --stack explicitly.
downstream PRs with stale context.
The caller creates TodoWrite items:
stack-mode:membership-resolvedstack-mode:iteration-completestack-mode:root-summary-postedmembership-resolved)Given a starting PR number $PR_NUM and optional base
$BASE (default: master), try three strategies in
order and stop at the first that yields a stack of size
>= 2.
START_HEAD=$(gh pr view "$PR_NUM" \
--json headRefName -q .headRefName)
# stack/<feature-name>/<slice-name>
if [[ "$START_HEAD" =~ ^stack/([^/]+)/.+$ ]]; then
FEATURE="${BASH_REMATCH[1]}"
PREFIX="stack/${FEATURE}/"
# All local branches with the same prefix
STACK_BRANCHES=$(git branch --list "${PREFIX}*" \
| sed 's/^[* ]*//' | sort)
# Map branches -> PR numbers (skip branches with no PR)
STACK_PRS=()
for branch in $STACK_BRANCHES; do
pr=$(gh pr list --head "$branch" \
--json number --jq '.[0].number')
[ -n "$pr" ] && STACK_PRS+=("$pr")
done
echo "strategy=A prs=${STACK_PRS[*]}"
fi
stack-push posts a ## Stack markdown table on the
root PR with columns # | Branch | PR. Parse it:
# Find the root PR of the stack containing $PR_NUM
# The current PR may be the root, or may link to it via
# a "Part of stack `stack/<feature>`" phrase in its body
BODY=$(gh pr view "$PR_NUM" --json body -q .body)
# Look for a "## Stack" table in THIS PR's comments
TABLE=$(gh pr view "$PR_NUM" --json comments \
--jq '.comments[] | select(.body | contains("## Stack"))
| .body' | head -1)
# If not on this PR, look for the root PR reference
if [ -z "$TABLE" ]; then
ROOT_REF=$(echo "$BODY" \
| grep -oE 'stack/[^ `]+' | head -1)
# ... resolve ROOT_REF -> root PR number and re-fetch
fi
# Parse the table: extract all `#<num>` from the PR column
STACK_PRS=($(echo "$TABLE" \
| grep -oE '#[0-9]+' | tr -d '#' | sort -un))
echo "strategy=B prs=${STACK_PRS[*]}"
Walk both directions from $PR_NUM:
baseRefName until it equals$BASE. Each visited head is a stack member.
baseRefName equals one ofthe visited heads. Recurse.
# Ascend from $PR_NUM to the root
CUR=$PR_NUM
CHAIN=($CUR)
while true; do
BASE_REF=$(gh pr view "$CUR" \
--json baseRefName -q .baseRefName)
[ "$BASE_REF" = "$BASE" ] && break
PARENT=$(gh pr list --head "$BASE_REF" \
--state open --json number --jq '.[0].number')
[ -z "$PARENT" ] && break
CHAIN=("$PARENT" "${CHAIN[@]}")
CUR=$PARENT
done
# Descend from the root collecting children
ROOT=${CHAIN[0]}
FRONTIER=("$ROOT")
while [ ${#FRONTIER[@]} -gt 0 ]; do
PARENT=${FRONTIER[0]}
FRONTIER=("${FRONTIER[@]:1}")
PARENT_HEAD=$(gh pr view "$PARENT" \
--json headRefName -q .headRefName)
CHILDREN=($(gh pr list --base "$PARENT_HEAD" \
--state open --json number --jq '.[].number'))
for c in "${CHILDREN[@]}"; do
CHAIN+=("$c")
FRONTIER+=("$c")
done
done
STACK_PRS=("${CHAIN[@]}")
echo "strategy=C prs=${STACK_PRS[*]}"
Regardless of strategy, order the final list base-to-tip
(root PR first, leaf PR last). The root is the PR whose
baseRefName equals $BASE.
ROOT_PR=""
for p in "${STACK_PRS[@]}"; do
base=$(gh pr view "$p" --json baseRefName -q .baseRefName)
if [ "$base" = "$BASE" ]; then
ROOT_PR=$p
break
fi
done
If the stack size is 1, the command MUST fall back to
single-PR mode and emit a warning: `stack-mode: only one
PR found; --stack has no effect, proceeding as single-PR`.
See Skill(leyline:git-platform) for glab / Bitbucket
equivalents of the gh calls above. The three strategies
translate directly: GitLab MR dependencies replace
base-chain walks, and the glab mr view --output json
shape matches gh pr view.
When the caller did NOT pass --stack but Step 1 resolved
a stack of size >= 2, print the stack and ask:
Detected stack of N PRs rooted at #<root>:
1. #<root> <title>
2. #<p2> <title>
...
Run the workflow on the full stack? [y/N]
Default to "N" (single-PR mode). With --stack explicit,
skip this prompt.
iteration-complete)Run the caller's main workflow once per PR in base-to-tip
order:
FAILED_PRS=()
for p in "${STACK_PRS[@]}"; do
echo "=== stack-mode: PR #$p ($(($idx+1))/${#STACK_PRS[@]}) ==="
# Caller hook: run the single-PR workflow on $p
# This includes Gate 1 (thread resolution) and
# Gate 2 (issue tracking) per the caller's contract.
if ! run_single_pr_workflow "$p"; then
FAILED_PRS+=("$p")
# Stop iteration: downstream PRs may now be stale
break
fi
done
If any PR fails, the command MUST stop and report
stack-mode: halted at #<pr>; downstream PRs untouched.
Downstream PRs are intentionally left alone because
their context may have shifted.
root-summary-posted)After successful iteration, post ONE consolidated summary
comment on the root PR. This does NOT replace per-PR
summaries: it links them together.
## Stack <Command Name> Summary
**Stack**: N PRs rooted at #<root>, base `<base-branch>`
**Command**: <command> (e.g., /fix-pr, /pr-review)
**Run**: <timestamp>
| # | PR | Branch | Status | Per-PR Summary |
|---|----|--------|--------|----------------|
| 1 | #<root> | `stack/<feat>/<root-slice>` | <done/skipped/failed> | [link to comment] |
| 2 | #<p2> | `stack/<feat>/<slice-2>` | <done/skipped/failed> | [link to comment] |
| ... | ... | ... | ... | ... |
### Cross-PR Notes
- <Any observations that spanned multiple PRs>
- <Issues created that reference multiple stack PRs>
- <Base-PR-merged cascade reminders, if any>
### Next Steps
- If root PR merges, run `Skill(sanctum:stack-rebase)` to
cascade the base update through descendants.
- Reviewers: rebase-triggered stale-review flags on
descendants are expected after any force-push.
Post via gh pr comment "$ROOT_PR" --body-file <file>.
| Failure | Detection | Recovery |
|---------|-----------|----------|
| Stack of size 1 | Step 1 returns one PR | Emit warning, fall back to single-PR mode |
| Base chain loops | Visited set on ascent | Halt, report stack-mode: cycle detected |
| Mid-stack PR failed its Gates | Caller raises | Halt iteration, leave downstream untouched |
| Strategy B table malformed | Parse fails | Fall through to Strategy C |
| Auth scope insufficient | gh returns 403 | Halt with clear error; caller surfaces to user |
Only the iteration and root summary are new.
tracking) remain unchanged: each PR still passes its
own gates independently.
on one PR is not a resolved thread on another.
should update (not duplicate) the summary comment.
Match on ## Stack <Command Name> Summary as the key.
stack-mode is a read/orchestration skill; it does NOTpush, rebase, or edit branches. Those concerns are in
stack-create, stack-push, and stack-rebase.
strategies (A: branch names, B: stack comment, C: base-chain
walk) and PRs ordered base-to-tip before iteration starts
stack-mode:membership-resolved,stack-mode:iteration-complete, stack-mode:root-summary-posted)
are created and marked complete in sequence
Summary` comment after all PRs are processed
--stack has no effect` is emitted and single-PR mode used
are left untouched with a stack-mode: halted at #<pr> report
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take athola/stack-mode from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.