Skip to content
7 changes: 6 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
"permissions": {
"allow": [
"Bash(ya tool ast-index *)",
"Bash(ast-index *)"
"Bash(ast-index *)",
"Bash(gh *)",
"Bash(git *)",
"Bash(cargo *)",
"Bash(xcodebuild *)",
"Bash(swiftlint *)"
]
}
}
301 changes: 301 additions & 0 deletions .claude/skills/issue-runner/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
---
name: issue-runner
description: >
Explicit-only skill — invoke only via /issue-runner.
Autonomous GitHub Issue sprint runner: reads the project board (androidbroadcast/Relay,
project #2), assigns tasks to subagents, moves cards through statuses
(Todo → In Progress → Review → Done), and drives each issue through the full
dev-workflow pipeline (plan → implement → quality → PR).
---

# Issue Runner — Relay Project

Project manager + orchestrator: reads the board → assigns issues to agents →
moves cards by status → each agent implements, quality loop, PR → board reflects reality.

---

## Board constants

```
Project: org androidbroadcast, project #2 (org-level ProjectV2, not repo-scoped)
Project ID: PVT_kwDOA59kY84BUaDa
Status field: PVTSSF_lADOA59kY84BUaDazhBiYJE
Comment thread
kirich1409 marked this conversation as resolved.
Todo: f75ad846
In Progress: 47fc9ee4
Review: 1be2fb87
Done: 98236657
Complexity: PVTSSF_lADOA59kY84BUaDazhBm5vc
S: 58b3409e
M: 8da00f47
L: 581040d4
Wave: PVTIF_lADOA59kY84BUaDazhBm6NQ (IterationField — value is iteration title)
```
Comment thread
kirich1409 marked this conversation as resolved.

> These IDs are stable for the lifetime of the project fields. If a field/option is deleted and recreated, re-query with:
> `gh api graphql -f query='{ organization(login:"androidbroadcast") { projectV2(number:2) { fields(first:30) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } ... on ProjectV2IterationField { id name } } } } } }'`

### Move a card to a status

```bash
gh api graphql -f query='mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "PVT_kwDOA59kY84BUaDa"
itemId: "<ITEM_ID>"
fieldId: "PVTSSF_lADOA59kY84BUaDazhBiYJE"
value: { singleSelectOptionId: "<STATUS_OPTION_ID>" }
}) { projectV2Item { id } }
}'
```

### Get all board items with issue numbers and statuses

```bash
gh api graphql -f query='
{
organization(login: "androidbroadcast") {
projectV2(number: 2) {
items(first: 100, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
id
Comment thread
kirich1409 marked this conversation as resolved.
content {
... on Issue {
number title
issueType { name }
parent { number title }
labels(first: 10) { nodes { name } }
}
}
fieldValues(first: 20) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
name
field { ... on ProjectV2SingleSelectField { name } }
}
... on ProjectV2ItemFieldIterationValue {
title
field { ... on ProjectV2IterationField { name } }
}
}
}
}
}
}
}
}'
```

Paginate: repeat with `-f cursor="<endCursor>"` while `pageInfo.hasNextPage == true`. Collect all nodes across pages.

---

## Phase 0: Setup

```bash
gh auth status
git worktree list
mkdir -p .worktree
grep -q "swarm-report" .gitignore || echo "swarm-report/" >> .gitignore
```
Comment thread
kirich1409 marked this conversation as resolved.

---

## Phase 1: Read the board

Fetch all board items (query above). For each item:
- Extract issue number, title, current Status, Complexity field, Wave field, parent epic
- Skip items with `issueType.name == "Feature"` — those are Epics, not Tasks
- Ignore items already in **Done**
- Collect item IDs — you need them to move cards

Build a priority-ordered queue:
- Group by Wave iteration title: "Wave 1" first, then "Wave 2"…"Wave 5", no wave last
- Within wave: Complexity `S` → `M` → `L`
- Items labeled `blocker` → log as pre-blocked, skip

Comment thread
kirich1409 marked this conversation as resolved.
**Dependency check:** use the REST field `issue_dependencies_summary.blocked_by`:
```bash
gh api repos/androidbroadcast/Relay/issues/<NUMBER> \
--jq '.issue_dependencies_summary.blocked_by'
```
If `blocked_by > 0` → the issue has unresolved blockers → mark BLOCKED. No body parsing needed — GitHub Relations are the authoritative source.

Comment thread
kirich1409 marked this conversation as resolved.
---

## Phase 2: State file

Save to `swarm-report/issue-runner-state.md`. Re-read at the start of every turn.

```markdown
# Issue Runner State
Started: <ISO timestamp>

## Queue
### Wave 1
- [ ] #N <title> [item_id: PVTI_xxx, complexity:S]
### Wave 2
- [ ] #N <title> [item_id: PVTI_xxx, complexity:M]

## In Progress
- #N <title> (item_id: PVTI_xxx) — agent running

## Completed
| Issue | Item ID | PR | Result |
|-------|---------|----|--------|

## Blocked / Failed
| Issue | Reason |
|-------|--------|
```

---

## Phase 3: Wave loop

**Do not start wave N+1 until all wave N agents are done.**

For each wave:
1. Separate wave items into **ready** (not BLOCKED) and **skipped** (BLOCKED)
2. Print: "Wave N: M ready, K skipped (blocked)"
3. Move only **ready** items to **In Progress** on the board
4. Launch all ready agents simultaneously (one Agent call per issue, all in one message)
5. As each agent starts successfully, confirm the card stays **In Progress**; if an agent fails to start, revert its card to **Todo**
6. As agents complete, update board and state file
7. Print wave summary (N done / N blocked / N failed)
8. Pause — user can stop or redirect before the next wave

---

## Phase 4: Per-issue agent prompt

```
## Task
GitHub Issue #<NUMBER>: <TITLE>
Epic: <PARENT_EPIC_TITLE or "none">
Complexity: <S|M|L>
Wave: <Wave N>
Board item ID: <PVTI_xxx>

<FULL ISSUE BODY>

---

Your job: implement this issue end-to-end and create a PR.

## Step 1 — Read the spec

docs/architecture/relay-cloud-decomposition.md
→ find the section for this task (e.g. T-3, T-7a) by matching task ID in the title
→ read full description and acceptance criteria — this is your definition of done

docs/architecture/relay-cloud-architecture.md (context)
docs/testplans/relay-cloud-mvp-test-plan.md (if relevant)

## Step 2 — Worktree

Comment thread
kirich1409 marked this conversation as resolved.
git worktree add .worktree/<SLUG> -b feature/<SLUG>

SLUG = kebab-case from issue title. All work happens in this worktree.

## Step 3 — Pipeline (by Complexity field)

S → implement → quality loop → PR
M → plan (swarm-report/<N>-<SLUG>-plan.md) → implement → quality loop → PR
L → research → plan → implement → quality loop → PR

Backend (Rust): use `rust-backend-engineer` agent (defined in `.claude/agents/`).
Frontend (Swift/TCA): use `developer-workflow:swiftui-developer` plugin agent.

> `developer-workflow:*` agents are part of the globally installed `krozov-ai-tools/developer-workflow` plugin — available in all sessions, not defined per-project.

## Step 4 — Quality loop (max 3 attempts per gate)

1. Build: cd runner && cargo build (backend)
xcodebuild build -project MacApp/Relay.xcodeproj -scheme Relay -destination 'platform=macOS,arch=arm64' CODE_SIGNING_ALLOWED=NO (frontend)
2. Format: cd runner && cargo fmt --check (backend)
3. Lint: cd runner && cargo clippy -- -D warnings (backend)
cd MacApp && swiftlint lint --strict (frontend)
4. Tests: cd runner && cargo test (backend)
xcodebuild test -project MacApp/Relay.xcodeproj -scheme Relay -destination 'platform=macOS,arch=arm64' CODE_SIGNING_ALLOWED=NO (frontend)
5. Review: `developer-workflow:code-reviewer` plugin agent with task description + git diff
Comment thread
kirich1409 marked this conversation as resolved.
Comment thread
kirich1409 marked this conversation as resolved.
Comment thread
kirich1409 marked this conversation as resolved.

## Step 5 — PR

gh pr create --repo androidbroadcast/Relay \
--title "<ISSUE TITLE>" \
--body "Summary + acceptance criteria checklist (all checked) + Closes #<NUMBER>"

"Closes #<NUMBER>" in the body closes the issue automatically on merge.

## Step 6 — Report back (exactly this format)

STATUS: DONE|BLOCKED|FAILED
PR: <url or none>
ISSUE: #<NUMBER>
ITEM_ID: <PVTI_xxx>
NOTES: <one-line summary or blocker reason>

## Constraints
- Never commit to main
- Worktree: .worktree/<SLUG>, branch: feature/<SLUG>
- Reports: swarm-report/<N>-<SLUG>-*.md
- Max 3 attempts per quality gate; escalate as FAILED if still broken
- If a required dependency is not merged: BLOCKED "depends on #N (open)"
```

**Model by Complexity field:**
- `L` → `model: opus`
- `M` → default (sonnet)
- `S` → `model: haiku`

---

## Phase 5: Board updates after agent completes

| Agent result | Board action | State file |
|---|---|---|
| DONE | Move to **Review** | → Completed |
| BLOCKED | Move to **Todo** | → Blocked/Failed |
| FAILED | Move to **Todo** | → Blocked/Failed |
| PR merged (poll or webhook) | Move to **Done** | → mark closed |

### Check if PR is merged (after wave)

```bash
gh pr view <PR_NUMBER> --repo androidbroadcast/Relay --json state,mergedAt
```

If merged → move board item to Done.

---

## Phase 6: Dependency retry

After each wave, re-check BLOCKED issues:
- If the blocking issue is now Done on the board → add dependent to next wave queue
- Still open → keep BLOCKED

---

## Phase 7: Final report

```
# Issue Runner — Final Report
Date: <date>

✅ Completed: N (#N → PR url, ...)
⚠️ Blocked: N (reasons)
❌ Failed: N (details)
```

---

## Error handling

| Situation | Action |
|---|---|
| BLOCKED | Move card to Todo, log, continue |
| FAILED | Move card to Todo, log, continue |
| Build fails 3+ times | Escalate to user with error |
| No Todo/In-Progress items | Print "Nothing to do" and exit |
Loading