src/autoskillit/skills_extended/analyze-prs/SKILL.md
Analyze all open PRs targeting a base branch — determine merge order, identify file overlaps, and tag each PR as simple or needs_check for complexity. Use at the start of a PR consolidation workflow.
npx skillsauth add talont-org/autoskillit analyze-prsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Analyze all open PRs targeting a base branch, determine a safe merge order, assess
complexity, and produce machine-readable output for the merge-prs recipe.
merge-prs runNEVER:
{{AUTOSKILLIT_TEMP}}/merge-prs/ directoryrun_in_background: true is prohibited)ALWAYS:
model: "sonnet" when spawning all subagents via the Task toolgh CLI is not authenticated{base_branch} [merge_queue_data_path=<path>]
base_branch — the base branch to list PRs against (e.g., main)merge_queue_data_path (optional) — absolute path to a JSON file containing pre-fetched merge queue data (produced by fetch_merge_queue_data run_python step). When provided and present, read from file instead of calling GitHub GraphQL API inline.Run:
gh pr list --base {base_branch} --state open --json number,title,headRefName,author,body,additions,deletions,changedFiles --limit 100
If zero PRs are returned: write a summary to terminal and exit cleanly with an empty
pr_order_{ts}.json (zero PRs, no integration branch needed).
If gh returns an auth error: abort with a clear message.
Before fetching diffs, load pre-fetched merge queue data to determine whether a merge queue
is active on {base_branch} with MERGEABLE entries.
Read pre-fetched merge queue data from disk when available:
if [ -n "${merge_queue_data_path:-}" ]; then
if [ -f "$merge_queue_data_path" ]; then
QUEUE_ENTRIES="$(cat "$merge_queue_data_path")"
else
echo "WARNING: merge_queue_data_path='$merge_queue_data_path' provided but file not found — possible misconfiguration. Falling back to no-queue mode."
QUEUE_ENTRIES="[]"
fi
else
QUEUE_ENTRIES="[]" # no path provided (standalone invocation) → QUEUE_MODE=false
fi
QUEUE_ENTRIES is a JSON array of {position, state, pr_number, pr_title} objects,
pre-fetched by the fetch_merge_queue_data run_python step in the recipe. When
merge_queue_data_path is absent (standalone invocation), QUEUE_ENTRIES defaults to
[], which sets QUEUE_MODE = false.
Determine mode:
state == "MERGEABLE":
set QUEUE_MODE = true and store the full sorted entry list as QUEUE_ENTRIES.QUEUE_MODE = false and proceed with the existing analysis path.Log which mode was selected and the entry count to the terminal so pipeline runs are observable.
State variables set by this step (referenced in Steps 1–4):
| Variable | Type | Description |
|----------|------|-------------|
| QUEUE_MODE | boolean | true when the merge queue has ≥1 MERGEABLE entry; false otherwise |
| QUEUE_ENTRIES | list[dict] | Sorted queue entries {position, state, pr_number, pr_title} when QUEUE_MODE = true; empty list when false |
If QUEUE_MODE = false: ALWAYS launch subagents in parallel — never process PRs
sequentially. Launch one Explore subagent per PR (up to 8 simultaneously; batch in groups
of 8 if more):
Each subagent fetches:
gh pr diff {number} — full unified diffgh pr view {number} --json files — structured file list; extract path strings via
gh pr view {number} --json files -q '[.files[].path]'gh pr view {number} --json body -q .body — PR body to extract ## Requirements section if presentEach subagent returns:
pr_number: inttitle: strbranch: str (headRefName)files_changed: list of file paths (strings extracted from .files[].path)additions: intdeletions: inttest_files_changed: list of test file paths (files matching test_*.py, *_test.py, *.test.*, tests/**)requirements_section: str — the ## Requirements section extracted from the PR body, or "" if not presentIf QUEUE_MODE = true: for each PR number in QUEUE_ENTRIES, fetch only the
metadata needed for the manifest (no diffs, no body extraction):
gh pr view {number} --json headRefName,files,additions,deletions,changedFiles
Extract file paths from the files array: gh pr view {number} --json files -q '[.files[].path]'.
Collect files_changed (list of file path strings), test_files_changed (subset matching
test_*.py, *_test.py, *.test.*, tests/**), additions, deletions, and
branch (headRefName). Run these fetches in parallel (up to 8 simultaneously).
The PR list for subsequent steps is exactly QUEUE_ENTRIES — do not use the
gh pr list output.
If QUEUE_MODE = false: After fetching all PR diffs, filter the candidate list before
building the overlap matrix. PRs that fail either gate are reported in the manifest but
excluded from merge ordering.
Use a single GraphQL alias query to fetch CI status and reviews for all PRs at once (1 API call regardless of PR count, instead of 2N sequential REST calls). Chunk into batches of 20 PRs to stay within GraphQL complexity limits.
ELIGIBLE_PRS=()
CI_BLOCKED_PRS=() # [{number, title, reason}]
REVIEW_BLOCKED_PRS=() # [{number, title, reason}]
# Build GraphQL alias query for all PRs in batches of 20
ALL_PR_NUMS=($(echo "$ALL_PRS" | jq -r '.[].number'))
BATCH_SIZE=20
for batch_start in $(seq 0 $BATCH_SIZE $((${#ALL_PR_NUMS[@]} - 1))); do
BATCH_NUMS=("${ALL_PR_NUMS[@]:$batch_start:$BATCH_SIZE}")
QUERY="query { "
for i in $(seq 0 $((${#BATCH_NUMS[@]} - 1))); do
NUM="${BATCH_NUMS[$i]}"
QUERY="${QUERY} pr${i}: repository(owner: \"${OWNER}\", name: \"${REPO}\") {
pullRequest(number: ${NUM}) {
number
reviews(last: 20) { nodes { state } }
commits(last: 1) { nodes { commit { statusCheckRollup {
state
contexts(last: 100) { nodes { ... on CheckRun { name status conclusion } } }
} } } }
}
}"
done
QUERY="${QUERY} }"
BATCH_RESULT=$(gh api graphql -f query="${QUERY}")
# Parse per-PR results from BATCH_RESULT
for i in $(seq 0 $((${#BATCH_NUMS[@]} - 1))); do
NUM="${BATCH_NUMS[$i]}"
PR_TITLE=$(echo "$ALL_PRS" | jq -r --argjson n "$NUM" '.[] | select(.number == $n) | .title')
PR_DATA=$(echo "$BATCH_RESULT" | jq --arg key "pr${i}" '.data[$key].pullRequest')
# --- CI Gate ---
FAILING=$(echo "$PR_DATA" | jq '
[(.commits.nodes[0].commit.statusCheckRollup.contexts.nodes // [])[] |
select(.conclusion != null and
.conclusion != "success" and
.conclusion != "skipped" and
.conclusion != "neutral")] | length')
IN_PROGRESS=$(echo "$PR_DATA" | jq '
[(.commits.nodes[0].commit.statusCheckRollup.contexts.nodes // [])[] |
select(.conclusion == null)] | length')
if [ "${FAILING:-0}" -gt 0 ] || [ "${IN_PROGRESS:-0}" -gt 0 ]; then
REASON="CI failing: ${FAILING} failed, ${IN_PROGRESS} in-progress"
CI_BLOCKED_PRS+=("{\"number\":${NUM},\"title\":\"${PR_TITLE}\",\"reason\":\"${REASON}\"}")
continue
fi
# --- Review Gate ---
CHANGES_REQUESTED=$(echo "$PR_DATA" | jq '
[.reviews.nodes |
group_by(.author.login)[] |
last |
select(.state == "CHANGES_REQUESTED")] | length')
if [ "${CHANGES_REQUESTED:-0}" -gt 0 ]; then
REASON="${CHANGES_REQUESTED} unresolved CHANGES_REQUESTED review(s)"
REVIEW_BLOCKED_PRS+=("{\"number\":${NUM},\"title\":\"${PR_TITLE}\",\"reason\":\"${REASON}\"}")
continue
fi
mapfile -t _pr_entry < <(echo "$ALL_PRS" | jq -c --argjson n "$NUM" '.[] | select(.number == $n)')
ELIGIBLE_PRS+=("${_pr_entry[@]}")
done
done
All subsequent steps (overlap matrix, topo sort, PR ordering) operate on ELIGIBLE_PRS only.
CI_BLOCKED_PRS and REVIEW_BLOCKED_PRS are written to the manifest in Step 5.
If QUEUE_MODE = true: skip this step entirely. The MERGEABLE state returned by
the merge queue API already signifies that the PR passed CI checks and has no blocking
reviews. Initialize both arrays explicitly so Step 5 can reference them unconditionally:
CI_BLOCKED_PRS=()
REVIEW_BLOCKED_PRS=()
If QUEUE_MODE = false: For each pair of PRs, compute:
shared_files: files modified by both PRsshared_test_files: test files modified by both PRsA PR pair is conflicting if shared_files is non-empty.
If QUEUE_MODE = true: skip this step entirely. The merge queue has already proven
compatibility. Set overlap_with_pr_numbers = [] for every PR.
If QUEUE_MODE = false: Order PRs to minimize cascading conflict risk:
Produce a final ordered list. Document the rationale for each ordering decision.
If QUEUE_MODE = true: the merge order is QUEUE_ENTRIES sorted by position
ascending (ascending is already guaranteed by parse_merge_queue_response).
If QUEUE_MODE = false: For each PR in the ordered list, assign a complexity tag:
simple — all of the following are true:
needs_check — any of the following:
If QUEUE_MODE = true: tag every PR whose queue entry has state == "MERGEABLE" as
simple. Any entry with a different state (e.g., AWAITING_CHECKS) retains the
needs_check tag (defensive — such entries should not appear since Step 0.5 only sets
QUEUE_MODE when MERGEABLE entries exist, but the tag is applied correctly for robustness).
Compute a timestamp: YYYY-MM-DD_HHMMSS.
Compute integration branch name: pr-batch/pr-merge-{YYYYMMDD-HHMMSS}.
Ensure {{AUTOSKILLIT_TEMP}}/merge-prs/ exists.
5a. Machine-readable order file: {{AUTOSKILLIT_TEMP}}/merge-prs/pr_order_{ts}.json
{
"batch_branch": "pr-batch/pr-merge-YYYYMMDD-HHMMSS",
"base_branch": "{base_branch}",
"generated_at": "{ISO timestamp}",
"pr_count": 5,
"prs": [
{
"number": 42,
"title": "Add user authentication",
"branch": "feature/auth",
"complexity": "simple",
"files_changed": ["src/auth.py", "tests/test_auth.py"],
"test_files_changed": ["tests/test_auth.py"],
"additions": 87,
"deletions": 12,
"overlap_with_pr_numbers": []
},
{
"number": 47,
"title": "Refactor database layer",
"branch": "feature/db-refactor",
"complexity": "needs_check",
"files_changed": ["src/db.py", "src/auth.py", "tests/test_db.py"],
"test_files_changed": ["tests/test_db.py"],
"additions": 165,
"deletions": 45,
"overlap_with_pr_numbers": [42]
}
],
"ci_blocked_prs": [
{"number": 99, "title": "Broken CI PR", "reason": "CI failing: 1 failed, 0 in-progress"}
],
"review_blocked_prs": [
{"number": 88, "title": "Needs changes PR", "reason": "2 unresolved CHANGES_REQUESTED review(s)"}
]
}
pr_count reflects the number of eligible PRs (i.e., ${#ELIGIBLE_PRS[@]}).
5b. Human-readable analysis plan: {{AUTOSKILLIT_TEMP}}/merge-prs/pr_analysis_plan_{ts}.md
This file is named *_plan_*.md so audit-impl can discover it as the baseline specification.
# PR Analysis: Integration into {base_branch}
**Date:** {YYYY-MM-DD}
**Base Branch:** {base_branch}
**Integration Branch:** pr-batch/pr-merge-YYYYMMDD-HHMMSS
**PRs Analyzed:** {count}
## Merge Order
1. PR #{number} — "{title}" (complexity: simple)
2. PR #{number} — "{title}" (complexity: needs_check)
...
## Excluded PRs
### CI-Blocked ({ci_blocked_count})
| PR | Title | Reason |
|----|-------|--------|
| #{number} | {title} | CI failing: 1 failed, 0 in-progress |
### Review-Blocked ({review_blocked_count})
| PR | Title | Reason |
|----|-------|--------|
| #{number} | {title} | 2 unresolved CHANGES_REQUESTED review(s) |
## File Overlap Matrix
| PR | Files | Overlaps With |
|----|-------|---------------|
| #{number} | {file list} | None |
| #{number} | {file list} | PR #{number} (src/auth.py) |
## Per-PR Assessment
### PR #{number}: "{title}"
- **Branch:** {branch}
- **Complexity:** simple / needs_check
- **Rationale:** {why this complexity tag was assigned}
- **Key files:** {list}
- **Risk notes:** {any concerns}
{If requirements_section is non-empty, include this block so reviewers can trace intent:}
#### Requirements
{requirements_section from PR body}
{repeat for each PR}
## Integration Strategy
{2–3 sentences describing the overall merge strategy and key risk areas}
- **If `QUEUE_MODE = true`**: PR order sourced from GitHub merge queue (position ordering). File overlap analysis skipped.
Verify:
pr_order_{ts}.json is valid JSON and parseablebatch_branch field is setReport to terminal:
{{AUTOSKILLIT_TEMP}}/merge-prs/
├── pr_order_{ts}.json # Machine-readable manifest (captured by recipe)
└── pr_analysis_plan_{ts}.md # Human-readable analysis (discovered by audit-impl)
After writing all output files and printing the terminal report, emit the following structured output tokens as the very last lines of your text output:
IMPORTANT: Emit the structured output tokens as literal plain text with no markdown formatting on the token names. Do not wrap token names in
**bold**,*italic*, or any other markdown. The adjudicator performs a regex match on the exact token name — decorators cause match failure.
pr_order_file = {absolute_path_to_pr_order_json}
analysis_file = {absolute_path_to_pr_analysis_plan_md}
batch_branch = {batch_branch_name}
pr_count = {eligible_pr_count}
simple_count = {simple_pr_count}
needs_check_count = {needs_check_pr_count}
ci_blocked_count = {ci_blocked_pr_count}
review_blocked_count = {review_blocked_pr_count}
queue_mode = {queue_mode} # true when merge queue has ≥1 MERGEABLE entry; false otherwise
/autoskillit:merge-pr — Merges individual PRs from this skill's ordered list/autoskillit:make-plan — Called for complex PRs that need conflict resolution plans/autoskillit:audit-impl — Receives {{AUTOSKILLIT_TEMP}}/merge-prs/ as plans_inputdevelopment
Generate YAML recipes for .autoskillit/recipes/. Use when user says "make script skill", "generate script", "script a workflow", "write a script", "create a script", "new recipe", "write a pipeline", or when loaded by other skills for script formatting.
data-ai
Create Uncertainty Representation visualization planning spec showing error bar definitions, distribution-aware alternatives, and multi-seed variance protocols. Statistical lens answering "How is uncertainty honestly represented?"
data-ai
Create Temporal Dynamics visualization planning spec showing axis scaling (linear vs log), smoothing disclosure, epoch/step alignment, run aggregation (mean + variance bands), early-stopping markers, and wall-clock vs step-count x-axis. Temporal lens answering "Are training dynamics shown clearly and honestly?"
data-ai
Create Narrative Story Arc visualization planning spec showing visual consistency across the report (same color = same model everywhere), logical figure progression, redundant figure detection, and narrative dependency between figures. Narrative lens answering "Do the figures tell a coherent story across the report?"