fixup(claude): address review-bot findings on PR #608
Some checks failed
CI / backend (pull_request) Failing after 43s
CI / frontend (pull_request) Successful in 1m35s

🟠 High:
- auto-backend.md: drop execute_sql из tools (DDL/DML blast radius)
- auto-code-reviewer.md: drop execute_sql + add analyze_query_indexes
- _autonomous_pickup.md: env vars FORGEJO_PAT/BASE_URL → FORGEJO_TOKEN/URL (align с rules/git-pr.md)
- auto-code-reviewer.md: добавлен canonical marker template
  <!-- gendesign-review-bot: sha=X verdict=Y --> + SHA guard перед merge
- auto-code-reviewer.md: self-extending guard exception (NEVER merge PRs
  меняющие git-pr.md auto-merge policy / CLAUDE.md critical rules /
  этот файл / содержащие литеральные secrets)

🟡 Medium:
- _autonomous_pickup.md: STRICT claim race check (length==1 AND assignee==me)
  + best-effort rollback при race lost
- _autonomous_pickup.md: pause-bots mid-work policy (finish current, не abort)
- _autonomous_pickup.md: stale-claim cron explicitly marked TODO
- auto-qa-tester.md: differentiate flaky/prod_down/feature_regression — global
  pause только при prod_down (3× FAIL на /health endpoint), не на flaky
- auto-backend.md: PR body template из rules/git-pr.md (Summary + Test plan + Closes)

🟢 Low:
- _autonomous_pickup.md: BOT_USERNAME documented в env list
- _autonomous_pickup.md: PAT scope guidance (narrow repo, не user-scope)
- auto-analyst.md: de-dup через forgejo_issue frontmatter preferred (fuzzy
  query — fallback)

Refs: PR #608 review by @lekss361 (gendesign-review-bot marker)
This commit is contained in:
lekss361 2026-05-27 23:11:58 +03:00
parent 89303c9769
commit 76784e91fd
5 changed files with 104 additions and 26 deletions

View file

@ -13,15 +13,21 @@ created_at: 2026-05-27
## Forgejo API endpoints (на основе bot-PAT в окружении) ## Forgejo API endpoints (на основе bot-PAT в окружении)
```bash ```bash
# Базовый URL и auth — приходят через env vars при запуске окна: # Environment vars (align с .claude/rules/git-pr.md + existing infrastructure):
# FORGEJO_BASE_URL=https://git.gendsgn.ru/api/v1 # FORGEJO_URL=https://git.gendsgn.ru # БЕЗ /api/v1 — добавляется ниже
# FORGEJO_PAT=<your bot's PAT> # FORGEJO_TOKEN=<bot's PAT> # narrow scope: ТОЛЬКО repo lekss361/gendesign
# # (write:issue, write:repository), НЕ user-scope
# FORGEJO_REPO=lekss361/gendesign # 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_forgejo() {
curl -sS -H "Authorization: token $FORGEJO_PAT" \ curl -sS -H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \ -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 ## 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 ```bash
ISSUE=$(echo "$NEXT" | cut -f1) 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 \ curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE" -X PATCH \
-d "{\"assignees\": [\"$BOT_USERNAME\"]}" -d "{\"assignees\": [\"$BOT_USERNAME\"]}"
@ -65,10 +75,17 @@ curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels" -X POST \
-d '{"labels": ["status/wip"]}' -d '{"labels": ["status/wip"]}'
curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels/status/ready" -X DELETE curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE/labels/status/ready" -X DELETE
# 3. Verify claim не перехвачен # 3. Verify claim не перехвачен — STRICT check
ASSIGNEE=$(curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE" | jq -r '.assignees[0].login // ""') ISSUE_JSON=$(curl_forgejo "repos/$FORGEJO_REPO/issues/$ISSUE")
if [[ "$ASSIGNEE" != "$BOT_USERNAME" ]]; then ASSIGNEE_COUNT=$(echo "$ISSUE_JSON" | jq '.assignees | length')
echo "result: lost race for #$ISSUE (claimed by $ASSIGNEE)" 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 exit 0
fi fi
``` ```
@ -87,7 +104,21 @@ fi
| `status/blocked``status/ready` | **only human** | manual unblock | | `status/blocked``status/ready` | **only human** | manual unblock |
| любой + `pause-bots` присутствует | (никто не работает) | kill-switch | | любой + `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 ```bash
# Каждые 30 минут — освобождать issues застрявшие в wip >4h # Каждые 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" - **Single scope per issue** — никаких "backend+frontend"
- **Цепочки через depends-on** — frontend issue идёт со `status/blocked` пока backend не done - **Цепочки через 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 в комментах - **Estimate** — S/M/L в комментах
- **Priority** — default p2; p0 только для прод-incident / blocker - **Priority** — default p2; p0 только для прод-incident / blocker

View file

@ -4,7 +4,7 @@ description: "[DRAFT — autonomous loop only] Backend engineer в режиме
status: draft status: draft
created_at: 2026-05-27 created_at: 2026-05-27
model: sonnet 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 # 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) - Read vault MOCs (см. .claude/agents/backend-engineer.md)
- Code → lint (`uv run ruff check`) → tests (`uv run pytest`) - Code → lint (`uv run ruff check`) → tests (`uv run pytest`)
- 3× lint/test fail → +status/blocked +needs-human, exit - 3× lint/test fail → +status/blocked +needs-human, exit
6. PR: 6. PR (body должен matches rules/git-pr.md template):
POST /repos/<repo>/pulls 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 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 7. NO POLLING — обратно к step 1 за следующим issue
8. result: PR #X opened для issue #N (lines: K) 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` запрещены - ❌ `--no-verify` / `--amend` / `--force` запрещены
- ❌ НЕ редактировать frontend файлы (scope/frontend) - ❌ НЕ редактировать frontend файлы (scope/frontend)
- ❌ НЕ делать cross-scope issue — если задача требует frontend, +blocked +needs-human - ❌ НЕ делать 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`) - ✅ Isolation:worktree обязательна (`feedback_worker_always_isolation_worktree`)
- ✅ Vault search первым делом (`obsidian_simple_search` по теме) - ✅ Vault search первым делом (`obsidian_simple_search` по теме)

View file

@ -4,7 +4,7 @@ description: "[DRAFT — autonomous loop only] Code reviewer + merge authority
status: draft status: draft
created_at: 2026-05-27 created_at: 2026-05-27
model: opus 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 # auto-code-reviewer — Autonomous PR reviewer + merger
@ -35,18 +35,30 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
🟠 FIX — серьёзный баг, нужны правки до merge 🟠 FIX — серьёзный баг, нужны правки до merge
🟡 MINOR — мелочи, не блокирует, advisory comment OK 🟡 MINOR — мелочи, не блокирует, advisory comment OK
✅ APPROVE — clean, merge ✅ APPROVE — clean, merge
4. ACT: 4. ACT (каждый comment ДОЛЖЕН содержать canonical marker, см. ниже):
🔴 BLOCK / 🟠 FIX: 🔴 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: +status/blocked -status/review
- PATCH issue: assignee → original worker - PATCH issue: assignee → original worker
🟡 MINOR: 🟡 MINOR:
- POST advisory comment - POST advisory comment + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=comment -->`
- APPROVE + squash-merge - APPROVE + squash-merge (ниже)
✅ APPROVE: ✅ 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} - POST /pulls/<N>/merge {Do: "squash", delete_branch_after_merge: true}
- На linked issue: +status/qa -status/review (передача qa окну) - На 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) 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. - ❌ НЕ запускай Playwright smoke сам — это работа auto-qa-tester. Передача через status/qa.
- ❌ НЕ редактируй чужой код. Нужен fix → comment + status/blocked. - ❌ НЕ редактируй чужой код. Нужен fix → comment + status/blocked.
- ❌ НЕ мерджи свой PR (если случайно review-bot user). - ❌ НЕ мерджи свой 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) - ✅ Anti-regression check — `obsidian_simple_search` по теме PR (был ли похожий fix, не воспроизводится ли incident)
- ✅ На SQL migrations — `explain_query` на ключевых SQL чтобы убедиться план разумный - ✅ На SQL migrations — `explain_query` на ключевых SQL чтобы убедиться план разумный
- ✅ Linked issue tracking — verdict на PR, статус issue двигается - ✅ Linked issue tracking — verdict на PR, статус issue двигается

View file

@ -64,10 +64,32 @@ Smoke длинный → стоит ограничивать **3 issues за т
## Failure escalation ## Failure escalation
3× FAIL на разных issues подряд → возможно prod down. Action: **Differentiate**: flaky-smoke (network blip / Playwright timing) vs prod-down (infra).
- Set `pause-bots` label сам (kill-switch для всех)
- POST issue title "🚨 Prod smoke fail rate spike" со списком FAIL issues ```
- result: PROD_SMOKE_SPIKE escalated, paused all bots 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 ## Cost-saving