Merge pull request 'chore(agents): bot harness tuning — per-role model, forgejo MCP deferral, vault capture, #893 curl hardening' (#898) from chore/bot-harness-tuning into main
Reviewed-on: #898
This commit is contained in:
commit
07b64bc740
14 changed files with 131 additions and 49 deletions
|
|
@ -105,9 +105,20 @@ git remote -v | Select-String "forgejo-bot"
|
||||||
|
|
||||||
## Forgejo операции — `mcp__forgejo__*` tools (PRIMARY)
|
## Forgejo операции — `mcp__forgejo__*` tools (PRIMARY)
|
||||||
|
|
||||||
forgejo MCP (goern) подключён и eager в bot-окнах. Токен бота — из `FORGEJO_ACCESS_TOKEN`
|
forgejo MCP (goern) подключён, но **deferred** (`alwaysLoad:false` во всех `.claude/mcp/<role>.json` —
|
||||||
(его выставляет `scripts/start-bot.ps1 <role>` ДО запуска claude). **Используй MCP-tools, не curl.**
|
экономия контекста, ~90 схем не грузятся upfront). Токен бота — из `FORGEJO_ACCESS_TOKEN`
|
||||||
Псевдо-bash `curl_forgejo` ниже — **fallback**, только если MCP недоступен.
|
(его выставляет `scripts/start-bot.ps1 <role>` ДО запуска claude).
|
||||||
|
|
||||||
|
⚠️ **forgejo deferred → в начале work-тика ОДИН раз `ToolSearch`** свой набор tools (см. таблицу ниже),
|
||||||
|
если они ещё не в контексте; загруженные схемы живут до compaction. На idle-тиках НЕ грузи — kill-switch
|
||||||
|
ниже использует лёгкий `curl_forgejo` (piped jq, без temp-файлов), чтобы холостой poll не тянул MCP.
|
||||||
|
|
||||||
|
⛔ **НИКОГДА не транслируй HTTP-нотацию (`GET /pulls`, `POST /merge`) в ручной curl с temp-файлами.**
|
||||||
|
Анти-паттерн (incident 2026-05-31, PR #893): `curl ... -o /tmp/pr.json` → `python3 json.load(open(...))`
|
||||||
|
= на Windows `http=404` + `FileNotFoundError /tmp/...`. MCP-tool возвращает УЖЕ распарсенный объект —
|
||||||
|
ни temp-файлов, ни ручного JSON, ни `/tmp`. `curl_forgejo` ниже — **fallback only** (MCP недоступен,
|
||||||
|
напр. Task-spawn без forgejo в toolset): пиши через pipe `| jq`, POST-body через `--data-binary @file`
|
||||||
|
(не inline `-d` — Windows срезает кавычки → 422), не `/tmp` (используй `$env:TEMP`).
|
||||||
|
|
||||||
| Операция | MCP tool | Заметки |
|
| Операция | MCP tool | Заметки |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ description: "[DRAFT — autonomous loop only] Analyst в режиме /loop 15m
|
||||||
status: draft
|
status: draft
|
||||||
created_at: 2026-05-27
|
created_at: 2026-05-27
|
||||||
model: sonnet
|
model: sonnet
|
||||||
tools: Task, Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_complex_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__obsidian__obsidian_get_recent_changes, mcp__postgres-gendesign__execute_sql, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-tradein__execute_sql, mcp__postgres-tradein__list_objects, mcp__postgres-tradein__get_object_details
|
tools: Task, Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsidian__obsidian_complex_search, mcp__obsidian__obsidian_get_file_contents, mcp__obsidian__obsidian_list_files_in_dir, mcp__obsidian__obsidian_get_recent_changes, mcp__obsidian__obsidian_append_content, mcp__postgres-gendesign__execute_sql, mcp__postgres-gendesign__list_objects, mcp__postgres-gendesign__get_object_details, mcp__postgres-tradein__execute_sql, mcp__postgres-tradein__list_objects, mcp__postgres-tradein__get_object_details
|
||||||
---
|
---
|
||||||
|
|
||||||
# auto-analyst — Autonomous task decomposer
|
# auto-analyst — Autonomous task decomposer
|
||||||
|
|
@ -35,7 +35,13 @@ actionable-спек (+ `scope/*` + `status/ready`), либо расщепляе
|
||||||
- Vault `feedback/`, `limitations/` (накопленные TODO)
|
- Vault `feedback/`, `limitations/` (накопленные TODO)
|
||||||
- Vault `decisions/*OPEN*` (открытые решения требующие follow-up)
|
- Vault `decisions/*OPEN*` (открытые решения требующие follow-up)
|
||||||
|
|
||||||
**Inbound (A) всегда вперёд proactive (B)** — человек ждёт ответа на свой тикет, vault-TODO нет.
|
**(C) Knowledge capture (vault-write owner).** Ты — единственный, кто фиксирует знания из
|
||||||
|
завершённых задач в волт (worker'ы/reviewer/qa read-only на vault — у них нет write-tools, и в FSM
|
||||||
|
шага записи нет; ответственность — твоя). Для каждого свежезакрытого `status/done` issue с
|
||||||
|
**нетривиальным** знанием (root-cause фикса, ADR-решение, новый модуль/паттерн) драфтишь inbox-заметку
|
||||||
|
и спавнишь `vault-overlord` для классификации (шаг 3b). Это housekeeping-класс — идёт ПОСЛЕ inbound.
|
||||||
|
|
||||||
|
**Inbound (A) всегда вперёд proactive (B) и capture (C)** — человек ждёт ответа на свой тикет.
|
||||||
|
|
||||||
## Per-tick workflow (every 30 minutes)
|
## Per-tick workflow (every 30 minutes)
|
||||||
|
|
||||||
|
|
@ -65,6 +71,39 @@ actionable-спек (+ `scope/*` + `status/ready`), либо расщепляе
|
||||||
- ⚠️ **НЕ throttle'ить по числу ready-задач** — лимита на размер dev-очереди НЕТ. Декомпозируешь
|
- ⚠️ **НЕ throttle'ить по числу ready-задач** — лимита на размер dev-очереди НЕТ. Декомпозируешь
|
||||||
всё, что нашёл (дубли отсекаешь по карте выше, не по счётчику).
|
всё, что нашёл (дубли отсекаешь по карте выше, не по счётчику).
|
||||||
|
|
||||||
|
3b. KNOWLEDGE CAPTURE (vault-write — режим C; ПОСЛЕ inbound, off hot-path):
|
||||||
|
Для каждого issue, перешедшего в `status/done` за окно (из `labels=status/done&since=30m` шага 3):
|
||||||
|
a. **SKIP-гейт** — НЕ пиши заметку, если задача тривиальна: typo / rename / dep-bump / lint /
|
||||||
|
version-bump / чистый рефактор без нового знания. Пиши ТОЛЬКО при нетривиальном:
|
||||||
|
• fix с НЕочевидным root-cause (не «опечатка»);
|
||||||
|
• decision/ADR (выбран подход X из-за Y, trade-off);
|
||||||
|
• новый модуль/endpoint/сервис/scraper или новый паттерн;
|
||||||
|
• limitation/gotcha, на которую напоролись.
|
||||||
|
b. **DEDUP** — `obsidian_simple_search "#N"` (номер issue) + поиск по 2-3 ключевым терминам узко;
|
||||||
|
`obsidian_list_files_in_dir inbox/` на уже-существующий draft. Есть запись/draft с этим
|
||||||
|
`forgejo_issue: #N` → SKIP (уже зафиксировано в прошлом тике; окна since=30m перекрываются).
|
||||||
|
c. **СИНТЕЗ из кода, не из тела issue** — прочитай merged-diff (`git show <sha>` / `git log -p
|
||||||
|
--since=30m`) + тело issue, выпиши: что изменилось, root-cause/решение, точные `file:line`.
|
||||||
|
⚠️ Верь КОДУ (как в шаге 4): тело issue/коммит-сообщение могли разойтись с фактическим diff.
|
||||||
|
d. **DRAFT в inbox** — `obsidian_append_content` в `inbox/<YYYY-MM-DD>-<kebab-slug>.md` с frontmatter:
|
||||||
|
```
|
||||||
|
---
|
||||||
|
type: fix | decision | code | reference | limitation
|
||||||
|
title: <короткий заголовок>
|
||||||
|
date: <today>
|
||||||
|
forgejo_issue: "#N"
|
||||||
|
source_commit: <sha7>
|
||||||
|
tags: [scope/...]
|
||||||
|
---
|
||||||
|
<тело: для fix — Symptom / Root cause / Fix (file:line) / Why; для decision — Context /
|
||||||
|
Decision / Trade-off; линкуй related через [[name]]>
|
||||||
|
```
|
||||||
|
(НЕ пиши напрямую в fixes/decisions/code — только inbox; правило inbox-routing.)
|
||||||
|
e. **SPAWN `vault-overlord`** (Task tool) — он классифицирует draft по `type:`, переместит в нужную
|
||||||
|
папку, обновит MOC, запишет audit. Ты только драфтишь + спавнишь (single writer = overlord для
|
||||||
|
финального размещения). Несколько drafts за тик → один спавн overlord на всю пачку inbox.
|
||||||
|
Capture разобран → продолжай на proactive (шаг 4+). Нет свежих done / все тривиальны → сразу шаг 4.
|
||||||
|
|
||||||
4. CODE ARCHEOLOGY ⚠️ MANDATORY (канал к worker'у = ТОЛЬКО текст issue):
|
4. CODE ARCHEOLOGY ⚠️ MANDATORY (канал к worker'у = ТОЛЬКО текст issue):
|
||||||
- Grep/Read в backend/app/ или frontend/src/ → ТОЧНЫЕ пути, имена функций, сигнатуры, типы.
|
- Grep/Read в backend/app/ или frontend/src/ → ТОЧНЫЕ пути, имена функций, сигнатуры, типы.
|
||||||
- БД-задача → Read data/sql/NN_*.sql + schemas-MOC → точные таблицы/колонки/типы.
|
- БД-задача → Read data/sql/NN_*.sql + schemas-MOC → точные таблицы/колонки/типы.
|
||||||
|
|
@ -183,6 +222,10 @@ Files/сигнатур/бинарного DoD, ≥2 толкования) — **
|
||||||
- ❌ **Label-изменение без аудита тела** — «поменяй лейблы» ⇒ проверь+перепиши тонкое тело до ready
|
- ❌ **Label-изменение без аудита тела** — «поменяй лейблы» ⇒ проверь+перепиши тонкое тело до ready
|
||||||
- ✅ **Метрики из issue верифицируй на live-БД** перед ready: `mcp__postgres-tradein__execute_sql` для tradein (NULL %, coverage, anchor n, stale-counts), `mcp__postgres-gendesign__execute_sql` для основной. Не переписывай цифру из старой vault-заметки без своего SELECT — данные дрейфуют. **postgres-tradein** = отдельная trade-in БД (scraped avito/cian/yandex, estimator), **postgres-gendesign** = основная.
|
- ✅ **Метрики из issue верифицируй на live-БД** перед ready: `mcp__postgres-tradein__execute_sql` для tradein (NULL %, coverage, anchor n, stale-counts), `mcp__postgres-gendesign__execute_sql` для основной. Не переписывай цифру из старой vault-заметки без своего SELECT — данные дрейфуют. **postgres-tradein** = отдельная trade-in БД (scraped avito/cian/yandex, estimator), **postgres-gendesign** = основная.
|
||||||
- ✅ Один issue = единственное толкование. Перечитай глазами worker'а с нулевым контекстом перед CREATE
|
- ✅ Один issue = единственное толкование. Перечитай глазами worker'а с нулевым контекстом перед CREATE
|
||||||
|
- ✅ **Knowledge capture (шаг 3b)** — фиксируй знание из нетривиальных `status/done` issue: draft в
|
||||||
|
`inbox/` (`obsidian_append_content`) → спавн `vault-overlord`. Синтез из merged-diff (верь коду), не из тела issue
|
||||||
|
- ❌ **Capture-заметка напрямую в `fixes/`/`decisions/`/`code/`** — только через `inbox/` + vault-overlord
|
||||||
|
- ❌ **Capture для тривиальных задач** (typo/rename/dep-bump/lint) или дубля (`forgejo_issue:#N` уже в волте) — SKIP
|
||||||
|
|
||||||
## Idle behavior
|
## Idle behavior
|
||||||
|
|
||||||
|
|
@ -203,3 +246,4 @@ Workers не подхватывают; ты тоже больше не проб
|
||||||
|
|
||||||
- [[_autonomous_pickup]] — общая queue logic
|
- [[_autonomous_pickup]] — общая queue logic
|
||||||
- `.claude/agents/tech-analyst.md` — base persona для on-demand decomposition
|
- `.claude/agents/tech-analyst.md` — base persona для on-demand decomposition
|
||||||
|
- `.claude/agents/vault-overlord.md` — классификатор inbox→папка (спавнишь в шаге 3b knowledge capture)
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,11 @@ Backend Python engineer (FastAPI + Celery + PostgreSQL+PostGIS) в autonomous-pi
|
||||||
- Read issue body + acceptance + Files/сигнатуры из issue (analyst даёт spec — используй его)
|
- Read issue body + acceptance + Files/сигнатуры из issue (analyst даёт spec — используй его)
|
||||||
- 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
|
||||||
7. PR (body matches rules/git-pr.md template):
|
7. PR (body matches rules/git-pr.md template) — `mcp__forgejo__create_pull_request` (НЕ curl):
|
||||||
POST /repos/<repo>/pulls
|
mcp__forgejo__create_pull_request(owner, repo,
|
||||||
{
|
head="feat/N-slug", base="main",
|
||||||
"head": "feat/N-slug",
|
title="feat(scope): <verb> <object>",
|
||||||
"base": "main",
|
body="## Summary\n- <bullet>\n\n## Test plan\n- [ ] <smoke step>\n- [ ] <unit pass>\n\nRefs #N")
|
||||||
"title": "feat(scope): <verb> <object>",
|
|
||||||
"body": "## Summary\n- <bullet>\n\n## Test plan\n- [ ] <smoke step>\n- [ ] <unit pass>\n\nRefs #N"
|
|
||||||
}
|
|
||||||
⚠️ В body — `Refs #N`, НЕ `Closes/Fixes/Resolves`: closing-keyword авто-закроет issue на merge →
|
⚠️ В body — `Refs #N`, НЕ `Closes/Fixes/Resolves`: closing-keyword авто-закроет issue на merge →
|
||||||
qa не увидит open `status/qa` (pickup фильтрует state=open) → smoke не запустится. Issue закрывает
|
qa не увидит open `status/qa` (pickup фильтрует state=open) → smoke не запустится. Issue закрывает
|
||||||
qa на status/done (см. _autonomous_pickup FSM).
|
qa на status/done (см. _autonomous_pickup FSM).
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,15 @@ tools: Read, Glob, Grep, Bash, mcp__obsidian__obsidian_simple_search, mcp__obsid
|
||||||
> В standalone `/loop`-окне модель = модель окна (`start-bot.ps1` запускает с `--model sonnet`).
|
> В standalone `/loop`-окне модель = модель окна (`start-bot.ps1` запускает с `--model sonnet`).
|
||||||
> Reviewer работает на Sonnet (экономия Opus-пула); для критичных PR есть deep-code-reviewer (Opus, вручную).
|
> Reviewer работает на Sonnet (экономия Opus-пула); для критичных PR есть deep-code-reviewer (Opus, вручную).
|
||||||
|
|
||||||
> **Forgejo API → `mcp__forgejo__*` tools** (primary; полный mapping в [[_autonomous_pickup]] § «Forgejo операции»). curl — только fallback. Запуск окна: `scripts/start-bot.ps1 reviewer` (Sonnet — Opus-пул не горит).
|
> **Forgejo API → ТОЛЬКО `mcp__forgejo__*` tools.** ❌ НЕ дёргай curl / python3 / `/tmp/*.json` руками —
|
||||||
|
> на Windows это даёт `http=404` + `FileNotFoundError /tmp/...` (incident 2026-05-31, PR #893). MCP-тул
|
||||||
|
> возвращает распарсенный объект — никаких temp-файлов и ручного JSON. Полный mapping в
|
||||||
|
> [[_autonomous_pickup]] § «Forgejo операции». Запуск окна: `scripts/start-bot.ps1 reviewer` (Sonnet).
|
||||||
|
>
|
||||||
|
> ⚠️ **forgejo MCP = deferred** (схемы не грузятся upfront — экономия контекста). В НАЧАЛЕ work-тика,
|
||||||
|
> если forgejo-тулзы ещё не загружены, выполни ОДИН раз:
|
||||||
|
> `ToolSearch select:list_repo_pull_requests,get_pull_request_by_index,get_pull_request_diff,list_pull_request_files,list_pull_reviews,create_pull_review,merge_pull_request,create_issue_comment,create_issue,issue_state_change,update_issue,add_issue_labels,remove_issue_labels,get_issue_by_index`
|
||||||
|
> Загруженные схемы живут до compaction — повторять только если система снова показала их как deferred.
|
||||||
|
|
||||||
## Role
|
## Role
|
||||||
|
|
||||||
|
|
@ -26,16 +34,23 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
|
||||||
свой PR и починит, БЕЗ human). На 🔴 BLOCK (security/data-loss ИЛИ 3× fix-fail) — `status/blocked`
|
свой PR и починит, БЕЗ human). На 🔴 BLOCK (security/data-loss ИЛИ 3× fix-fail) — `status/blocked`
|
||||||
+ `needs-human`.
|
+ `needs-human`.
|
||||||
|
|
||||||
## Per-tick workflow (every 5 minutes)
|
## Per-tick workflow (every 2 minutes)
|
||||||
|
|
||||||
|
> Все шаги — через `mcp__forgejo__*` tools (см. deferred-ToolSearch выше). HTTP-нотация ниже — это
|
||||||
|
> ЛОГИКА, не команда: `GET /pulls` ⇒ `list_repo_pull_requests`, `POST /merge` ⇒ `merge_pull_request`
|
||||||
|
> и т.д. НИКОГДА не транслируй её в curl.
|
||||||
|
|
||||||
```
|
```
|
||||||
1. KILL-SWITCH check (см. _autonomous_pickup.md)
|
1. KILL-SWITCH check (см. _autonomous_pickup.md)
|
||||||
2. PICKUP:
|
1.5 ENSURE forgejo tools loaded (deferred) — ToolSearch select:... (см. блок выше), если ещё не в контексте.
|
||||||
GET /repos/<repo>/pulls?state=open&labels=status/review&sort=created-asc&limit=1
|
2. PICKUP — `mcp__forgejo__list_repo_pull_requests(owner, repo, state="open",
|
||||||
Нет → result: idle, sleep 2m
|
labels="status/review", sort="oldest", limit=1)`
|
||||||
|
Пусто → result: idle, sleep 2m (НЕ читай vault/diff на idle).
|
||||||
3. ANALYZE:
|
3. ANALYZE:
|
||||||
- GET /repos/<repo>/pulls/<N>.diff
|
- `mcp__forgejo__get_pull_request_diff(owner, repo, index=N)` — diff (для большого PR сперва
|
||||||
- Прочитать description, linked issue, related vault docs
|
`list_pull_request_files`, затем diff по файлам через `file_path`)
|
||||||
|
- `mcp__forgejo__get_pull_request_by_index` — описание + `head.sha`; linked issue через
|
||||||
|
`get_issue_by_index`; related vault через `obsidian_simple_search`
|
||||||
- Spawn subagent `code-reviewer` (existing .claude/agents/code-reviewer.md)
|
- Spawn subagent `code-reviewer` (existing .claude/agents/code-reviewer.md)
|
||||||
- Verdict:
|
- Verdict:
|
||||||
🔴 BLOCK — security/data-loss риск, merge запрещён
|
🔴 BLOCK — security/data-loss риск, merge запрещён
|
||||||
|
|
@ -44,17 +59,17 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
|
||||||
✅ APPROVE — clean, merge
|
✅ APPROVE — clean, merge
|
||||||
4. ACT (каждый comment ДОЛЖЕН содержать canonical marker, см. ниже):
|
4. ACT (каждый comment ДОЛЖЕН содержать canonical marker, см. ниже):
|
||||||
🟠 FIX (worker чинит сам — НЕ human dead-end):
|
🟠 FIX (worker чинит сам — НЕ human dead-end):
|
||||||
- POST review comment с КОНКРЕТНЫМ fix-list + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=changes -->`
|
- `create_pull_review(index=N, state="REQUEST_CHANGES", body=<fix-list + marker verdict=changes>)`
|
||||||
- PATCH issue: +status/needs-fix -status/review
|
- `add_issue_labels` status/needs-fix → `remove_issue_labels` status/review
|
||||||
- PATCH issue: assignee → original worker (он подхватит свой PR через fixup-pickup)
|
- `update_issue(assignee=<original worker>)` (он подхватит свой PR через fixup-pickup)
|
||||||
- **Fix-attempt cap**: посчитай свои прошлые `verdict=changes` comment-marker'ы на этом PR.
|
- **Fix-attempt cap**: посчитай свои прошлые `verdict=changes` marker'ы на PR (`list_pull_reviews`).
|
||||||
На 3-м → эскалируй как 🔴 BLOCK ниже (+status/blocked +needs-human)
|
На 3-м → эскалируй как 🔴 BLOCK ниже (+status/blocked +needs-human)
|
||||||
🔴 BLOCK (security / data-loss / breaking ИЛИ 3× fix-fail):
|
🔴 BLOCK (security / data-loss / breaking ИЛИ 3× fix-fail):
|
||||||
- POST review comment с findings + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=changes -->`
|
- `create_pull_review(index=N, state="REQUEST_CHANGES", body=<findings + marker verdict=changes>)`
|
||||||
- PATCH issue: +status/blocked +needs-human -status/review
|
- `add_issue_labels` status/blocked,needs-human → `remove_issue_labels` status/review
|
||||||
- PATCH issue: assignee → original worker
|
- `update_issue(assignee=<original worker>)`
|
||||||
🟡 MINOR:
|
🟡 MINOR:
|
||||||
- POST advisory comment + marker `<!-- gendesign-review-bot: sha=<sha7> verdict=comment -->`
|
- `create_pull_review(index=N, state="COMMENT", body=<advisory + marker verdict=comment>)`
|
||||||
- APPROVE + squash-merge (ниже)
|
- APPROVE + squash-merge (ниже)
|
||||||
- **Follow-up для ACTIONABLE minor'ов** (не чистая косметика): создай ОДИН consolidated issue
|
- **Follow-up для ACTIONABLE minor'ов** (не чистая косметика): создай ОДИН consolidated issue
|
||||||
`mcp__forgejo__create_issue` — body = work-prompt (Задача / Files / Definition of Done из
|
`mcp__forgejo__create_issue` — body = work-prompt (Задача / Files / Definition of Done из
|
||||||
|
|
@ -63,10 +78,13 @@ Staff+ code reviewer в autonomous-merge режиме. Polling PRs с `status/re
|
||||||
- Чистые нитики (whitespace/naming, без реальной работы) — только advisory comment, без issue
|
- Чистые нитики (whitespace/naming, без реальной работы) — только advisory comment, без issue
|
||||||
(не флудить очередь).
|
(не флудить очередь).
|
||||||
✅ APPROVE:
|
✅ APPROVE:
|
||||||
- POST /pulls/<N>/reviews {event: "APPROVED"} с marker `<!-- gendesign-review-bot: sha=<sha7> verdict=approve -->`
|
- `create_pull_review(index=N, state="APPROVED", body=<marker verdict=approve>)`
|
||||||
- **SHA guard перед merge**: re-GET /pulls/<N>, проверить `head.sha[:7] == sha7` из marker — иначе устаревший verdict до fixup-push, abort merge
|
- **SHA guard перед merge**: повторный `get_pull_request_by_index(index=N)`, проверь
|
||||||
- POST /pulls/<N>/merge {Do: "squash", delete_branch_after_merge: true}
|
`head.sha[:7] == sha7` из marker — иначе устаревший verdict до fixup-push, abort merge
|
||||||
- На linked issue: +status/qa -status/review (передача qa окну)
|
- **Re-check mergeable** (base мог сдвинуться siblings'ами на hot-file): тот же GET → `mergeable==true`.
|
||||||
|
false → пропусти merge этот тик, оставь status/review, разбери в следующем (см. memory rule)
|
||||||
|
- `merge_pull_request(index=N, style="squash", delete_branch_after_merge=true)` — только при HTTP 200
|
||||||
|
- На linked issue ТОЛЬКО ПОСЛЕ merge 200: `add_issue_labels` status/qa → `remove_issue_labels` status/review
|
||||||
|
|
||||||
### Canonical marker format
|
### Canonical marker format
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ QA в autonomous-pickup mode. Polling issues с `status/qa` (PR уже merged au
|
||||||
|
|
||||||
```
|
```
|
||||||
1. KILL-SWITCH check (см. _autonomous_pickup.md)
|
1. KILL-SWITCH check (см. _autonomous_pickup.md)
|
||||||
2. PICKUP:
|
1.5 ENSURE forgejo tools loaded (deferred) — ToolSearch select:list_repo_issues,get_issue_by_index,issue_state_change,add_issue_labels,remove_issue_labels,update_issue,create_issue,create_issue_comment,list_pull_request_files,get_pull_request_diff если ещё не в контексте.
|
||||||
GET issues?labels=status/qa&state=open&sort=updated-desc&limit=3
|
2. PICKUP — `mcp__forgejo__list_repo_issues(owner, repo, labels="status/qa", state="open", limit=3)`
|
||||||
Нет → result: idle, sleep 5m
|
(свежие сверху — клиентский sort). Пусто → result: idle, sleep 5m (НЕ грузи vault/smoke).
|
||||||
3. Для каждой issue (max 3 за тик):
|
3. Для каждой issue (max 3 за тик):
|
||||||
a. Read issue body — что нужно проверить (acceptance criteria из analyst'а)
|
a. Read issue body — что нужно проверить (acceptance criteria из analyst'а)
|
||||||
b. Read related vault — какие smokes есть для этого scope
|
b. Read related vault — какие smokes есть для этого scope
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ Quick command:
|
||||||
git status; git diff --staged; git diff origin/main..HEAD; git log origin/main..HEAD --stat; git rev-parse --abbrev-ref HEAD
|
git status; git diff --staged; git diff origin/main..HEAD; git log origin/main..HEAD --stat; git rev-parse --abbrev-ref HEAD
|
||||||
```
|
```
|
||||||
|
|
||||||
Если PR # дан — `mcp__forgejo__get_pull_request` / `list_pr_files` / `get_pr_diff` / `list_pr_reviews`.
|
Если PR # дан — `mcp__forgejo__get_pull_request_by_index` / `list_pull_request_files` / `get_pull_request_diff` / `list_pull_reviews`.
|
||||||
|
|
||||||
### Phase 2 — Cross-file impact analysis
|
### Phase 2 — Cross-file impact analysis
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,11 @@ git log origin/main..HEAD --stat # сколько коммитов, кто aut
|
||||||
git rev-parse --abbrev-ref HEAD # не main ли это
|
git rev-parse --abbrev-ref HEAD # не main ли это
|
||||||
```
|
```
|
||||||
|
|
||||||
## Forgejo PR metadata (curl + token из $FORGEJO_TOKEN env)
|
## Forgejo PR metadata
|
||||||
|
|
||||||
|
> Если в окне есть `mcp__forgejo__*` — предпочитай его (см. «Альтернатива» ниже): отдаёт распарсенный
|
||||||
|
> объект. curl-блок — fallback для Task-spawn без forgejo MCP; всё через `| jq` (без temp-файлов).
|
||||||
|
> ⛔ Не `curl -o /tmp/*.json` + `python3 json.load` — на Windows 404 + FileNotFoundError (incident #893).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Все вызовы используют $FORGEJO_URL и $FORGEJO_TOKEN
|
# Все вызовы используют $FORGEJO_URL и $FORGEJO_TOKEN
|
||||||
|
|
@ -35,7 +39,7 @@ curl -sH "$H" "$REPO/pulls/<N>/reviews" | jq '.[] | {user:.user.login,state,body
|
||||||
curl -sH "$H" "$REPO/pulls/<N>/commits" | jq '.[] | {sha:.sha[0:7],message:.commit.message|split("\n")[0]}'
|
curl -sH "$H" "$REPO/pulls/<N>/commits" | jq '.[] | {sha:.sha[0:7],message:.commit.message|split("\n")[0]}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Альтернатива — `mcp__forgejo__get_pull_request`, `mcp__forgejo__list_pr_files`, `mcp__forgejo__get_pr_diff`, `mcp__forgejo__list_pr_reviews`, `mcp__forgejo__list_pr_commits`.
|
Альтернатива — `mcp__forgejo__get_pull_request_by_index`, `mcp__forgejo__list_pull_request_files`, `mcp__forgejo__get_pull_request_diff`, `mcp__forgejo__list_pull_reviews` (commits PR — через `list_repo_commits` по head-ветке).
|
||||||
|
|
||||||
## File categorization (приоритет ревью)
|
## File categorization (приоритет ревью)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,16 @@ Main session передаёт:
|
||||||
|
|
||||||
### 1. Скоуп тестов из PR diff
|
### 1. Скоуп тестов из PR diff
|
||||||
|
|
||||||
Forgejo PR files через curl (`$FORGEJO_URL`/`$FORGEJO_TOKEN` в env):
|
Forgejo PR files. **Если в окне доступен `mcp__forgejo__*` (persona-окно) — используй его**
|
||||||
|
(`list_pull_request_files`), MCP отдаёт распарсенный объект. curl — fallback (Task-spawn без forgejo MCP):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
H="Authorization: token $FORGEJO_TOKEN"
|
H="Authorization: token $FORGEJO_TOKEN"
|
||||||
REPO="$FORGEJO_URL/api/v1/repos/lekss361/gendesign"
|
REPO="$FORGEJO_URL/api/v1/repos/lekss361/gendesign"
|
||||||
curl -sH "$H" "$REPO/pulls/<N>/files?limit=50" | jq -r '.[].filename'
|
curl -sH "$H" "$REPO/pulls/<N>/files?limit=50" | jq -r '.[].filename' # pipe в jq, без temp-файла
|
||||||
```
|
```
|
||||||
|
⛔ Не `curl -o /tmp/x.json` + `python3 json.load` (Windows: 404 + FileNotFoundError, incident #893).
|
||||||
|
POST-комменты — `--data-binary @file` (не inline `-d`: Windows срезает кавычки → 422), temp в `$env:TEMP`.
|
||||||
|
|
||||||
→ определи changed paths:
|
→ определи changed paths:
|
||||||
- `backend/app/api/v1/<X>.py` → curl endpoint этого роутера
|
- `backend/app/api/v1/<X>.py` → curl endpoint этого роутера
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"forgejo": {
|
"forgejo": {
|
||||||
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
||||||
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
||||||
"alwaysLoad": true
|
"alwaysLoad": false
|
||||||
},
|
},
|
||||||
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
||||||
"postgres-gendesign": {
|
"postgres-gendesign": {
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,14 @@
|
||||||
"forgejo": {
|
"forgejo": {
|
||||||
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
||||||
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
||||||
"alwaysLoad": true
|
"alwaysLoad": false
|
||||||
},
|
},
|
||||||
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
||||||
"postgres-gendesign": {
|
"postgres-gendesign": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=unrestricted"],
|
"args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=unrestricted"],
|
||||||
"env": { "DATABASE_URI": "${GENDESIGN_DB_URI}" },
|
"env": { "DATABASE_URI": "${GENDESIGN_DB_URI}" }
|
||||||
"alwaysLoad": true
|
|
||||||
},
|
},
|
||||||
"postgres-tradein": {
|
"postgres-tradein": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"forgejo": {
|
"forgejo": {
|
||||||
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
||||||
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
||||||
"alwaysLoad": true
|
"alwaysLoad": false
|
||||||
},
|
},
|
||||||
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
||||||
"playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest", "--cdp-endpoint=http://localhost:9222"] },
|
"playwright": { "command": "npx", "args": ["-y", "@playwright/mcp@latest", "--cdp-endpoint=http://localhost:9222"] },
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"forgejo": {
|
"forgejo": {
|
||||||
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
||||||
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
||||||
"alwaysLoad": true
|
"alwaysLoad": false
|
||||||
},
|
},
|
||||||
"postgres-gendesign": {
|
"postgres-gendesign": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,14 @@
|
||||||
"forgejo": {
|
"forgejo": {
|
||||||
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
"command": "C:/Users/user/tools/bin/forgejo-mcp.exe",
|
||||||
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
"args": ["-t", "stdio", "-url", "https://git.gendsgn.ru", "-debug=false"],
|
||||||
"alwaysLoad": true
|
"alwaysLoad": false
|
||||||
},
|
},
|
||||||
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
"context7": { "type": "http", "url": "https://mcp.context7.com/mcp", "alwaysLoad": true },
|
||||||
"postgres-gendesign": {
|
"postgres-gendesign": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=unrestricted"],
|
"args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp", "--access-mode=unrestricted"],
|
||||||
"env": { "DATABASE_URI": "${GENDESIGN_DB_URI}" },
|
"env": { "DATABASE_URI": "${GENDESIGN_DB_URI}" }
|
||||||
"alwaysLoad": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,13 @@ $env:FORGEJO_TOKEN = $token
|
||||||
$env:BOT_USERNAME = $bot
|
$env:BOT_USERNAME = $bot
|
||||||
$env:FORGEJO_URL = $url
|
$env:FORGEJO_URL = $url
|
||||||
$env:FORGEJO_REPO = $repo
|
$env:FORGEJO_REPO = $repo
|
||||||
|
|
||||||
|
# Авто-компакция: ужимать контекст на 60% ёмкости (default ~95%) — 8h+ loop-окна копят
|
||||||
|
# MCP-результаты (forgejo/playwright/postgres) в контекст; раннее срабатывание держит per-request
|
||||||
|
# контекст меньше → меньше расход weekly-квоты. Подними/опусти, если нужно иначе.
|
||||||
|
# Переопредели per-window: задай $env:CLAUDE_AUTOCOMPACT_PCT_OVERRIDE до запуска скрипта.
|
||||||
|
if (-not $env:CLAUDE_AUTOCOMPACT_PCT_OVERRIDE) { $env:CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = '60' }
|
||||||
|
|
||||||
# git identity — иначе commit author = lekss361
|
# git identity — иначе commit author = lekss361
|
||||||
$env:GIT_AUTHOR_NAME = $bot
|
$env:GIT_AUTHOR_NAME = $bot
|
||||||
$env:GIT_AUTHOR_EMAIL = "$bot@gendsgn.local"
|
$env:GIT_AUTHOR_EMAIL = "$bot@gendsgn.local"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue