# Promote To Main

> Promote integration to main with comprehensive changelog and PR creation. Use when user says "promote to main", "open promotion PR", "integration to main", or "create release PR to main". Runs pre-flight checks, change inventory, architecture diagrams, and creates a rich PR with release notes and traceability.

- Skill: `talont-org/promote-to-main` (Agent Skill)
- Install (CLI): `npx skillmds@latest add talont-org/promote-to-main`
- Raw SKILL.md: https://api.skillmd.com/api/skills/talont-org/promote-to-main/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: TalonT-Org (https://skillmd.com/u/talont-org)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/talont-org/promote-to-main

---


# Promote to Main

Orchestrate the integration-to-main promotion workflow. This skill discovers everything
that changed on integration since it diverged from main, runs pre-flight quality checks,
performs change inventory, generates architecture diagrams, synthesizes release notes,
and creates a comprehensive promotion PR.

## Arguments

```
/autoskillit:promote-to-main [batch_branch] [base_branch] [--dry-run]
```

- `batch_branch` (optional) — source branch to promote. Defaults to `integration`.
- `base_branch` (optional) — target branch. Defaults to `main`.
- `--dry-run` — generate the full PR body without creating a PR.

## When to Use

- When the develop branch is stable and ready to be promoted to main
- After feature PRs, bug fixes, and cleanup work have been collected and tested on develop
- This is the final gate before changes land on main

## Critical Constraints

**NEVER:**
- Create files outside `.autoskillit/temp/promote-to-main/`
- Modify any source code — this skill is read-only analysis + PR creation
- Fail silently if `gh` is unavailable — output `pr_url = ` (empty) and exit successfully
- Push or merge anything — this skill only creates the PR
- Skip pre-flight checks — a failing pre-flight must block PR creation
- Use the Bash tool for file reads — use Read, Grep, Glob for all codebase inspection
- Use `gh pr create --body` inline — always use `--body-file`

**ALWAYS:**
- Run ALL pre-flight checks before any analysis work
- Check `gh auth status` before any GitHub operations
- Output `pr_url = <url>` as a structured token (empty string when GitHub unavailable or dry-run)
- Output `verdict = <value>` as a structured token
- Carry forward ALL `Closes #N`, `Fixes #N`, and `Resolves #N` references from merged PR bodies
- Use `gh pr create --body-file` (never inline body via `--body`)
- `pr_body_path` must be an absolute path (prepend CWD)
## Workflow

### Phase 0: Setup

#### Step 0.1: Parse Arguments

Parse optional positional arguments and flags:
- `batch_branch` — default `"develop"` if absent or empty
- `base_branch` — default `"main"` if absent or empty
- `dry_run` — `true` if `--dry-run` present in ARGUMENTS

Validate that both branches exist locally:
```bash
git rev-parse --verify {batch_branch} 2>/dev/null
git rev-parse --verify {base_branch} 2>/dev/null
```
If either fails, try fetching:
```bash
git fetch origin {branch}:{branch} 2>/dev/null
```
If still missing, print error to stderr and exit 1.

#### Step 0.2: Compute Divergence Point

```bash
git merge-base {base_branch} {batch_branch}
```

Store as `merge_base_sha`.

Get commit count and timestamp:
```bash
git rev-list --count {merge_base_sha}..{batch_branch}
git show -s --format=%cI {merge_base_sha}
```
Store as `commit_count` and `merge_base_date`.

#### Step 0.3: Retrieve Token Summary from Session Logs

Determine the pipeline working directory and self-retrieve token telemetry from disk.
This aggregates token usage across all constituent PR sessions that ran in this pipeline
working directory.

```bash
mkdir -p .autoskillit/temp/promote-to-main
python3 - <<'EOF' > .autoskillit/temp/promote-to-main/token_summary.md 2>/dev/null || true
import json, pathlib, sys
from autoskillit.pipeline.tokens import DefaultTokenLog
from autoskillit.pipeline.telemetry_fmt import TelemetryFormatter
from autoskillit.execution.session_log import resolve_log_dir

cfg_path = pathlib.Path(".autoskillit") / "temp" / ".hook_config.json"
kitchen_id = ""
if cfg_path.exists():
    _cfg = json.loads(cfg_path.read_text())
    if isinstance(_cfg, dict):
        kitchen_id = _cfg.get("kitchen_id") or _cfg.get("pipeline_id", "")

log_root = resolve_log_dir("")
tl = DefaultTokenLog()
n = tl.load_from_log_dir(log_root, kitchen_id_filter=kitchen_id)
if n == 0:
    sys.exit(0)
steps = tl.get_report()
total = tl.compute_total()
print(TelemetryFormatter.format_token_table(steps, total))
EOF
```

- If `.autoskillit/temp/promote-to-main/token_summary.md` is non-empty, set `TOKEN_SUMMARY_CONTENT` to its
  contents and embed it in the PR body under `## Token Usage Summary`.
- If empty or absent (standalone invocation, no pipeline sessions in this cwd), omit the
  section — graceful degradation with no error.

### Phase 1: Pre-flight Checks (parallel, blocking)

Spawn three parallel Task subagents (model: sonnet) to validate promotion readiness.
All three must pass before analysis proceeds. If any fails, report the failure clearly
and exit 1. Do NOT create a PR when pre-flight fails.
#### Subagent 1A: CI and Branch Status

Check:
1. CI is green on the integration branch — run `gh pr checks` for any open PR from
   integration, or `gh run list --branch {batch_branch} --workflow tests.yml --limit 1 --json conclusion`
2. The integration branch is not behind base — run `git rev-list --count {batch_branch}..{base_branch}`
   to check if base has commits not in integration (if > 0, warn that a rebase may be needed)
3. No open PRs targeting integration with failing CI — `gh pr list --base {batch_branch} --state open --json number,title,statusCheckRollup`

Return JSON:
```json
{
  "ci_status": "pass|fail|unknown",
  "ci_details": "description of CI state",
  "behind_base_by": 0,
  "open_prs_with_failing_ci": [],
  "pass": true
}
```

#### Subagent 1B: Version Consistency

Check:
1. `pyproject.toml` version matches `src/autoskillit/.claude-plugin/plugin.json` version
2. `uv lock --check` passes (lockfile consistent)
3. The integration branch version is ahead of the base branch version

Return JSON:
```json
{
  "pyproject_version": "X.Y.Z",
  "plugin_version": "X.Y.Z",
  "versions_match": true,
  "lockfile_consistent": true,
  "version_ahead_of_base": true,
  "pass": true
}
```

#### Subagent 1C: Outstanding Review Items

Check:
1. No open PRs targeting integration with `CHANGES_REQUESTED` reviews —
   `gh pr list --base {batch_branch} --state open --json number,title,reviews`
2. No `in-progress` labeled issues that might indicate incomplete work —
   `gh issue list --label in-progress --state open --json number,title`

Return JSON:
```json
{
  "prs_with_changes_requested": [],
  "in_progress_issues": [],
  "pass": true
}
```

**Pre-flight gate:** If any subagent returns `"pass": false`, report all failures in a
clear summary table and exit 1 without proceeding to analysis. If `ci_status` is `unknown`,
treat as a warning (non-blocking) and note it in the report.

### Phase 2: Change Inventory (parallel subagents)

Spawn four parallel Task subagents (model: sonnet).

#### Subagent 2A: Commit Categorization

Receive the output of:
```bash
git log {merge_base_sha}..{batch_branch} --format="%H %s"
```

Categorize each commit into exactly one category based on its subject line:
- `rectify` — subject contains "Rectify:" or "fix:" or "bugfix" (case-insensitive)
- `feature` — subject contains "Implementation Plan:", "feat:", "Add ", or introduces new capability
- `infra` — subject contains CI, workflow, config, build, or infrastructure changes
- `test` — subject only touches test files (infer from "test" in subject or `tests/` paths)
- `docs` — subject contains "docs:", "README", or documentation-only changes

Extract PR numbers from patterns like `(#123)` in commit subjects.

Return JSON:
```json
{
  "categories": {
    "rectify": [{"sha": "abc123", "title": "...", "pr_number": 495}],
    "feature": [],
    "infra": [],
    "test": [],
    "docs": []
  },
  "totals": {"rectify": 14, "feature": 13, "infra": 3, "test": 1, "docs": 1},
  "category_summary": "14 fixes, 13 features, 3 infra"
}
```

#### Subagent 2B: PR Discovery and Issue Linkage

Run:
```bash
gh pr list --base {batch_branch} --state merged --limit 200 --json number,title,author,mergedAt,body,headRefName,additions,deletions,labels,url
```

Filter to PRs merged after `merge_base_date`. If empty, fall back to commit-subject
discovery:
```bash
git log {merge_base_sha}..{batch_branch} --oneline --grep="(#" --format="%s"
```

For each PR, extract `Closes|Fixes|Resolves #N` references (case-insensitive).
Deduplicate across all PRs. For each linked issue number, fetch details:
```bash
gh issue view {number} --json number,title,state,url,labels 2>/dev/null
```

Build a traceability matrix: for each issue, identify which PR(s) close it.

Return JSON:
```json
{
  "prs": [{"number": 1, "title": "...", "author": "...", "labels": [], "url": "...", "additions": 0, "deletions": 0}],
  "closing_refs": ["Closes #42", "Fixes #50"],
  "linked_issue_numbers": [42, 50],
  "issue_details": [{"number": 42, "title": "...", "state": "OPEN", "url": "...", "labels": ["recipe:implementation"]}],
  "traceability": [{"issue_number": 42, "issue_title": "...", "pr_numbers": [491], "recipe_route": "implementation"}]
}
```

#### Subagent 2C: File Lifecycle Tracking

Run:
```bash
git diff --name-only {base_branch}..{batch_branch}
git diff --diff-filter=A --name-only {base_branch}..{batch_branch}
git diff --diff-filter=M --name-only {base_branch}..{batch_branch}
git diff --diff-filter=D --name-only {base_branch}..{batch_branch}
git diff --diff-filter=R --name-only {base_branch}..{batch_branch}
git diff --stat {base_branch}..{batch_branch} | tail -1
```

Return JSON:
```json
{
  "changed_files": ["..."],
  "new_files": ["..."],
  "modified_files": ["..."],
  "deleted_files": ["..."],
  "renamed_files": ["..."],
  "diff_stat_summary": "185 files changed, 10013 insertions(+), 1164 deletions(-)"
}
```

#### Subagent 2D: Migration and Schema Detection

Scan the diff for changes that require manual attention on merge. Check:
1. Changes to `src/autoskillit/migrations/` — new migration YAML notes
2. Changes to `src/autoskillit/recipe/schema.py` — recipe schema modifications
3. Changes to `pyproject.toml` — dependency additions/removals/version bumps
4. Changes to `src/autoskillit/config/defaults.yaml` — config schema changes
5. Changes to `src/autoskillit/hooks/hooks.json` — hook registration changes
6. Changes to `src/autoskillit/.claude-plugin/plugin.json` — plugin metadata changes
7. Changes to `.github/workflows/` — CI workflow modifications

For each detected change, provide a brief description of what changed and why it
might need attention.

Return JSON:
```json
{
  "migration_changes": ["Added migration note for v0.3.2"],
  "schema_changes": ["New RecipeStep field: optional"],
  "dependency_changes": ["Added httpx>=0.27"],
  "config_changes": ["New config key: github.staged_label"],
  "hook_changes": ["New hook: pretty_output PostToolUse"],
  "ci_changes": ["Updated tests.yml matrix"],
  "attention_required": true,
  "attention_summary": "Brief description of what needs human review"
}
```

### Phase 3: Architecture Diagrams

#### Step 3.1: Select Arch-Lens Lenses

Spawn a Task subagent (model: sonnet) with the `changed_files` list and this lens menu:

```
c4-container, concurrency, data-lineage, deployment, development,
error-resilience, module-dependency, operational, process-flow,
repository-access, scenarios, security, state-lifecycle
```

Return 1-3 lens names. Apply the same selection criteria as open-pr:

**Development lens guard:** Only select `development` if at least one changed file matches:
`pyproject.toml`, `Taskfile*`, `conftest.py`, `.github/workflows/*`, `Makefile`,
`setup.cfg`, `setup.py`, `tox.ini`, `noxfile.py`, or files under `ci/`.

For a promotion PR, prefer lenses that show the broadest architectural impact:
- `module-dependency` if changes span multiple packages
- `process-flow` if workflow routing or state transitions changed
- `c4-container` if new services, tools, or integrations were added

#### Step 3.2: Generate Arch-Lens Diagrams

For each selected lens, follow this exact sequence:

**CRITICAL:** Do NOT output any prose status text between lens iterations.
After completing all sub-steps for one lens, immediately begin sub-step 1 for the
next lens.

**1. Write the PR context to a file using the Write tool:**

- **Path:** `.autoskillit/temp/promote-to-main/pr_arch_lens_context_{YYYY-MM-DD_HHMMSS}.md`
- **Content:**

```markdown
# PR Context — Integration to Main Promotion

This diagram is for a promotion PR merging the integration branch into main. Focus on the areas of the codebase affected by all accumulated changes. Do not create a generic whole-project diagram.

## New files (use star prefix on these nodes):
{list of new_files, or "None"}

## Modified files (use bullet prefix on these nodes):
{list of modified_files, or "None"}

## Deleted files:
{list of deleted_files, or "None"}

## Instructions:
- Focus exploration and the diagram on the architectural areas these files belong to
- Use star prefix on nodes representing new files/components
- Use bullet prefix on nodes representing modified files/components
- Mark deleted components with strikethrough or a X prefix
- Leave unchanged components unmarked (include only if needed for context/connectivity)
- This is a promotion PR — show the cumulative architectural impact of all changes
```

**2. Immediately call the Skill tool** to load the arch-lens skill (e.g.,
`/autoskillit:arch-lens-module-dependency`).

**3. Follow the loaded skill's instructions** to generate the diagram.

Read the output from `.autoskillit/temp/arch-lens-{lens-name}/` and extract the mermaid block(s).

Validate: if the block contains at least one star marker or bullet marker for
new/modified nodes, add to `validated_diagrams`. Otherwise discard.

### Phase 4: Compose PR + Create PR

#### Step 4.1: Release Notes Synthesis

Spawn one Task subagent (model: sonnet) with results from Phase 2 ONLY (no domain
analysis or quality assessment data).
The subagent receives:
- Commit categorization from Subagent 2A
- PR and issue data from Subagent 2B
- File lifecycle from Subagent 2C
- Migration/schema detection from Subagent 2D

Return JSON:
```json
{
  "executive_summary": "3-5 sentence narrative of what this promotion brings, focused on change themes",
  "highlights": ["top 3-5 most significant changes"],
  "release_notes_md": "### New Features\n- ...\n### Bug Fixes\n- ...\n### Infrastructure\n...\n### Breaking Changes\n...\n### Attention Required\n..."
}
```

#### Step 4.2: Write PR Body

Write to `.autoskillit/temp/promote-to-main/pr_body_{YYYY-MM-DD_HHMMSS}.md`
(relative to the current working directory).

Sections in order:
1. `## Promotion: {batch_branch} to {base_branch}` — executive summary + stats (`diff_stat_summary`, `commit_count`, PR count)
2. `### Highlights` — from synthesis
3. `## Release Notes` — from synthesis
4. `## Merged PRs` — table (PR, Title, Author, Labels) from Subagent 2B
5. `## Linked Issues` — table (Issue, Title, Status, Action) from Subagent 2B
6. `## Attention Required` — from Subagent 2D (only if `attention_required=true`)
7. `## Architecture Impact` — validated mermaid diagrams from Phase 3
8. Closing references (`Closes #N` lines from Subagent 2B, one per line)
9. `## Token Usage Summary` — if TOKEN_SUMMARY_CONTENT non-empty
10. Footer: `<sub>Generated with Claude Code via AutoSkillit</sub>`

#### Step 4.3: Create PR

If `dry_run` is true: skip to Output section.

Check GitHub availability:
```bash
gh auth status 2>/dev/null
```
If exit code non-zero: output `pr_url = ` and exit successfully.

Construct PR title using actual branch names:
`Promote {batch_branch} to {base_branch} ({len(prs)} PRs, {len(linked_issue_numbers)} issues, {category_summary})`

```bash
gh pr create \
  --base {base_branch} \
  --head {batch_branch} \
  --title "{pr_title}" \
  --body-file .autoskillit/temp/promote-to-main/pr_body_{timestamp}.md
```

Capture the PR URL as `pr_url`.

Add label (optional, continue if fails):
```bash
gh pr edit {pr_url} --add-label "promotion" 2>/dev/null || true
```

### Output

Always emit these structured output tokens as the final lines:

```
pr_body_path = {absolute path to .autoskillit/temp/promote-to-main/pr_body_{timestamp}.md}
pr_url = {pr_url, empty if dry-run or gh unavailable}
verdict = {created|dry_run|preflight_failed}
category_summary = {e.g., "14 fixes, 13 features, 3 infra"}
```

