# Issue Work

> Automate GitHub issue workflow - select issue, create branch, implement, build, test, and create PR.

- Skill: `kcenon/issue-work` (Agent Skill, multi-file: 17 files)
- Install (CLI): `npx skillmds@latest add kcenon/issue-work`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kcenon/issue-work/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: kcenon (https://skillmd.com/u/kcenon)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/kcenon/issue-work

---


# Issue Work Command

Automate GitHub issue workflow with project name as argument.

## Usage

```
/issue-work                                       # Batch: all repos, all open issues
/issue-work vi_slam                               # Batch: all open issues in vi_slam
/issue-work vi_slam 21                            # Single: work on issue #21
/issue-work vi_slam 21 --org mycompany           # Explicit organization
/issue-work mycompany/vi_slam 21                 # Full repo path format
/issue-work vi_slam 21 --solo                    # Force solo mode (sequential)
/issue-work vi_slam 21 --team                    # Force team mode (implementer + tester)
/issue-work --org mycompany                      # Batch: all repos in org
/issue-work vi_slam --limit 5                    # Batch: top 5 priority issues
/issue-work vi_slam --dry-run                    # Preview batch plan only
/issue-work vi_slam --inline                     # Batch: process items in the parent context (legacy)
```

## Arguments

- `[project-name]`: Project name or full repository path (optional)
  - Format 1: `<project-name>` - auto-detect organization from git remote
  - Format 2: `<project-name> --org <organization>` - explicit organization
  - Format 3: `<organization>/<project-name>` - full repository path
  - If omitted: **Batch mode** — discover all user repos and process all open issues

- `[issue-number]`: GitHub issue number (optional)
  - If provided: Work on the specified issue (single-item mode)
  - If omitted with project: **Batch mode** — process all open issues in the project
  - If omitted without project: **Batch mode** — process all open issues across all repos

- `[--solo|--team]`: Execution mode override (optional)
  - `--solo` — Force solo mode for all items (single agent, sequential workflow)
  - `--team` — Force team mode for all items (implementer + tester agents in parallel)
  - If omitted in single-item mode: auto-recommend based on issue size, then ask user
  - If omitted in batch mode: auto-decide per item using weighted scoring (no per-item prompt)

- `[--limit N]`: Maximum number of items to process in batch mode (default: 5, max: 10)
  - Values above 10 require `--force-large` to acknowledge rule drift risk. Empirically, drift becomes visible around items 15-25 in long batches; the conservative default keeps batches inside the safe zone.

- `[--force-large]`: Allow `--limit > 10`. Required to bypass the safe-batch cap.

- `[--no-confirm]`: Skip the chunked confirmation gate fired every 5 items in batch mode. Intended for CI-driven or fully unattended batches; interactive sessions should leave it off so the gate can serve as both a user-control checkpoint and an attention refresh for accumulated context.

- `[--auto-restart]`: Force a session restart every `CONFIRM_INTERVAL` items instead of showing the interactive chunked gate. The batch writes `.claude/resume.md` using the Batch Workflow Resume Format and exits cleanly; a fresh `claude` session picks up the next item from the resume file. Use for long unattended batches where a full process-level attention reset per chunk matters more than human confirmation. Ignored in single-item mode.

- `[--no-restart]`: Suppress the forced restart. When combined with `--auto-restart`, the batch falls back to the interactive chunked gate. Meaningful primarily as a defensive flag in scripts that want to guarantee no session exit even if `--auto-restart` is set elsewhere (aliases, wrappers, or a future default change).

- `[--dry-run]`: Show batch plan only, do not execute

- `[--inline]`: Process each batch item in the parent conversation context instead of delegating to a fresh subagent.
  - **Default (omitted)**: Each batch item is handled by a fresh `general-purpose` Agent. The parent keeps only the queue state and a short per-item summary; gh outputs, build logs, and file reads live inside the subagent and are discarded on completion. This is the preferred mode for batches >3 items because rule compliance at item 30 looks like item 1.
  - **With `--inline`**: The parent executes Solo/Team workflow directly for every item. Lower token overhead (~10-15% savings) but accumulated tool results cause rule drift around items 15-25. Use for tiny batches (≤3 items) or when inter-item context is actually useful (e.g., fixing related regressions).
  - Ignored in single-item mode.

- `[--priority <level>]`: Filter batch to this priority level and above
  - Levels: `critical`, `high`, `medium`, `low`, `all` (default: `all`)

- `[--wait-on-timeout]`: On the CI-polling timeout (Step 9), ask the user whether to keep waiting instead of taking the default action. **Default (omitted)**: when the 10-minute polling limit is reached with runs still pending, leave the PR open and print a resume command (`pr-work <PR_NUM>`) rather than blocking on a prompt. Pass this flag only when you want the interactive "wait longer?" question back.

## Argument Parsing

Parse `$ARGUMENTS` and extract project, organization, issue number, and batch flags:

```bash
ARGS="$ARGUMENTS"
ISSUE_NUMBER="" PROJECT="" ORG="" EXEC_MODE=""
BATCH_MODE="single"  BATCH_LIMIT=5  DRY_RUN=false  PRIORITY_FILTER="all"  FORCE_LARGE=false  NO_CONFIRM=false  INLINE_MODE=false  AUTO_RESTART=false  NO_RESTART=false  WAIT_ON_TIMEOUT=false
MAX_LIMIT=10
CONFIRM_INTERVAL=5

# Extract flags
if [[ "$ARGS" == *"--solo"* ]]; then EXEC_MODE="solo"; ARGS=$(echo "$ARGS" | sed 's/--solo//g'); fi
if [[ "$ARGS" == *"--team"* ]]; then EXEC_MODE="team"; ARGS=$(echo "$ARGS" | sed 's/--team//g'); fi
if [[ "$ARGS" == *"--dry-run"* ]]; then DRY_RUN=true; ARGS=$(echo "$ARGS" | sed 's/--dry-run//g'); fi
if [[ "$ARGS" == *"--force-large"* ]]; then FORCE_LARGE=true; ARGS=$(echo "$ARGS" | sed 's/--force-large//g'); fi
if [[ "$ARGS" == *"--no-confirm"* ]]; then NO_CONFIRM=true; ARGS=$(echo "$ARGS" | sed 's/--no-confirm//g'); fi
if [[ "$ARGS" == *"--no-restart"* ]]; then NO_RESTART=true; ARGS=$(echo "$ARGS" | sed 's/--no-restart//g'); fi
if [[ "$ARGS" == *"--auto-restart"* ]]; then AUTO_RESTART=true; ARGS=$(echo "$ARGS" | sed 's/--auto-restart//g'); fi
if [[ "$ARGS" == *"--inline"* ]]; then INLINE_MODE=true; ARGS=$(echo "$ARGS" | sed 's/--inline//g'); fi
if [[ "$ARGS" == *"--wait-on-timeout"* ]]; then WAIT_ON_TIMEOUT=true; ARGS=$(echo "$ARGS" | sed 's/--wait-on-timeout//g'); fi
if [[ "$ARGS" =~ --limit[[:space:]]+([0-9]+) ]]; then BATCH_LIMIT="${BASH_REMATCH[1]}"; ARGS=$(echo "$ARGS" | sed -E 's/--limit[[:space:]]+[0-9]+//g'); fi
if [[ "$ARGS" =~ --priority[[:space:]]+(critical|high|medium|low|all) ]]; then PRIORITY_FILTER="${BASH_REMATCH[1]}"; ARGS=$(echo "$ARGS" | sed -E 's/--priority[[:space:]]+\w+//g'); fi

# Hard cap on batch size to mitigate rule drift in long batches.
# Drift becomes empirically visible around items 15-25; default 5 keeps the
# operator inside the safe zone, and bypassing requires explicit acknowledgment.
if (( BATCH_LIMIT > MAX_LIMIT )) && [[ "$FORCE_LARGE" != "true" ]]; then
    echo "Error: --limit ${BATCH_LIMIT} exceeds safe cap of ${MAX_LIMIT}." >&2
    echo "Long batches risk rule drift around items 15-25." >&2
    echo "Either split the batch into smaller runs or pass --force-large to override." >&2
    exit 1
fi

# Extract issue number if present (numeric argument)
if [[ "$ARGS" =~ [[:space:]]([0-9]+)([[:space:]]|$) ]]; then
    ISSUE_NUMBER="${BASH_REMATCH[1]}"
    ARGS=$(echo "$ARGS" | sed -E "s/[[:space:]]+${ISSUE_NUMBER}([[:space:]]|$)/ /g")
fi
ARGS=$(echo "$ARGS" | xargs)

# Helper: resolve ORG/PROJECT from remaining ARGS
resolve_org_project() {
    if [[ "$ARGS" == *"--org"* ]]; then
        PROJECT=$(echo "$ARGS" | awk '{print $1}')
        ORG=$(echo "$ARGS" | sed -n 's/.*--org[[:space:]]*\([^[:space:]]*\).*/\1/p')
    elif [[ "$ARGS" == *"/"* ]]; then
        ORG=$(echo "$ARGS" | cut -d'/' -f1 | xargs)
        PROJECT=$(echo "$ARGS" | cut -d'/' -f2 | xargs)
    else
        PROJECT="$ARGS"
        cd "$PROJECT" 2>/dev/null || { echo "Error: Project directory not found: $PROJECT"; exit 1; }
        ORG=$(git remote get-url origin 2>/dev/null | sed -E 's|.*[:/]([^/]+)/[^/]+\.git$|\1|' | sed -E 's|.*[:/]([^/]+)/[^/]+$|\1|')
    fi
}

# Determine batch mode and resolve org/project
if [[ -z "$ARGS" && -z "$ISSUE_NUMBER" ]]; then
    BATCH_MODE="cross-repo"
    if [[ "$ARGS" == *"--org"* ]]; then
        ORG=$(echo "$ARGS" | sed -n 's/.*--org[[:space:]]*\([^[:space:]]*\).*/\1/p')
    fi
elif [[ -n "$ARGS" && -z "$ISSUE_NUMBER" ]]; then
    BATCH_MODE="single-repo"
    resolve_org_project
else
    BATCH_MODE="single"
    resolve_org_project
fi
```

- Repository: `https://github.com/$ORG/$PROJECT` (single/single-repo modes)
- Source path: `./$PROJECT`
- Issue Number: `$ISSUE_NUMBER` (empty for batch modes)
- Batch Mode: `$BATCH_MODE` (`single`, `single-repo`, or `cross-repo`)

## Instructions

**Gates are tier-independent.** The triage gate (Step 1 / T-1), the isolated
workspace lifecycle (Step 3 clone and the Step 13 teardown), and the pre-PR
readiness gate (Step 7.5) are MANDATORY and run in every tier, including
`light`. The frontmatter `tiers.<name>.deep_checks` and `ref_docs` only
control which OPTIONAL reference docs auto-load and whether the extra deep
checks run — they never disable these three gates.

### Mode Routing

- If `$BATCH_MODE == "single-repo"` or `$BATCH_MODE == "cross-repo"` → Execute **Batch Mode Instructions** below
- If `$BATCH_MODE == "single"` → Execute **Phase 0: Execution Mode Selection** (skip Batch Mode)

---

## Batch Mode Instructions

See `reference/batch-mode.md` for the complete batch mode workflow including discovery, priority sorting, plan approval, and sequential execution.

**Batch-only behaviors** (do not apply in single-item mode):
- **Subagent delegation by default** (B-4): each item is dispatched to a fresh `general-purpose` Agent so it starts with an unpolluted attention pool. The parent retains only `{item_id, status, requested, root, active, pr_url, ci_conclusion}` per item. Pass `--inline` to fall back to the legacy single-context loop.
- **Per-item rule reminder** (B-4.0): a 5-line invariant block is emitted as a fresh tool result before each item so language/CI/attribution rules stay in the recent attention window. In delegated mode this reminder is embedded in the subagent prompt; in `--inline` mode it is emitted directly in the parent context.
- **No `@load: reference/...` inside the per-item loop**: keep the inline reminder as the most recent context anchor.
- **Chunked confirmation gate** (B-4.1): user confirmation prompt every 5 items, bypassable with `--no-confirm`. When `--auto-restart` is set (and `--no-restart` is not), the gate is replaced by a forced session restart that writes `.claude/resume.md` and exits; a fresh `claude` session resumes from the next item.

---

### Phase 0: Execution Mode Selection (Single-Item Mode)

Determine whether to run in Solo mode (single agent, sequential) or Team mode (implementer + tester agents in parallel).

#### 0-1. If `--solo` or `--team` flag was provided

Skip mode selection — use `$EXEC_MODE` directly.

#### 0-2. If no flag was provided (interactive selection)

First, fetch issue information for size estimation (after the Issue Triage Gate
in Step 1 resolves and claims the active issue via `$ISSUE_NUMBER`):

```bash
ISSUE_INFO=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT \
  --json title,body,labels -q '{title: .title, body: .body, labels: [.labels[].name]}')
```

Auto-recommend based on issue size:

| Signal | Solo (Recommended) | Team (Recommended) |
|--------|-------------------|-------------------|
| Size label | `size/XS`, `size/S` | `size/M`, `size/L`, `size/XL` |
| Description length | < 500 chars | > 500 chars |
| Acceptance criteria | < 3 items | 4+ items |
| Subtask references | None | "Part of", checklist items |

**Decisive signals → apply silently.** When the signals are unambiguous (all point the same direction — e.g. `size/XS`/`size/S` → solo, `size/L`/`size/XL` → team), set `$EXEC_MODE` to the recommended mode WITHOUT asking and print a one-line notice, e.g. `[Mode: solo — XS issue; pass --team to override]` (or `[Mode: team — L issue; pass --solo to override]`). The user can still override with the `--solo`/`--team` flags handled in 0-1.

**Conflicting signals → ask.** Only when signals genuinely point in different directions (e.g. `size/S` label but 4+ acceptance criteria and "Part of" references) use `AskUserQuestion` to present the choice:

- **Question**: "Issue #$ISSUE_NUMBER — <title> (<estimated-size>, mixed signals). Which execution mode?"
- **Header**: "Mode"
- **Options**:
  1. Recommended mode with "(Recommended)" suffix
  2. The other mode
- **Description for Solo**: "Sequential execution by a single agent. Lower token cost. Best for XS-S issues."
- **Description for Team**: "3-team parallel: dev + reviewer + doc-writer with review feedback loop. Higher quality for M+ issues. ~2x token cost."

Store the result in `$EXEC_MODE` (solo | team).

#### 0-3. Mode Routing

- If `$EXEC_MODE == "solo"` → Execute **Solo Mode Instructions** (Steps 1-13 below)
- If `$EXEC_MODE == "team"` → Execute **Team Mode Instructions** (after Solo Mode section)

---

## Solo Mode Instructions

Execute the following workflow for the specified project:

### 1. Issue Triage Gate (state machine)

Issue selection and size evaluation are handled by the shared triage state
machine — a single deterministic, idempotent gate that solo, team, and batch
modes all route through **before** any repository is cloned or branch created.
The contract (states, outcome schema, comment fingerprint rule, eligibility
predicate, sort key) lives in `reference/triage-state-machine.md`; the reference
implementation is `scripts/triage.sh`.

Run the gate, then branch on its structured outcome:

```bash
TRIAGE_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/triage.sh \
  --repo "$ORG/$PROJECT" ${ISSUE_NUMBER:+--issue "$ISSUE_NUMBER"} ${PLAN_FILE:+--plan-file "$PLAN_FILE"})

OUTCOME=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["outcome"])')
REQUESTED_ISSUE=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["requested"])')
ROOT_ISSUE=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["root"])')
ISSUE_NUMBER=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["active"])')
```

**Outcome routing** (the JSON `outcome` field is authoritative):

| `outcome` | Action |
|-----------|--------|
| `proceed` | An eligible issue was selected and claimed. `active` is the issue to work — continue to Step 3. |
| `decomposed` | The issue was oversized and had no eligible open child; children were created and one parent summary was posted. **Stop** — no clone, no branch. Report the decomposition. |
| `blocked` | The issue has an unresolved blocker (comment posted only if the blocker state changed), or the issue fetch failed identically three times. **Stop** — no clone, no branch. |
| `skipped` | The issue was closed, reassigned, or every child lost a claim race. **Stop.** |
| `failed` | Cycle/depth guard tripped, or `needs_plan` (oversized issue, no children, no `--plan-file`). A `needs_plan` reason means "design a plan and re-invoke with `--plan-file`", not a hard error. **Stop and report.** |

Every **Stop** row above still ends the invocation by printing the
`ISSUE_WORK_RESULT:` marker (see Output) as its last line — `status` matches
the `outcome` value, `requested`/`root` come from `$REQUESTED_ISSUE`/
`$ROOT_ISSUE` above, and `pr_url` is `null`.

Only `proceed` continues into code work. The other four outcomes are terminal
for this invocation and perform no repository side effects — this is what lets a
`blocked` or `decomposed` result be produced from a bare `gh` session (issue
#829 AC9). In batch mode, `decomposed`/`blocked`/`skipped`/`failed` are **not**
merge successes (see `reference/batch-mode.md` B-5).

**Decomposition plan**: when an oversized issue has no eligible open child, the
gate creates children from a plan file (one child title per line) supplied via
`--plan-file`. Design the sub-issue titles per the 5W1H template and the issue
splitting rules, write them to a temp file, and set `$PLAN_FILE` before invoking
the gate. Reconciliation is idempotent: a re-run creates only the missing
children and never posts a second summary (AC6).

### 3. Git Environment Setup

Git setup clones through the isolated-workspace stage (`scripts/workspace.sh`)
instead of an in-place checkout of `$PROJECT` — every run gets a private,
identity-verified clone rather than mutating a shared working copy. See
`reference/workspace-lifecycle.md` for the full contract (run-root layout,
manifest schema, identity-verification rule); this step only invokes it.

```bash
WORKSPACE_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/workspace.sh \
  --repo "$ORG/$PROJECT" --base "${TMPDIR:-/tmp}" --issue "$ISSUE_NUMBER")

WS_STATE=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["state"])')
```

**Outcome routing** (the JSON `state` field is authoritative):

| `state` | Action |
|---------|--------|
| `READY` | Capture `repo_dir`, `baseline`, `manifest` below and continue. |
| `REJECTED` | **Stop.** Report the `reason` (clone failure or origin identity mismatch). No branch, no code work. |

```bash
REPO_DIR=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["repo_dir"])')
BASELINE=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["baseline"])')
MANIFEST=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["manifest"])')
cd "$REPO_DIR"
```

Every step from here on (implementation, build, commit, push, PR) runs inside
`$REPO_DIR`, not the shared `$PROJECT` checkout. Branch-name derivation is
unchanged:

```bash
# Extract issue title for branch name
ISSUE_TITLE=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json title -q '.title')
# Convert to kebab-case (lowercase, replace spaces with hyphens)
SHORT_DESC=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g' | cut -c1-50)

# Determine branch type from issue labels
LABELS=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json labels -q '.labels[].name')
if echo "$LABELS" | grep -q "type/feature"; then
    BRANCH_TYPE="feat"
elif echo "$LABELS" | grep -q "type/bug"; then
    BRANCH_TYPE="fix"
elif echo "$LABELS" | grep -q "type/refactor"; then
    BRANCH_TYPE="refactor"
elif echo "$LABELS" | grep -q "type/docs"; then
    BRANCH_TYPE="docs"
else
    BRANCH_TYPE="feat"  # Default
fi

BRANCH_NAME="${BRANCH_TYPE}/issue-${ISSUE_NUMBER}-${SHORT_DESC}"
git checkout -b "$BRANCH_NAME"
```

### 4. Issue Assignment

```bash
gh issue edit <NUMBER> --repo $ORG/$PROJECT --add-assignee @me
```

### 5. Code Implementation

**Priority**: Start implementation immediately. Minimize upfront planning — analyze code
as you implement, not in a separate planning phase.

1. **Analyze existing code style**:
   - Check `.clang-format`, `.editorconfig` if present
   - Review existing file patterns and conventions

2. **Implement changes**:
   - Follow existing code style strictly
   - Keep changes minimal and focused
   - **Validate incrementally**: Build/test after each logical change, not after all changes

3. **Header file review** (C/C++ projects):
   - Verify all used symbols have corresponding #include
   - Add missing headers

4. **Commit per logical unit**:
   - Format: `type(scope): description`
   - **Language**: Follow the active `CLAUDE_CONTENT_LANGUAGE` policy resolved from `commit-settings.md` (default `english`; supports `korean_plus_english`, `exclusive_bilingual`, `any`). Do **not** assume English-only.
   - **Forbidden** (every policy): Claude/AI references, emojis, Co-Authored-By

### 6. Build and Test Verification

Follow the build verification workflow rule (`build-verification.md`) to select the
appropriate strategy based on expected build duration.

#### Toolchain Availability Check

Before running local builds, verify required toolchains are installed:

```bash
# Check availability — do NOT install without asking the user
command -v go &>/dev/null    # Go
command -v cargo &>/dev/null # Rust
command -v cmake &>/dev/null # C++
command -v npm &>/dev/null   # Node.js
```

**If toolchain is unavailable**: Skip local build verification and rely on CI.
Do NOT attempt to install toolchains without asking the user first.
Report what was verified locally vs what needs CI.

#### Strategy Selection

| Build System | Typical Duration | Strategy |
|-------------|-----------------|----------|
| `go build` / `cargo check` | < 30s | Inline (synchronous) |
| `cmake --build` / `gradle build` | 30s - 5min | Background + log polling |
| `ctest` / `pytest` (large suites) | 1 - 10min | Background + log polling |
| CI pipeline (`gh workflow run`) | 5min+ | CI log check |

#### Inline Strategy (short builds)

For builds expected under 30 seconds:

```
Bash(command="go build ./...", timeout=60000)
Bash(command="cargo check", timeout=60000)
```

#### Background + Log Polling Strategy (long builds)

For builds expected over 30 seconds:

1. Launch in background: `Bash(command="cmake --build build/ ...", run_in_background=true)`
2. Poll output every 10-15s: `TaskOutput(task_id="<id>", block=false, timeout=10000)`
3. Detect outcome: `Built target`/`Finished` = success, `error:`/`FAILED` = fix needed
4. Run tests with same pattern: `Bash(command="ctest --test-dir build/ ...", run_in_background=true)`

#### On Build/Test Failure

1. Read the error output from build logs
2. Categorize failure (compile error, linker error, test assertion, missing dependency)
3. Apply fix based on error pattern
4. Re-run build/test to verify fix
5. If persistent failure: create draft PR with failure log (see Error Handling)

Do NOT retry the same build without changes -- diagnose first.

### 7. Documentation Update

Update relevant documentation if applicable:
- README.md
- CHANGELOG.md
- API documentation
- Code comments for complex logic

Commit separately:
```
docs(scope): update documentation for <feature>
```

### 7.5. Pre-PR Readiness Gate and Documentation-to-Issue Gap Audit

**Mandatory.** This gate runs **after** the implementation and documentation are
committed to the feature branch and **before** any push or PR. It has two
halves — a deterministic git-state gate (`scripts/pre-pr-gate.sh`) and an
agent-driven documentation-to-issue gap audit. The full contract (outcome table,
develop-refresh rules, conflict rule, base-movement retry rule, gap-ledger
schema, dispositions, Korean-PR requirement) lives in
`reference/pre-pr-readiness.md`; do not duplicate it here.

Run the git-state gate from the feature-branch checkout (the `$PROJECT` directory
entered in Step 3), then branch on its structured outcome:

```bash
PREPR_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/pre-pr-gate.sh \
  --repo "$ORG/$PROJECT" --base develop --branch "$BRANCH_NAME")

OUTCOME=$(printf '%s' "$PREPR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["outcome"])')
REASON=$(printf '%s' "$PREPR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["reason"])')
```

**Outcome routing** (the JSON `outcome` field is authoritative):

| `outcome` | Action |
|-----------|--------|
| `ready` | The base was refreshed (fast-forwarded or already current) and the feature was integrated cleanly onto a stable base. Run the gap audit below, then continue to Step 8. |
| `blocked` | **STOP.** Report the `reason` (`dirty_worktree`, `base_ahead`, `base_diverged`, `conflict`, `base_unstable`, `fetch_failed`, ...). Do **NOT** push or open a PR. Resolve the cause per `reference/pre-pr-readiness.md` (e.g. commit impl+docs for `dirty_worktree`; hand-resolve only an unambiguous `conflict`, never guess), then rerun the gate. |

The gate only ever **fast-forwards** the local `develop` when it is strictly
behind the remote; an `ahead` or `diverged` base is left untouched and blocks —
it is never reset. Integration defaults to rebase (private branch); pass
`--integrate merge` for a shared branch. On any conflict the script aborts and
blocks, leaving the feature branch exactly as it was — the agent may hand-resolve
**only** verifiably-unambiguous conflicts and must rerun the affected
verification and the gate before proceeding.

On a `ready` outcome, perform the **documentation-to-issue gap audit** per
`reference/pre-pr-readiness.md`: index the active issue plus its parent/child and
linked issues, reconcile each required behavior against implementation, test, and
documentation evidence, and record a gap ledger. Each row's `disposition` is
exactly one of `fix-in-pr` (in scope — fix before pushing), `followup-issue`
(out of scope — file a deduplicated follow-up), `already-satisfied`, or
`blocked`. Never report "no gap" when issue or documentation retrieval was
incomplete — treat those rows as `blocked`. Resolve every `fix-in-pr` row and
rerun the gate before continuing to Step 8.

The PR created in Step 8 must **target `develop`**, be written in **Korean** per
`reference/pre-pr-readiness.md` (machine tokens — identifiers, paths, URLs, and
the `Closes #N` keyword — stay ASCII), and **close the active issue**.

### 8. Push and Create PR

```bash
git push -u origin "$BRANCH_NAME"

gh pr create --repo $ORG/$PROJECT \
  --title "${BRANCH_TYPE}(scope): description" \
  --body "Closes #${ISSUE_NUMBER}

## Summary
- Brief description of changes

## Test Plan
- How to verify the changes"
```

**Required**:
- `Closes #<NUMBER>` keyword to link issue
- **Language**: Follow the active `CLAUDE_CONTENT_LANGUAGE` policy (see `commit-settings.md`). PR title and description must each comply with the per-artifact rule of the resolved policy; under `exclusive_bilingual`, each artifact must be wholly English-only or wholly Korean-only.
- No Claude/AI references, emojis, or Co-Authored-By regardless of policy (see `commit-settings.md`)

After PR creation, capture the PR URL from `gh pr create` output for the summary.

### 9. Monitor CI

After PR creation, monitor CI with non-blocking polling:

```bash
# Wait briefly for workflows to register
sleep 8
```

Poll **all** PR checks every 30 seconds, max 10 minutes:

```bash
gh run list --repo $ORG/$PROJECT --branch "$BRANCH_NAME" \
  --json databaseId,name,status,conclusion
```

**Decision table — apply per run, evaluate ALL runs each poll cycle:**

| All runs status | Any conclusion=failure | Action |
|-----------------|----------------------|--------|
| All `completed` | No | All pass → proceed to merge |
| All `completed` | Yes | Diagnose, fix, push, re-poll |
| Any `in_progress` or `queued` | — | Poll again after 30s |

**Merge gate**: Do NOT merge until every run shows `status: completed`.
A run that is `in_progress` or `queued` is NOT a passing run — wait for it.

**Timeout**: If 10-minute polling limit is reached and any run is still
`in_progress` or `queued`, stop polling, report the current status table
to the user, and **do NOT merge**.

**Default** (no `--wait-on-timeout`): leave the PR open and print a resume
command so CI can finish unattended:

```
[CI still running after 10 min — leaving PR open. Resume with: pr-work $PR_NUMBER]
```

Do not block on a prompt. Only when `--wait-on-timeout` was passed, ask the
user whether to wait longer or leave the PR open for manual merge. This
mirrors the ci-fix timeout behavior of defaulting to a resumable hand-off
rather than an interactive stall.

**Do NOT** use `gh run watch` — it blocks the entire session.

On CI failure, fix the issue and push. Repeat up to 3 attempts. After 3 failures,
convert the PR to draft, report the status, and **do NOT proceed to merge or produce a completion summary**. The task is NOT complete until all CI checks pass and the PR is merged.

### 10. Squash Merge

**ABSOLUTE CI GATE — MANDATORY PRE-MERGE VERIFICATION:**

Before executing `gh pr merge`, you MUST run `gh pr checks` and verify every single check:

```bash
gh pr checks $PR_NUMBER --repo $ORG/$PROJECT
```

**Do NOT merge if ANY check shows:**
- `fail` or `failure` conclusion (regardless of perceived cause)
- `pending`, `queued`, or `in_progress` status
- `cancelled`, `timed_out`, or `startup_failure` conclusion

**ALL checks must show `pass` or `neutral` to proceed.** No exceptions. No rationalization.
Never judge a failure as "unrelated", "pre-existing", or "infrastructure-only" — all failures block merge.

If any check is not passing, STOP. Do NOT proceed to merge. Instead:
1. Report the full `gh pr checks` output to the user
2. Either fix the failure and re-poll, or let the user decide

Only when ALL checks pass:

```bash
gh pr merge $PR_NUMBER --repo $ORG/$PROJECT --squash --delete-branch
```

If merge fails (e.g., review required), report the status and skip merge.

### 11. Close Related Issues and Epics

After merge:

```bash
# Verify the linked issue was closed by the merge
STATE=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json state -q '.state')
if [[ "$STATE" != "CLOSED" ]]; then
    gh issue close $ISSUE_NUMBER --repo $ORG/$PROJECT
fi
```

**Epic closure**: If the issue references a parent epic (e.g., `Part of #N`),
check if all sub-issues of that epic are now closed. If so, close the epic
with a summary comment.

### 12. Update Original Issue

**IMPORTANT**: Issue comments must comply with the active `CLAUDE_CONTENT_LANGUAGE` policy (resolved from `commit-settings.md`; default `english`). Do not hard-code "English only" — under `exclusive_bilingual` a Korean-only comment is valid, and under `korean_plus_english` mixed inline is valid.

```bash
gh issue comment <NUMBER> --repo $ORG/$PROJECT \
  --body "Implementation PR: #<PR_NUMBER>"
```

### 13. Workspace Teardown

**Mandatory, all tiers.** After the merge (Step 10) and issue/epic closure
(Steps 11-12), tear down the isolated workspace claimed in Step 3. See
`reference/workspace-lifecycle.md` (the #840 sections) for the full contract
(resume-reconciliation rule, preservation predicate, remotely-recoverable
rule, 3-fail preservation policy).

```bash
bash ~/.claude/skills/_internal/issue-work/scripts/cleanup-workspace.sh \
  --phase reconcile --repo-dir "$REPO_DIR" --manifest "$MANIFEST" --pr "$PR_NUMBER"

bash ~/.claude/skills/_internal/issue-work/scripts/cleanup-workspace.sh \
  --phase cleanup --run-root "$(dirname "$REPO_DIR")" --repo-dir "$REPO_DIR" \
  --manifest "$MANIFEST" --base "${TMPDIR:-/tmp}" --issue "$ISSUE_NUMBER" \
  --pr "$PR_NUMBER" ${MERGE_COMMIT:+--merge-commit "$MERGE_COMMIT"}
```

`reconcile` re-reads the live branch/PR state before `cleanup` decides whether
to remove the run root. A `PRESERVED` result (e.g. uncommitted work, an
unmerged PR, or a still-held agent lease) is not a failure — report the
`reason` and leave the run root on disk. Only a `CLEANED` result removes it.

---

## Team Mode Instructions

See `reference/team-mode.md` for the complete team mode workflow with 3-team architecture (dev, reviewer, doc-writer), feedback loops, and cleanup.

## Policies

See [_policy.md](../_policy.md) for common rules, including the **Atomic Multi-Phase Execution** rule — when the user specifies multiple phases (e.g., "Phase 1/2/3"), complete all phases without pausing between them for confirmation.

### Command-Specific Rules

| Item | Rule |
|------|------|
| **Language** | All issue comments, PR titles, PR descriptions, and commit messages follow the active `CLAUDE_CONTENT_LANGUAGE` policy (see `commit-settings.md`) |
| Issue linking | `Closes #NUM` required in PR |
| Build verification | Must pass before PR creation |

## Output

**CRITICAL**: Do NOT produce a completion summary if CI has any failing, pending, or incomplete checks. A task is only complete when the PR is merged with all CI checks passing.

### Result Marker

Every exit path of this workflow — a terminal triage outcome (Step 1), a
pre-PR gate block (Step 7.5), a CI failure (Step 9-10), or a full merge — ends
by printing exactly one `ISSUE_WORK_RESULT:` line as the **last line** of its
output:

```
ISSUE_WORK_RESULT: {"status":"merged|decomposed|blocked|skipped|failed","requested":"<n>","root":"<n>","active":"<n>","pr_url":"<url-or-null>","reason":"<short>"}
```

Fields mirror the batch subagent JSON contract (`reference/batch-mode.md`
B-4.a): the same status vocabulary and the same `requested`/`root`/`active`
triple from the triage outcome (`reference/triage-state-machine.md`). `status`
is derived from the triage outcome when work did not proceed, else from the
final merge/CI state. External orchestrators (`scripts/batch-issue-work.sh` /
`.ps1`) parse this line to branch on the structured outcome instead of the
process exit code alone. Both Work Summary formats below end with this
marker, as the trailing line after the table and any following sections.

After successful merge, provide summary:

```markdown
## Work Summary

| Item | Value |
|------|-------|
| Repository | $ORG/$PROJECT |
| Issue | #$ISSUE_NUMBER - Title |
| Branch | $BRANCH_NAME |
| Execution mode | Solo / Team |
| PR | [#PR_NUMBER](https://github.com/$ORG/$PROJECT/pull/PR_NUMBER) |
| CI Status | All checks passed |
| Merged | Yes |
| Commits | N commits |

### Changes Made
- List of changes

### Files Modified
- file1.cpp
- file2.h

### Next Steps
- Any follow-up items

ISSUE_WORK_RESULT: {"status":"merged","requested":"$REQUESTED_ISSUE","root":"$ROOT_ISSUE","active":"$ISSUE_NUMBER","pr_url":"https://github.com/$ORG/$PROJECT/pull/$PR_NUMBER","reason":""}
```

If CI failed or the PR was not merged, use this format instead:

```markdown
## Work Summary (INCOMPLETE)

| Item | Value |
|------|-------|
| Repository | $ORG/$PROJECT |
| Issue | #$ISSUE_NUMBER - Title |
| Branch | $BRANCH_NAME |
| PR | [#PR_NUMBER](https://github.com/$ORG/$PROJECT/pull/PR_NUMBER) |
| CI Status | FAILING — [list failed checks] |
| Merged | No |
| Reason | [CI failure / Max retries exceeded / Timeout] |

### Action Required
- User must resolve CI failures before merge

ISSUE_WORK_RESULT: {"status":"failed","requested":"$REQUESTED_ISSUE","root":"$ROOT_ISSUE","active":"$ISSUE_NUMBER","pr_url":"<url-or-null>","reason":"<short reason>"}
```

**IMPORTANT**: Always include the full PR URL in the output (e.g., `https://github.com/org/repo/pull/123`).

### Batch Mode Output

In batch mode, use the summary format from **Phase B-5** instead. Include per-item results and the overall success/failure count.

## Error Handling

See `reference/error-handling.md` for prerequisite checks, runtime errors, and batch mode errors.

## Side Effects and Loop-Safety

This skill is `loop_safe: false`. Each invocation creates branches, opens or advances pull requests, and posts comments against live GitHub issues. Wrapping it in `/loop` would re-trigger work on already-handled issues and create duplicate branches/PRs. The exit contract is "one resolved issue (or batch) per invocation," not a no-side-effect retry — resume an interrupted run from the documented session-resume state rather than re-running blindly.

