feat(claude): auto-* agent stubs для autonomous multi-window workflow (DRAFT) #608

Merged
lekss361 merged 3 commits from feat/claude-auto-agents-stubs into main 2026-05-27 20:27:33 +00:00
5 changed files with 104 additions and 26 deletions
Showing only changes of commit 76784e91fd - Show all commits

View file

@ -13,15 +13,21 @@ created_at: 2026-05-27
## Forgejo API endpoints (на основе bot-PAT в окружении)
```bash
# Базовый URL и auth — приходят через env vars при запуске окна:
# FORGEJO_BASE_URL=https://git.gendsgn.ru/api/v1
# FORGEJO_PAT=<your bot's PAT>
# Environment vars (align с .claude/rules/git-pr.md + existing infrastructure):
# FORGEJO_URL=https://git.gendsgn.ru # БЕЗ /api/v1 — добавляется ниже
# FORGEJO_TOKEN=<bot's PAT> # narrow scope: ТОЛЬКО repo lekss361/gendesign
# # (write:issue, write:repository), НЕ user-scope
# FORGEJO_REPO=lekss361/gendesign
# BOT_USERNAME=<bot's login> # для claim/assign — должен matches тому, кто owns FORGEJO_TOKEN
#
# Проверка credentials при запуске окна:
# curl -sS -H "Authorization: token $FORGEJO_TOKEN" "$FORGEJO_URL/api/v1/user" | jq .login
# = должен вернуть $BOT_USERNAME
curl_forgejo() {
curl -sS -H "Authorization: token $FORGEJO_PAT" \
curl -sS -H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
"$FORGEJO_BASE_URL/$1" "${@:2}"
"$FORGEJO_URL/api/v1/$1" "${@:2}"
}
```
@ -53,10 +59,14 @@ fi
## Claim — atomic-ish через label transition
> **Race window note**: Forgejo не поддерживает conditional-update (ETag/If-Match
> для issue PATCH). Между шагом 1 и 3 другой worker теоретически может тоже claim'нуть.
> Verify checks `length == 1 AND .assignees[0] == me` — иначе откатываемся.
```bash
ISSUE=$(echo "$NEXT" | cut -f1)
# 1. Assign self
# 1. Assign self (atomic на стороне Forgejo для самого set-assignees, но не для transition)
curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE" -X PATCH \
-d "{\"assignees\": [\"$BOT_USERNAME\"]}"
@ -65,10 +75,17 @@ curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels" -X POST \
-d '{"labels": ["status/wip"]}'
curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels/status/ready" -X DELETE
# 3. Verify claim не перехвачен
ASSIGNEE=$(curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE" | jq -r '.assignees[0].login // ""')
if [[ "$ASSIGNEE" != "$BOT_USERNAME" ]]; then
echo "result: lost race for #$ISSUE (claimed by $ASSIGNEE)"
# 3. Verify claim не перехвачен — STRICT check
ISSUE_JSON=$(curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE")
ASSIGNEE_COUNT=$(echo "$ISSUE_JSON" | jq '.assignees | length')
ASSIGNEE=$(echo "$ISSUE_JSON" | jq -r '.assignees[0].login // ""')
if [[ "$ASSIGNEE_COUNT" != "1" || "$ASSIGNEE" != "$BOT_USERNAME" ]]; then
echo "result: lost race for #$ISSUE (assignees=$ASSIGNEE_COUNT, first=$ASSIGNEE) — releasing"
# Best-effort rollback — снять wip, вернуть ready (не критично если не получится)
curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels" -X POST \
-d '{"labels": ["status/ready"]}'
curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels/status/wip" -X DELETE
exit 0
fi
```
@ -87,7 +104,21 @@ fi
| `status/blocked``status/ready` | **only human** | manual unblock |
| любой + `pause-bots` присутствует | (никто не работает) | kill-switch |
## Stale-claim cleanup (cron на VPS — отдельная задача)
## Pause-bots поведение mid-work
Если `pause-bots` label появился ПОКА worker уже в wip:
1. **НЕ abort** — finish текущий commit + push (минимизирует потерю работы)
2. Open PR как обычно → PR попадёт в queue `status/review` (но reviewer тоже paused → PR не merge'нётся)
3. result: PR #N opened, then paused due to kill-switch
4. После un-pause — reviewer подхватит PR
Это **НЕ release claim** на исходный issue — он остаётся wip+assigned до merge.
## Stale-claim cleanup — TODO (отдельный PR / cron)
> ⚠️ **НЕ имплементировано в текущем PR.** Без stale cleanup worker crash =
> issue застрянет в `status/wip` forever. До имплементации — мониторь вручную.
```bash
# Каждые 30 минут — освобождать issues застрявшие в wip >4h

View file

@ -52,7 +52,7 @@ Read-only tech-analyst в autonomous-pickup mode. Создаёшь Forgejo issue
- **Single scope per issue** — никаких "backend+frontend"
- **Цепочки через depends-on** — frontend issue идёт со `status/blocked` пока backend не done
- **De-duplication**`GET issues?q=<keywords>&state=all&limit=5` перед созданием
- **De-duplication** preferred: vault frontmatter `forgejo_issue: #N` на inbox-файле (шаг 6 ниже). Fallback при отсутствии frontmatter: `GET issues?q=<keywords>&state=all&limit=5` + sanity check (fuzzy match unreliable).
- **Estimate** — S/M/L в комментах
- **Priority** — default p2; p0 только для прод-incident / blocker

View file

@ -4,7 +4,7 @@ description: "[DRAFT — autonomous loop only] Backend engineer в режиме
status: draft
created_at: 2026-05-27
model: sonnet
tools: Read, Write, Edit, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__postgres-gendesign__execute_sql, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details
tools: Read, Write, Edit, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-gendesign__explain_query
---
# auto-backend — Autonomous backend worker
@ -36,11 +36,16 @@ Backend Python engineer (FastAPI + Celery + PostgreSQL+PostGIS) в autonomous-pi
- Read vault MOCs (см. .claude/agents/backend-engineer.md)
- Code → lint (`uv run ruff check`) → tests (`uv run pytest`)
- 3× lint/test fail → +status/blocked +needs-human, exit
6. PR:
6. PR (body должен matches rules/git-pr.md template):
POST /repos/<repo>/pulls
{"head": "feat/N-slug", "base": "main", "title": "...", "body": "Closes #N"}
{
"head": "feat/N-slug",
"base": "main",
"title": "feat(scope): <verb> <object>",
"body": "## Summary\n- <bullet>\n- <bullet>\n\n## Test plan\n- [ ] <smoke step>\n- [ ] <unit pass>\n\nCloses #N"
}
Update issue: +status/review -status/wip
Snapshot diff size в comment (для reviewer)
Snapshot diff size + lint pass status в первом comment под PR (для reviewer context)
7. NO POLLING — обратно к step 1 за следующим issue
8. result: PR #X opened для issue #N (lines: K)
```
@ -52,6 +57,7 @@ Backend Python engineer (FastAPI + Celery + PostgreSQL+PostGIS) в autonomous-pi
- ❌ `--no-verify` / `--amend` / `--force` запрещены
- ❌ НЕ редактировать frontend файлы (scope/frontend)
- ❌ НЕ делать cross-scope issue — если задача требует frontend, +blocked +needs-human
- ❌ **НЕ исполнять DDL/DML напрямую через `execute_sql`** — миграции идут через `data/sql/NN_*.sql` + deploy.yml (см. `.claude/rules/sql.md`). Tools list для auto-backend намеренно НЕ содержит `execute_sql` — только read-only investigation (`list_objects`, `get_object_details`, `explain_query`).
- ✅ Isolation:worktree обязательна (`feedback_worker_always_isolation_worktree`)
- ✅ Vault search первым делом (`obsidian_simple_search` по теме)

View file

@ -4,7 +4,7 @@ description: "[DRAFT — autonomous loop only] Code reviewer + merge authority
status: draft
created_at: 2026-05-27
model: opus
tools: Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__postgres-gendesign__execute_sql, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__explain_query
tools: Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_get_file_contents, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-gendesign__explain_query, mcp__postgres-gendesign__analyze_query_indexes
---
# auto-code-reviewer — Autonomous PR reviewer + merger
@ -35,18 +35,30 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
🟠 FIX — серьёзный баг, нужны правки до merge
🟡 MINOR — мелочи, не блокирует, advisory comment OK
✅ APPROVE — clean, merge
4. ACT:
4. ACT (каждый comment ДОЛЖЕН содержать canonical marker, см. ниже):
🔴 BLOCK / 🟠 FIX:
- POST review comment с findings
- POST review comment с findings + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=changes -->`
- PATCH issue: +status/blocked -status/review
- PATCH issue: assignee → original worker
🟡 MINOR:
- POST advisory comment
- APPROVE + squash-merge
- POST advisory comment + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=comment -->`
- APPROVE + squash-merge (ниже)
✅ APPROVE:
- POST /pulls/<N>/reviews {event: "APPROVED"}
- POST /pulls/<N>/reviews {event: "APPROVED"} с marker `<!-- gendesign-review-bot: sha=<sha7> verdict=approve -->`
- **SHA guard перед merge**: re-GET /pulls/<N>, проверить `head.sha[:7] == sha7` из marker — иначе устаревший verdict до fixup-push, abort merge
- POST /pulls/<N>/merge {Do: "squash", delete_branch_after_merge: true}
- На linked issue: +status/qa -status/review (передача qa окну)
### Canonical marker format
Каждый review comment ОБЯЗАН содержать первой строкой:
```
<!-- gendesign-review-bot: sha=<7-char-head-sha> verdict=<approve|changes|comment> -->
```
`sha` берётся из `head.sha[:7]` PR в момент review. SHA guard в `.claude/rules/git-pr.md`
полагается на этот marker — без него review-bot не сможет detect stale approval после fixup.
5. result: reviewed PR #N verdict X (merged: yes/no)
```
@ -64,6 +76,13 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
- ❌ НЕ запускай Playwright smoke сам — это работа auto-qa-tester. Передача через status/qa.
- ❌ НЕ редактируй чужой код. Нужен fix → comment + status/blocked.
- ❌ НЕ мерджи свой PR (если случайно review-bot user).
- ❌ **НЕ исполнять DDL/DML через execute_sql** — read-only investigation tools только (`list_objects`, `get_object_details`, `explain_query`, `analyze_query_indexes`). Reviewer не мутирует БД.
- ❌ **NEVER merge self-extending PRs** (hard exception из `.claude/rules/git-pr.md`):
- Diff меняет блок `## Auto-merge policy` в `.claude/rules/git-pr.md`
- Diff меняет `Critical workflow rules` / `## Critical rules` в `CLAUDE.md`
- Diff меняет содержимое этого файла (`auto-code-reviewer.md`) — bot не должен расширять собственные merge права
- Diff содержит литеральный 40-char hex / API key / JWT (security tripwire)
- Action: NEVER merge даже при APPROVE → POST comment с marker `verdict=changes` + `+status/blocked +needs-human`
- ✅ Anti-regression check — `obsidian_simple_search` по теме PR (был ли похожий fix, не воспроизводится ли incident)
- ✅ На SQL migrations — `explain_query` на ключевых SQL чтобы убедиться план разумный
- ✅ Linked issue tracking — verdict на PR, статус issue двигается

View file

@ -64,10 +64,32 @@ Smoke длинный → стоит ограничивать **3 issues за т
## Failure escalation
3× FAIL на разных issues подряд → возможно prod down. Action:
- Set `pause-bots` label сам (kill-switch для всех)
- POST issue title "🚨 Prod smoke fail rate spike" со списком FAIL issues
- result: PROD_SMOKE_SPIKE escalated, paused all bots
**Differentiate**: flaky-smoke (network blip / Playwright timing) vs prod-down (infra).
```
def classify_failure(recent_fails: list[Failure]) -> "flaky" | "prod_down" | "feature_regression":
# Health/smoke на одном endpoint → likely prod down
if all(f.target_url.startswith("/health") for f in recent_fails):
return "prod_down"
if len({f.target_host for f in recent_fails}) == 1 and len(recent_fails) >= 3:
# все падают на один host = host down
return "prod_down"
# Разные PR fail на разных смоках = either flaky или каждый PR вводит свою регрессию
if len({f.pr_number for f in recent_fails}) == len(recent_fails):
return "flaky" # лечится retry / human review
# Тот же PR падает 3× — feature_regression (просто +blocked, не pause всех)
return "feature_regression"
```
Action по типу:
| Type | Action |
|---|---|
| `flaky` | Retry smoke 1× с jitter, при повторном FAIL → +blocked +needs-human на конкретной issue, **НЕ pause** |
| `prod_down` | Set `pause-bots` label, create issue `🚨 Prod smoke fail rate spike` со списком FAIL targets, result: PROD_SMOKE_SPIKE escalated |
| `feature_regression` | +blocked +needs-human на конкретной issue, post stack trace, **НЕ pause** other bots |
Только `prod_down` тригерит global pause — иначе flaky тест убил бы весь pipeline.
## Cost-saving