diff --git a/.claude/settings.json b/.claude/settings.json index 4f79d99..e1fbe36 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -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 *)" ] } } diff --git a/.claude/skills/issue-runner/SKILL.md b/.claude/skills/issue-runner/SKILL.md new file mode 100644 index 0000000..6644ace --- /dev/null +++ b/.claude/skills/issue-runner/SKILL.md @@ -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 + 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) +``` + +> 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: "" + fieldId: "PVTSSF_lADOA59kY84BUaDazhBiYJE" + value: { singleSelectOptionId: "" } + }) { 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 + 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=""` 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 +``` + +--- + +## 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 + +**Dependency check:** use the REST field `issue_dependencies_summary.blocked_by`: +```bash +gh api repos/androidbroadcast/Relay/issues/ \ + --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. + +--- + +## 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: + +## Queue +### Wave 1 +- [ ] #N [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 + +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 + +## 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 |