docs(agents): auto-analyst verify-before-ready + dedup/loop guardrails

Из инцидентов live-сессии 2026-05-30:
- шаг 4: file:line из vault-заметки НЕВЕРИФИЦИРОВАН — Read+confirm своими глазами
  до вписывания в issue (incident: спека «перевести на JSON», код уже на JSON)
- шаг 7: status/ready только на финальном verified-теле, не «ready→переписываю»
  (incident #697/#699 — воркер смержил по тонкому телу за ~30s)
- «поменяй лейблы» ⇒ также аудит+переписывание тонкого тела до ready
- _autonomous_pickup: dedup-before-create обязателен при параллельных окнах
  (incident #724/#728 vs #726/#727), list_repo_issues всегда с фильтром (token-limit)
- work-as-analyst: один loop-механизм, CronDelete перед CronCreate
- fix stale cross-ref шаг 6 → шаг 8
This commit is contained in:
bot-analyst 2026-05-30 21:15:48 +03:00
parent 950d0f60de
commit ae820d3bd9
3 changed files with 53 additions and 1 deletions

View file

@ -124,6 +124,22 @@ forgejo MCP (goern) подключён и eager в bot-окнах. Токен б
**Gotcha:** на user-репо (`lekss361` — не org) для label-листинга передавай `include_org_labels:false`,
иначе 403 на `/orgs/...`.
**⚠️ Token-limit на `list_repo_issues`:** без фильтра ответ рвёт лимит (видели 59k140k символов →
дамп в файл, тратятся тики на slicing). ВСЕГДА передавай `labels`+`state`+узкий `limit` (напр.
`labels:"status/ready"`, `limit:30`). Не звать без фильтра «посмотреть все issues» — для дедупа
используй `q=<keywords>&state=all&limit=5`, не полный листинг.
**⚠️ Параллельные окна одной роли (analyst/worker) → дубли + взаимное закрытие issues.**
Два окна на одном токене не имеют claim-lock на *создание* issue. Incident 2026-05-30: два
analyst-окна завели #724/#728 vs #726/#727 на те же находки, потом закрыли друг друга →
work-item остался без open-issue, 3 тика на recovery. Защита:
- **Перед /loop**: убедись, что нет второго live-окна твоей роли (спроси человека / проверь recent
issues на свой `bot-<role>` author за последние минуты).
- **Дедуп-before-create ОБЯЗАТЕЛЕН** (не опционален): `list_repo_issues` с `q=<keywords>&state=all`
ПЕРЕД каждым `create_issue`. Совпадение по сути → не создавай, прокомментируй существующий.
- Если коллизия уже произошла — НЕ закрывай вслепую; reopen один канонический, дубли закрой
комментом-ссылкой, проверь что work-item не остался без open-issue.
### Label IDs — goern `add_issue_labels`/`remove_issue_labels` требует ID, НЕ имя!
goern-MCP в add/remove принимает **числовой id** (несмотря на доку «names» — первый add по имени

View file

@ -43,6 +43,13 @@ Read-only tech-analyst в autonomous-pickup mode. Создаёшь Forgejo issue
- БД-задача → Read data/sql/NN_*.sql + schemas-MOC → точные таблицы/колонки/типы.
- Выписывай РЕАЛЬНЫЕ идентификаторы, НЕ плейсхолдеры. Worker строит код только из issue,
без Opus-оркестратора и без vault. Тонкий/расплывчатый issue = broken/флоуд PR.
- ⚠️⚠️ **`file:line` И СИМПТОМ ИЗ VAULT-ЗАМЕТКИ — НЕВЕРИФИЦИРОВАННЫ.** Заметка = указатель
ГДЕ искать, НЕ источник истины. Строки дрейфят, симптом может быть уже исправлен. ПЕРЕД
тем как вписать `file:line` в issue — открой файл через **Read** и подтверди СВОИМИ глазами:
(а) идентификатор существует на этой строке, (б) симптом реально присутствует (не пофикшен
прошлым PR). Конфликт код↔заметка → **верь коду**, заметка устарела; перепиши находку или
отклони её (skip + причина в inbox-стампе). Перенос `file:line` из заметки без своего Read —
запрещён (incident: спека «перевести на JSON», когда код уже на JSON).
5. DECOMPOSE: unprocessed item → 1-3 sub-issues, single-scope, dependency-ordered, estimate S/M/L.
6. NO-AMBIGUITY GATE ⚠️ (перед CREATE — перечитай issue ГЛАЗАМИ worker'а с нулевым контекстом):
- Все пути / имена / типы — ТОЧНЫЕ из archeology, без плейсхолдеров (`<area>`, «соответствующий
@ -88,15 +95,29 @@ Read-only tech-analyst в autonomous-pickup mode. Создаёшь Forgejo issue
"""
labels: ["scope/X", "status/ready" | "status/blocked", "priority/pN"]
estimate S(<2h)/M(2-8h)/L(>8h — ещё дроби) — первым comment (`mcp__forgejo__create_issue_comment`)
⚠️ **`status/ready` = финальное тело.** Воркер подхватывает ready за ~30s — переписать спеку
ПОСЛЕ постинга уже поздно (он строит из мусора). Создавай issue СРАЗУ с финальным
(verified+gate-passed) телом ИЛИ держи `status/blocked`, пока дорабатываешь. Паттерн «создал
ready → потом переписываю тело» — ЗАПРЕЩЁН (incident #697/#699: воркер смержил по тонкому телу
до переписи).
8. UPDATE inbox-файла — frontmatter `forgejo_issue: #N` для де-дупа
9. result: created N issues (ids: #X #Y #Z) from inbox/<file>
```
## Запрос «поменяй лейблы» ⇒ также аудит тела issue
Когда человек просит «поменяй/повесь лейблы» на существующий issue — это НЕ «только лейблы».
Для каждого затронутого issue: прочитай тело, и если оно тонкое/двусмысленное (нет точных
Files/сигнатур/бинарного DoD, ≥2 толкования) — **сначала** code-archeology + перепиши в спек по
шаблону шага 7, и только потом ставь `status/ready`. Двусмысленные → доуточни или `needs-human`,
НЕ ready. Лейбл `status/ready` обещает воркеру actionable-спек; повесить его на 2-строчное тело =
нарушение NO-AMBIGUITY GATE. (Правило from human-feedback 2026-05-30.)
## Decomposition rules
- **Single scope per issue** — никаких "backend+frontend"
- **Цепочки через depends-on** — frontend issue идёт со `status/blocked` пока backend не done
- **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).
- **De-duplication** — preferred: vault frontmatter `forgejo_issue: #N` на inbox-файле (шаг 8). Fallback при отсутствии frontmatter: `GET issues?q=<keywords>&state=all&limit=5` + sanity check (fuzzy match unreliable). ⚠️ При параллельных окнах дедуп-before-create ОБЯЗАТЕЛЕН (см. `_autonomous_pickup.md` «Параллельные окна»).
- **Estimate** — S/M/L в комментах
- **Priority** — default p2; p0 только для прод-incident / blocker
@ -112,6 +133,11 @@ Read-only tech-analyst в autonomous-pickup mode. Создаёшь Forgejo issue
endpoint», «быстро») — только точные идентификаторы из archeology
- ❌ **Не-бинарный Definition of Done** («работает корректно») — каждый пункт = команда + ожидаемый результат
- ❌ Постить `status/ready`, не пройдя **NO-AMBIGUITY GATE** (шаг 6) — двусмысленность → доуточни или +needs-human
- ❌ **Вписывать `file:line` из vault-заметки без своего Read** (шаг 4) — строки дрейфят, симптом
может быть пофикшен; verify СВОИМИ глазами или не вписывай
- ❌ **`status/ready` → потом переписываю тело** — ready только на финальном verified-теле; иначе
держи `status/blocked` (воркер берёт ready за ~30s)
- ❌ **Label-изменение без аудита тела** — «поменяй лейблы» ⇒ проверь+перепиши тонкое тело до ready
- ✅ Один issue = единственное толкование. Перечитай глазами worker'а с нулевым контекстом перед CREATE
## Idle behavior

View file

@ -64,6 +64,16 @@ Write-Host "✓ PAT belongs to $($me.login) — analyst persona ready"
- ❌ НЕ постить ready с двусмысленностью (≥2 толкований) — доуточни или +needs-human
- ❌ НЕ flooding — stop при ready queue ≥ 10
- ❌ НЕ trigger себя через Task tool
- ❌ НЕ вписывать `file:line` из vault-заметки без своего Read — строки дрейфят, симптом мог быть пофикшен (см. auto-analyst.md шаг 4)
- ❌ НЕ ставить `status/ready` и потом переписывать тело — ready только на финальном verified-теле, иначе `status/blocked`
- ❌ «Поменяй лейблы» ⇒ также проверить+переписать тонкое тело до ready (не только лейбл)
- ❌ Дубли при параллельных окнах — дедуп `list_repo_issues q=<keywords>&state=all` ПЕРЕД каждым create
## Loop-механизм (один, без дублей)
Используй ОДИН loop-механизм за раз. При смене интервала — `CronDelete` старого job ПЕРЕД
`CronCreate` нового (иначе двойной firing). Не смешивай cron-loop и ScheduleWakeup-dynamic на одном
окне. (incident: несколько крон-джоб + wakeup → риск double-tick.)
## Готов?