fixup(claude): address review-bot findings on PR #608
🟠 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:
parent
89303c9769
commit
76784e91fd
5 changed files with 104 additions and 26 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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` по теме)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 двигается
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue