A
AgentStaff
infaton/AgentStaff · арендатор agentstaff
обновлено 08.09.2026 10:57:29 UTC
версия 0.10.0

Что запланировано и что доказано

Живой снимок проекта: карточки, PR, проверки и вердикты читаются из GitHub, руками не правятся.

Не учтено вердиктов: 1.
всего
79
карточек в проекции
в работе
0
аренда, правка, открытый PR
на приёмке
0
CI зелёный, ждут вердикта
принято и готово
77
вердикт PASS и слитые
Дорожная карта по вехам
M0 Каркас
1/1
срок 05.09.2026
M1 Доска читает GitHub
7/7
срок 19.09.2026
M2 Очередь и аренда
6/6
срок 03.10.2026
M3 События и вебхуки
8/8
срок 17.10.2026
M4 Приёмка другим вендором
3/3
срок 31.10.2026
M5 Золотой путь пройден
14/14
срок 14.11.2026
M6 Продукт в бою
8/8
M7 Движок для приложений владельца
8/8
M8 Команда внутри
16/16
Без вехи
6/8
Карточка
Состояние
Факт
Следующий шаг
#1
Каркас: стандарт, ADR, контракты, CI, машина состояний
cmd, internal/domain, tools/limits
Готово
issue #1 закрыт, PR не найден
готово

Цель

Репозиторий с правилами, из которого можно вести разработку

Команда приёмки

make check

Ожидаемый вывод

limits: ok / forbidden: ok

Ссылки

Issue #1 PR нет

Вердикты

вердиктов нет

#2
github: чтение Issues, PR, проверок и комментариев
internal/githubPASS
Готово
PR #16 · слит · CI success · PASS 33ff3f9 от delovdelo · FAIL ×1
готово

Цель

Клиент GitHub REST только на чтение: карточки, PR с head sha, статус CI, комментарии PASS/FAIL. Токен из GITHUB_TOKEN.

Команда приёмки

go test ./internal/github/... -run TestClient -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #2 PR #16

Вердикты

delovdelo · 33ff3f9 · FAIL · 06.09.2026 16:26 delovdelo · 33ff3f9 · PASS · 06.09.2026 16:27

#3
domain: состояние карточки выводится из фактов GitHub
internal/domainPASS
Готово
PR #17 · слит · CI success · PASS 7b1e508 от delovdelo · FAIL ×1
готово

Цель

Функция Derive(issue, pr, ci, verdicts) → State по машине переходов; никаких ручных статусов

Команда приёмки

go test ./internal/domain/... -run TestDerive -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #3 PR #17

Вердикты

delovdelo · 7b1e508 · FAIL · 06.09.2026 16:25 delovdelo · 7b1e508 · PASS · 06.09.2026 16:28

#4
httpapi: доска в виде владельца
internal/httpapiPASS
Готово
PR #23 · слит · CI success · PASS d184be9 от delovdelo
готово

Цель

GET /board — html/template + owner.css: дорожная карта по вехам (план/факт), четыре сводные карточки, таблица карточек с раскрытием; GET /board.json тот же вид

Команда приёмки

go test ./internal/httpapi/... -run TestBoardRendersOwnerLayout -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #4 PR #23

Вердикты

delovdelo · d184be9 · PASS · 06.09.2026 16:41

#5
cmd: agentstaff serve и status
cmd/agentstaff, internal/appPASS
Готово
PR #24 · слит · CI success · PASS 07cc660 от delovdelo · FAIL ×1
готово

Цель

agentstaff serve поднимает /board и /healthz на порту из PORT; agentstaff status печатает доску текстом

Команда приёмки

go test ./cmd/agentstaff/... ./internal/app/... -run 'TestConfigFromEnv|TestLoader|TestNewCached' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #5 PR #24

Вердикты

delovdelo · 07cc660 · FAIL · 06.09.2026 16:47 delovdelo · 07cc660 · PASS · 06.09.2026 16:49

#6
store: PostgreSQL, пул, миграции
internal/store, migrationsPASS
Готово
PR #33 · слит · CI success · PASS 4dd6345 от delovdelo
готово

Цель

Один пул pgx, миграции с версией в БД, DDL только в migrations/

Команда приёмки

AGENTSTAFF_TEST_DATABASE_URL='postgres://agentstaff@127.0.0.1:55440/agentstaff_test?sslmode=disable' go test ./internal/store/... -run TestStore -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #6 PR #33

Вердикты

delovdelo · 4dd6345 · PASS · 06.09.2026 17:03

#7
queue: аренда с TTL и WIP=1 на исполнителя
internal/queuePASS
Готово
PR #37 · слит · CI success · PASS a5766d2 от delovdelo
готово

Цель

lease(card, worker) на 4 часа; второй lease тому же worker → 409; истечение снимает аренду

Команда приёмки

go test ./internal/queue/... -run TestLease -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #7 PR #37

Вердикты

delovdelo · a5766d2 · PASS · 06.09.2026 17:15

#8
httpapi: POST и DELETE lease
internal/app (фасад аренды), internal/httpapi (маршруты), cmd/agentstaff (подключение)PASS
Готово
PR #42 · слит · CI success · PASS a3cceeb от delovdelo · FAIL ×1
готово

Цель

Маршруты аренды по contracts/http-api.md. httpapi зависит только от app (ADR-0002): app даёт фасад Leaser с собственными типами и ошибками поверх queue

Команда приёмки

go test ./internal/httpapi/... -run TestLeaseRoutes -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #8 PR #42

Вердикты

delovdelo · a3cceeb · FAIL · 06.09.2026 17:50 delovdelo · a3cceeb · PASS · 06.09.2026 17:53

#9
events: outbox Event Contract v1
internal/eventsPASS
Готово
PR #51 · слит · CI success · PASS 94914a4 от delovdelo
готово

Цель

Конверт по contracts/event-contract-v1.md, outbox в БД, at-least-once, идемпотентность по id

Команда приёмки

go test ./internal/events/... -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #9 PR #51

Вердикты

delovdelo · 94914a4 · PASS · 06.09.2026 18:10

#10
github: вебхуки с подписью → события
internal/githubPASS
Готово
PR #49 · слит · CI success · PASS 7cb5c29 от delovdelo
готово

Цель

POST /webhooks/github проверяет X-Hub-Signature-256, превращает pull_request/check_suite/issue_comment в события

Команда приёмки

go test ./internal/github/... -run TestWebhook -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #10 PR #49

Вердикты

delovdelo · 7cb5c29 · PASS · 06.09.2026 18:07

#11
pipeline: переходы по событиям
internal/pipelinePASS
Готово
PR #55 · слит · CI success · PASS 025cc45 от delovdelo
готово

Цель

Состояние карточки меняется только от событий; verdict.stale и verdict.self_rejected не двигают состояние

Команда приёмки

go test ./internal/pipeline/... -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #11 PR #55

Вердикты

delovdelo · 025cc45 · PASS · 06.09.2026 18:28

#12
acceptance: PASS/FAIL, два круга, блокировка
internal/acceptance (новый), internal/domain, internal/pipeline (переход на acceptance)PASS
Готово
PR #65 · слит · CI success · PASS f5eae62 от delovdelo
готово

Цель

Правила вердикта живут в одном пакете acceptance (разбор PASS/FAIL/BLOCKED, законный приёмщик, совпадение sha, счёт кругов, порог блокировки) и используются и domain.Derive (доска), и pipeline (переходы); дубли shaMatches/isAcceptor удалены

Команда приёмки

go test ./internal/acceptance/... -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #12 PR #65

Вердикты

delovdelo · f5eae62 · PASS · 06.09.2026 19:01

#13
Сигнал владельцу: одно сообщение по событию
internal/github (метод записи комментария), internal/app (обработчик события)PASS
Готово
PR #64 · слит · CI success · PASS a53237f от delovdelo
готово

Цель

owner.decision_needed → комментарий с упоминанием владельца в Issue; не чаще одного на карточку

Команда приёмки

go test ./internal/app/... -run TestOwnerSignal -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #13 PR #64

Вердикты

delovdelo · a53237f · PASS · 06.09.2026 18:55

#14
Три карточки прошли путь 1→7, релиз v0.1.0
все
Готово
issue #14 закрыт, PR не найден
готово

Цель

Стоп-критерий версии 0.1 из docs/ЗОЛОТОЙ-ПУТЬ.md: три реальные карточки с исполнителем Opus 5 и приёмщиком Codex

Команда приёмки

git tag --list v0.1.0

Ожидаемый вывод

v0.1.0

Ссылки

Issue #14 PR нет

Вердикты

вердиктов нет

#15
Несколько арендаторов: второй репозиторий владельца только на чтение
cmd/agentstaff, internal/app (реестр арендаторов), internal/httpapi (маршрут /tenants/{t}/board и выбор Source по арендатору)PASS
Готово
PR #96 · слит · CI success · PASS 579a2e0 от delovdelo
готово

Цель

Продукт ведёт несколько арендаторов одновременно: список арендаторов в конфиге (AGENTSTAFF_TENANTS="agentstaff=infaton/AgentStaff,second=<owner>/<repo>" — пути репозиториев допустимы как адреса); для каждого — свой Loader/Board, свой Poller и Runtime (общая база, tenant_id разделяет строки — таблицы уже так устроены); доска и status принимают --tenant/параметр пути /tenants/{t}/board; второй арендатор в 0.1 — только чтение (без аренды и без записи комментариев). Один арендатор без списка — прежнее поведение

Команда приёмки

go test ./cmd/agentstaff/... ./internal/app/... ./internal/httpapi/... -run 'TestConfigFromEnv|TestTenants|TestBoardRendersOwnerLayout' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #15 PR #96

Вердикты

delovdelo · 579a2e0 · PASS · 07.09.2026 03:42

#27
app: срок вехи показывается на день раньше
internal/appPASS
Готово
PR #31 · слит · CI success · PASS f0d0a96 от delovdelo
готово

Цель

GitHub отдаёт due_on как полночь в зоне репозитория, сдвинутую в UTC; на доске должна быть календарная дата вехи из GitHub (06.09, не 05.09). Показывать дату по правилу GitHub: округлять due_on к ближайшей календарной дате в UTC+12/по дате, а не по времени

Команда приёмки

go test ./internal/app/... -run TestMilestoneDue -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #27 PR #31

Вердикты

delovdelo · f0d0a96 · PASS · 06.09.2026 16:57

#28
httpapi: заголовок в блоке hero как в прототипе
internal/httpapiPASS
Готово
PR #32 · слит · CI success · PASS 265dc8c от delovdelo
готово

Цель

Верхняя панель компактная (логотип, репозиторий, штамп), заголовок «Что запланировано и что доказано» и подзаголовок — в блоке .hero под ней, как в эталоне владельца

Команда приёмки

go test ./internal/httpapi/... -run TestBoardRendersOwnerLayout -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #28 PR #32

Вердикты

delovdelo · 265dc8c · PASS · 06.09.2026 16:58

#29
httpapi: панель «Заблокировано честно» для карточек blocked
internal/httpapiPASS
Готово
PR #36 · слит · CI success · PASS 93ec7fd от delovdelo
готово

Цель

Если есть карточки в состоянии blocked — над таблицей блок .focus-panel как в эталоне: номер и название первой заблокированной, факт, следующий шаг «нужно решение владельца»; если нет — блок не рисуется

Команда приёмки

go test ./internal/httpapi/... -run TestBoardRendersOwnerLayout -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #29 PR #36

Вердикты

delovdelo · 93ec7fd · PASS · 06.09.2026 17:07

#35
ci: PostgreSQL как сервис, тесты store не пропускаются
.github/workflows (правит архитектор), internal/store
Готово
PR #47 · слит · CI success
готово

Цель

В job check поднимается сервис postgres:17 и задаётся AGENTSTAFF_TEST_DATABASE_URL; тест TestStore в CI выполняется, а не skip. Исполнитель готовит изменение workflow как предложение в PR; сливает архитектор (защищённый путь)

Команда приёмки

gh run view --job $(gh run list --branch main --limit 1 --json databaseId --jq '.[0].databaseId') --log 2>/dev/null | grep -c 'SKIP.*TestStore'

Ожидаемый вывод

0

Ссылки

Issue #35 PR #47

Вердикты

вердиктов нет

#39
cmd: подкоманда version печатает также версию Go и commit
cmd/agentstaffPASS
Готово
PR #40 · слит · CI success · PASS fbe2c5b от delovdelo
готово

Цель

agentstaff version печатает три строки: версия продукта, версия Go из runtime, короткий commit из debug.ReadBuildInfo (vcs.revision, первые 7 символов, или «unknown»). Тест TestVersionOutput проверяет формат трёх строк

Команда приёмки

go test ./cmd/agentstaff/... -run TestVersionOutput -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #39 PR #40

Вердикты

delovdelo · fbe2c5b · PASS · 06.09.2026 17:57

#41
domain: связывать PR с карточкой также по «Closes #N» в описании
internal/domain, internal/appPASS
Готово
PR #48 · слит · CI success · PASS 722c759 от delovdelo · FAIL ×1
готово

Цель

LinkPull учитывает, кроме ветки feat/N-*, строку Closes/Fixes/Resolves #N в теле PR (поле Body у domain.Pull; app передаёт body из github.Pull). Приоритет: ветка, затем Closes. Нужно для исполнителей с чужим именованием веток (Copilot: copilot/*)

Команда приёмки

go test ./internal/domain/... -run TestDeriveLinkPull -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #41 PR #48

Вердикты

delovdelo · 722c759 · FAIL · 06.09.2026 18:03 delovdelo · 722c759 · PASS · 06.09.2026 18:04

#43
domain: третий исход вердикта BLOCKED
internal/domainPASS
Готово
PR #52 · слит · CI success · PASS 9e05804 от delovdelo
готово

Цель

ParseVerdict понимает BLOCKED <sha>: причина; такой вердикт не двигает состояние, попадает в Verdicts с Result=blocked и виден на доске в факте карточки как «приёмка не смогла проверить: причина»; Derive порождает признак OwnerDecisionNeeded=true

Команда приёмки

go test ./internal/domain/... -run 'TestDerive|TestParseVerdict' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #43 PR #52

Вердикты

delovdelo · 9e05804 · PASS · 06.09.2026 18:18

#50
httpapi: маршрут POST /webhooks/github
internal/httpapi, cmd/agentstaffPASS
Готово
PR #53 · слит · CI success · PASS f932f9b от delovdelo
готово

Цель

Маршрут принимает вебхук: проверка подписи и приём тела передаются функциями через Options (httpapi не импортирует github); ответы 202/401/400/503; cmd подключает github.VerifySignature с секретом AGENTSTAFF_WEBHOOK_SECRET и пока логирует событие

Команда приёмки

go test ./internal/httpapi/... -run TestWebhookRoute -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #50 PR #53

Вердикты

delovdelo · f932f9b · PASS · 06.09.2026 18:20

#54
limits: инструмент пропускал дерево; привести два файла к лимитам
tools/limits (правка архитектора уже в ветке fix/limits-root), internal/domain (только derive_test.go), internal/events (только events_test.go)PASS
Готово
PR #56 · слит · CI success · PASS ed85292 от delovdelo
готово

Цель

Инструмент лимитов чинится (ветка fix/limits-root), после чего TestDeriveVerdictsFiltered (89 строк) делится на две функции, а events_test.go (501 строка) делится на два файла; поведение тестов не меняется

Команда приёмки

go test ./tools/limits/... -run TestLimits -v && go run ./tools/limits

Ожидаемый вывод

PASS
ok
limits: ok

Ссылки

Issue #54 PR #56

Вердикты

delovdelo · ed85292 · PASS · 06.09.2026 18:28

#57
github: вебхук несёт автора PR и тело issue
internal/githubPASS
Готово
PR #60 · слит · CI success · PASS bfb4a70 от delovdelo
готово

Цель

WebhookEvent получает поля PRAuthor (pull_request.user.login; для issue_comment — issue.user.login, если это PR) и Body (issue.body для issues, pull_request.body для pull_request); ParseWebhook их заполняет; тесты TestWebhook* проверяют оба поля на фикстурах. Нужно пайплайну (#11) для отсева самоприёмки и разбора блока карточки

Команда приёмки

go test ./internal/github/... -run TestWebhook -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #57 PR #60

Вердикты

delovdelo · bfb4a70 · PASS · 06.09.2026 18:35

#58
cmd: вебхук идёт в pipeline.Apply, outbox доставляется в лог
cmd/agentstaff, internal/appPASS
Готово
PR #62 · слит · CI success · PASS 7eceafd от delovdelo
готово

Цель

При заданных AGENTSTAFF_DATABASE_URL и AGENTSTAFF_WEBHOOK_SECRET serve: WebhookSink разбирает событие через github.ParseWebhook и вызывает pipeline.Apply с арендатором из конфига; результат (From→To или Ignored) пишется в slog; фоновая горутина раз в 5 с вызывает events.Publisher.Deliver с sink=slog.Info и останавливается по контексту. Без БД — как раньше, только лог. Разбор конфига и сборка зависимостей вынесены в app.Runtime (New(cfg) → Board, Leaser, WebhookSink, Deliver) с тестом на фейках

Команда приёмки

go test ./internal/app/... -run TestRuntime -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #58 PR #62

Вердикты

delovdelo · 7eceafd · PASS · 06.09.2026 18:42

#61
pipeline: автор PR и блок карточки берутся из вебхука
internal/pipelinePASS
Готово
PR #63 · слит · CI success · PASS f65b942 от delovdelo
готово

Цель

При pull_request opened/synchronize пайплайн сохраняет row.PRAuthor = wh.PRAuthor; при issues opened/edited разбирает блок карточки из wh.Body (не из CommentBody); при issue_comment самоприёмка отсеивается по row.PRAuthor, а если он пуст — по wh.PRAuthor из вебхука

Команда приёмки

go test ./internal/pipeline/... -run TestPipeline -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #61 PR #63

Вердикты

delovdelo · f65b942 · PASS · 06.09.2026 18:45

#66
app: режим опроса — пайплайн работает без публичного адреса
internal/app, cmd/agentstaffPASS
Готово
PR #68 · слит · CI success · PASS 010542a от delovdelo
готово

Цель

Фоновый опрос GitHub раз в N секунд (AGENTSTAFF_POLL_SECONDS, 0 = выключено) читает PR, проверки и комментарии карточек через github.Client и синтезирует github.WebhookEvent (pull_request opened/synchronize/closed, check_suite completed, issue_comment created) с детерминированным DeliveryID (poll:<вид>:<pr>:<sha>|<comment id>), передавая их в pipeline через Runtime.WebhookSink; повторы отсеивает существующая идемпотентность по delivery. Нужно, чтобы золотой путь проходил на локальном сервере без туннеля

Команда приёмки

go test ./internal/app/... -run TestPoller -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #66 PR #68

Вердикты

delovdelo · 010542a · PASS · 06.09.2026 19:08

#69
app: опрос заводит карточки из Issues с меткой card
internal/appPASS
Готово
PR #72 · слит · CI success · PASS a397be7 от delovdelo
готово

Цель

Poller.Once перед обработкой PR читает Issues с меткой card (открытые и закрытые) и синтезирует issues opened с DeliveryID poll:issue:<n>:opened (тело — issue.body, чтобы пайплайн разобрал блок карточки); для закрытого Issue без PR — issues closed (DeliveryID poll:issue:<n>:closed). Порядок: сначала все issues, потом PR. Замер 07.09: на чистой базе все события PR игнорировались с card.unknown

Команда приёмки

go test ./internal/app/... -run TestPoller -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #69 PR #72

Вердикты

delovdelo · a397be7 · PASS · 06.09.2026 19:22

#70
pipeline: связь PR с карточкой по Closes/Fixes в теле, как в domain.LinkPull
internal/pipelinePASS
Готово
PR #71 · слит · CI success · PASS 50c96fe от delovdelo
готово

Цель

applyPull определяет карточку сначала по ветке feat/N-*|fix/N-*, а если не вышло — по Closes/Fixes/Resolves #N в теле PR (wh.Body), переиспользуя правило из domain (вынести bodyRefersTo в экспортируемую функцию domain.PullRefersTo или использовать LinkPull с одним PR). Замер 07.09: ветка copilot/* давала branch.no-card

Команда приёмки

go test ./internal/pipeline/... -run TestPipeline -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #70 PR #71

Вердикты

delovdelo · 50c96fe · PASS · 06.09.2026 19:19

#73
app: арендатор заводится при старте; опрос не останавливается на первой ошибке
internal/app, cmd/agentstaffPASS
Готово
PR #75 · слит · CI success · PASS 401748f от delovdelo
готово

Цель

NewRuntime (или serve) вызывает store.UpsertTenant(tenant, repo, acceptors) до первого события — иначе SaveCard падает по внешнему ключу (замер 07.09: SQLSTATE 23503). Poller.Once при ошибке sink на одном событии логирует её и продолжает остальные, возвращая в конце сводную ошибку (errors.Join)

Команда приёмки

go test ./internal/app/... -run 'TestRuntime|TestPoller' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #73 PR #75

Вердикты

delovdelo · 401748f · PASS · 06.09.2026 19:41

#74
pipeline: доставка помечается применённой только после успешного сохранения
internal/pipeline, internal/storePASS
Готово
PR #76 · слит · CI success · PASS eef062f от delovdelo
готово

Цель

MarkDelivery и SaveCard/публикация выполняются так, что при ошибке сохранения доставка НЕ считается применённой (транзакция в store: ApplyDelivery(ctx, tenant, delivery, fn) или порядок «сохранить → пометить»); повтор того же delivery после ошибки применяется заново. Замер 07.09: три доставки помечены, ни одной карточки не сохранено

Команда приёмки

AGENTSTAFF_TEST_DATABASE_URL='postgres://agentstaff@127.0.0.1:55440/agentstaff_test?sslmode=disable' go test ./internal/pipeline/... ./internal/store/... -run 'TestPipeline|TestStoreCards' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #74 PR #76

Вердикты

delovdelo · eef062f · PASS · 06.09.2026 19:48

#78
domain: открытый PR двигает карточку из new в in_progress без аренды
internal/domainPASS
Готово
PR #80 · слит · CI success · PASS 3e4f2a4 от delovdelo
готово

Цель

Next(StateNew, EventPROpened) → StateInProgress (аренда желательна, не обязательна — контракт обновлён). Derive уже так считает; машина переходов отставала. Замер 07.09: 29 PR отброшены как transition new --pr_opened--> not allowed

Команда приёмки

go test ./internal/domain/... -run 'TestGoldenPath|TestNoManual|TestDerive' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #78 PR #80

Вердикты

delovdelo · 3e4f2a4 · PASS · 06.09.2026 20:00

#79
pipeline: событие, отброшенное из-за состояния или неизвестной карточки, не помечается применённым
internal/pipeline, internal/storePASS
Готово
PR #81 · слит · CI success · PASS 4403459 от delovdelo
готово

Цель

ApplyDelivery помечает доставку применённой только если событие дало переход либо отброшено по структурной причине (branch.no-card, not-a-verdict, action.*); причины «transition … not allowed», check.no_card, comment.no_card, card.unknown оставляют доставку непомеченной, чтобы следующий опрос применил её, когда карточка или состояние догонят. Замер 07.09: после починки машины состояний 29 PR остались бы навсегда «duplicate»

Команда приёмки

AGENTSTAFF_TEST_DATABASE_URL='postgres://agentstaff@127.0.0.1:55440/agentstaff_test?sslmode=disable' go test ./internal/pipeline/... ./internal/store/... -run 'TestPipeline|TestStoreCards' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #79 PR #81

Вердикты

delovdelo · 4403459 · PASS · 06.09.2026 20:08

#82
app: опрос экономит лимит запросов GitHub
internal/app, internal/github (только добавить ETag-поддержку в get/listAll, не ломая интерфейс Reader), cmd/agentstaff (умолчание AGENTSTAFF_POLL_SECONDS → 120)PASS
Готово
PR #88 · слит · CI success · PASS 833abfa от delovdelo
готово

Цель

Опрос не тратит лимит GitHub (5000 запросов/час): кэш по updated_at и head sha — check-runs и комментарии запрашиваются только для PR, которые изменились с прошлого круга; закрытые и слитые PR старше N дней не перечитываются; ListPulls с per_page=100 и sort=updated; ETag/If-None-Match для issues/pulls там, где GitHub его отдаёт; в логе одной строкой на круг — число запросов. Замер 07.09: опрос раз в 30 с исчерпал 5000 запросов за ~40 минут (30 PR × 3 запроса × 120 кругов)

Команда приёмки

go test ./internal/app/... -run TestPoller -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #82 PR #88

Вердикты

delovdelo · 833abfa · PASS · 06.09.2026 20:59

#83
domain: повторная приёмка на том же sha после FAIL
internal/domain (state.go), internal/pipeline (тест сценария)PASS
Готово
PR #85 · слит · CI success · PASS 6948510 от delovdelo
готово

Цель

После FAIL приёмка может быть перезапущена на том же sha (карточка исправлена, код нет) — PASS в состоянии rework при зелёном CI переводит в accepted; merged в состоянии rework/ready_for_acceptance после PASS — в done. Замер 07.09 на живом прогоне: 5 карточек застряли в rework («transition rework --verdict_pass--> not allowed», «transition rework --merged--> not allowed»), хотя доска (Derive) считает их готовыми. Дополнительно: слияние — факт; merged из in_progress/ready_for_acceptance/rework тоже переводит в done, но пайплайн публикует событие card.done с data.without_acceptance=true, если PASS не было (замер: карточка #35 застряла в ready_for_acceptance после слияния PR архитектора без вердикта)

Команда приёмки

go test ./internal/domain/... ./internal/pipeline/... -run 'TestGoldenPath|TestSecondFail|TestNoManual|TestPipeline' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #83 PR #85

Вердикты

delovdelo · 6948510 · PASS · 06.09.2026 20:41

#84
pipeline: закрытие Issue без PR переводит карточку в done
internal/pipelinePASS
Готово
PR #87 · слит · CI success · PASS ba631ba от delovdelo
готово

Цель

Закрытие Issue без PR переводит карточку в done (контракт: «Issue закрыт руками без PR → done, допустимо только для карточек без процесса»). Замер 07.09: карточка #1 осталась new, событие issues closed отброшено как action.closed

Команда приёмки

go test ./internal/pipeline/... -run TestPipeline -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #84 PR #87

Вердикты

delovdelo · ba631ba · PASS · 06.09.2026 20:55

#89
app: разделить пакет на app, poll и runtime — лимит 3000 строк
internal/app, internal/poll (новый), internal/runtime (новый), cmd/agentstaff (импорты)PASS
Готово
PR #93 · слит · CI success · PASS 48e7a60 от delovdelo · FAIL ×1
готово

Цель

Пакет internal/app достиг лимита 3000 строк. Разделить без изменения поведения: internal/app (Board, Source, Count, Loader, Cached, Leaser — сборка доски и фасады), internal/poll (Poller, состояние круга, синтез вебхуков), internal/runtime (Runtime, Deps, OwnerSignal). Зависимости по ADR-0002: poll → github, domain; runtime → pipeline, events, store, github, app; httpapi по-прежнему только app. Тесты переезжают вместе с кодом, ни один не удаляется

Команда приёмки

go run ./tools/limits && go test ./internal/app/... ./internal/poll/... ./internal/runtime/... -count=1

Ожидаемый вывод

limits: ok
ok

Ссылки

Issue #89 PR #93

Вердикты

delovdelo · 48e7a60 · FAIL · 07.09.2026 03:21 delovdelo · 48e7a60 · PASS · 07.09.2026 03:24

#90
app: опрос отдаёт закрытие Issue после всех событий PR
internal/app (poller.go, poller_issues.go, тесты)PASS
Готово
PR #92 · слит · CI success · PASS ca0a796 от delovdelo
готово

Цель

Опрос отдаёт события в порядке: issues opened → все события PR (opened, check_suite, issue_comment, closed) → issues closed последними. Иначе закрытая карточка уходит в done по закрытию Issue раньше, чем пайплайн узнаёт о её PR, и путь PR → CI → вердикт → слияние не фиксируется (замер 07.09: 35 done, ни одного события ready_for_acceptance/verdict.pass, 102 «transition done --pr_opened»)

Команда приёмки

go test ./internal/app/... -run TestPoller -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #90 PR #92

Вердикты

delovdelo · ca0a796 · PASS · 06.09.2026 21:31

#95
Доступ к GitHub через приложение (GitHub App): свой лимит и без личного токена
internal/github (источник токена: TokenSource с обновлением), cmd/agentstaff (конфиг и выбор источника)PASS
Готово
PR #99 · слит · CI success · PASS 4491b69 от delovdelo
готово

Цель

Продукт ходит в GitHub от имени установленного приложения (GitHub App), а не по личному токену владельца: конфиг `AGENTSTAFF_GITHUB_APP_ID`, `AGENTSTAFF_GITHUB_APP_KEY_FILE` (PEM), `AGENTSTAFF_GITHUB_INSTALLATION_ID`; клиент сам получает токен установки по JWT и обновляет его до истечения (запас 5 минут); при заданном `AGENTSTAFF_GITHUB_TOKEN` и незаданном приложении — прежнее поведение. Смысл: у приложения свой лимит запросов, отдельный от лимита учётки владельца (замер 07.09: опрос каждые 30 с сжёг 5000/час за 40 минут), и в проде не лежит личный токен

Команда приёмки

go test ./internal/github/... ./cmd/agentstaff/... -run 'TestAppToken|TestConfigFromEnv' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #95 PR #99

Вердикты

delovdelo · 4491b69 · PASS · 07.09.2026 04:44

#100
Журнал опроса: каждая строка круга подписана арендатором и репозиторием
internal/poll (строки журнала), internal/runtime (строки журнала), cmd/agentstaff (сборка логгеров)PASS
Готово
PR #101 · слит · CI success · PASS d6ddb50 от delovdelo
готово

Цель

При нескольких арендаторах строки журнала опроса (`круг завершён requests=… emitted=… skipped=…`, «webhook ignored», ошибки запросов) не различить: в них нет имени арендатора и репозитория (замер 07.09 на трёх арендаторах). Логгер, который получает Poller, должен нести атрибуты `tenant` и `repo` во всех своих строках; Runtime — атрибут `tenant`. Сборка в `cmd` передаёт `slog.Default().With("tenant", …, "repo", …)`

Команда приёмки

go test ./internal/poll/... -run 'TestPollerLogsCarryTenant' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #100 PR #101

Вердикты

delovdelo · d6ddb50 · PASS · 07.09.2026 04:59

#102
Опрос: новый коммит в открытом PR — synchronize, а не повторный opened
internal/poll (эмиссия synchronize), internal/pipeline (opened для карточки с процессом → updateHead / дубль, не retryable)PASS
Готово
PR #104 · слит · CI success · PASS f3f5982 от delovdelo
готово

Цель

Прогон арендатора infaton 07.09 (карточка #22 → PR #27): после пуша второго коммита опрос каждый круг присылал `pull_request/opened` с новым sha, конвейер отвечал «transition in_progress --pr_opened--> not allowed», head карточки застрял на старом sha, настоящий PASS на новом sha был отвергнут как verdict.stale, а после слияния продукт ложно просигналил «слито без приёмки». Плюс тот же отвергнутый opened повторялся каждый круг для уже готовой карточки (retryable). Нужно: (1) опрос помнит head PR и при его смене эмитит `poll:pr:N:synchronize:<sha>` с action=synchronize (конвейер уже умеет updateHead); при первом наблюдении PR после старта, когда карточка уже с этим PR, — тоже synchronize, если sha отличается от head карточки — то есть (2) конвейер трактует `opened` для карточки, у которой уже есть этот PR, как обновление head при другом sha и как не повторяемый дубль (не retryable) при том же sha или в состоянии done

Команда приёмки

go test ./internal/poll/... ./internal/pipeline/... -run 'TestPollerEmitsSynchronize|TestPipelineOpenedAgain' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #102 PR #104

Вердикты

delovdelo · f3f5982 · PASS · 07.09.2026 07:54

#103
Опрос: при смене head дочитывать факты старого коммита (CI и вердикты) до synchronize
internal/poll (порядок эмиссии при смене head), internal/pipeline (только если понадобится: вердикт на старом sha после synchronize — verdict.stale, как сейчас)PASS
Готово
PR #106 · слит · CI success · PASS 42471a2 от delovdelo
готово

Цель

В режиме опроса факты о предыдущем head теряются, если пуш и слияние уложились между двумя кругами (прогон infaton 07.09, PR #27: CI зелёный на f6b2ce26fb, FAIL f6b2ce26fb, пуш a346e447f4, CI зелёный, PASS a346e447f4, слияние — всё за 7 минут; продукт увидел только opened и merged, путь FAIL→доработка→PASS не записан, fail_count=0). Факты остаются доступны в GitHub после события: нужно при смене head внутри круга сначала эмитить CI старого sha (`poll:ci:N:<old>:<conclusion>`) и вердикты (уже эмитятся все), затем synchronize нового sha, затем CI нового sha, затем закрытие. Порядок внутри круга: opened → ci(old) → comments(old) → synchronize(new) → ci(new) → comments(new) → closed. Стоимость — один запрос check-runs на смену head

Команда приёмки

go test ./internal/poll/... -run 'TestPollerReplaysSupersededHead' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #103 PR #106

Вердикты

delovdelo · 42471a2 · PASS · 07.09.2026 08:13

#108
Доска считает карточкой Issue с блоком «Карточка», а не по метке
internal/app (Loader и Config), internal/poll (отбор Issue для опроса — тоже по блоку, параметр cardLabel убрать), cmd/agentstaff (сигнатура NewPoller)PASS
Готово
PR #109 · слит · CI success · PASS 32a9d88 от delovdelo
готово

Цель

Доска (internal/app Loader) отбирает карточки по метке `card`, а конвейер (internal/pipeline через domain.ParseCard) — по блоку «## Карточка» в теле Issue; контракт tenant.md называет только блок. Опрос (internal/poll) тоже отбирает Issue по метке: четыре карточки вехи M6 без метки (#95, #100, #102, #103) ни доска не показывала, ни опрос не вёл — конвейер узнал о них только после проставления метки (замер 07.09 09:18Z) (замер 07.09 на проде и на ноутбуке: 39 карточек вместо 43). Нужно: Loader считает карточкой Issue, у которого ParseCard тела проходит без ошибки; метка не требуется и не мешает; Issue без блока не карточка, даже с меткой `card`. Поле CardLabel удалить из конфигурации app вместе с переменной, если она есть

Команда приёмки

go test ./internal/app/... ./internal/poll/... -run 'TestLoaderCardByBlock|TestPollerCardByBlock' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #108 PR #109

Вердикты

delovdelo · 32a9d88 · PASS · 07.09.2026 09:43

#111
Вебхук: неподдерживаемое событие (installation, ping) — 202 и запись в журнал, а не 500
internal/github (типизированная ошибка ErrUnsupportedEvent), internal/runtime (WebhookSink: неподдерживаемое → ignored), internal/httpapi (202 для ignored)PASS
Готово
PR #114 · слит · CI success · PASS 8921529 от delovdelo
готово

Цель

На проде 07.09 GitHub прислал события `ping` и `installation` (владелец сохранял права приложения), продукт ответил 500 «вебхук не обработан» (`github: unsupported webhook event: installation`), GitHub пометил доставку как сбой. Событие, которое продукт не разбирает, — не ошибка сервера: ответить 202, записать в журнал одной строкой уровня INFO «webhook ignored event=<тип> reason=event.unsupported» и ничего не применять. Подпись при этом проверяется как обычно (неверная подпись — по-прежнему отказ). Ошибка 500 остаётся только для настоящих сбоев (база недоступна и т.п.)

Команда приёмки

go test ./internal/httpapi/... ./internal/runtime/... -run 'TestWebhookUnsupportedEvent' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #111 PR #114

Вердикты

delovdelo · 8921529 · PASS · 07.09.2026 10:04

#112
Выкладка на прод по тэгу одной командой: scripts/deploy.sh vX.Y.Z
scripts (deploy.sh), docs/ЗОЛОТОЙ-ПУТЬ.md не трогать — про выкладку допишет архитекторPASS
Готово
PR #113 · слит · CI success · PASS 9c0bf39 от delovdelo · FAIL ×1
готово

Цель

Прод на pristock (ssh-имя `agentstaff-vps`, служба systemd `agentstaff`, бинарник `/opt/agentstaff/bin/agentstaff`) сегодня обновляется руками: сборка на ноутбуке, scp, mv, restart. Нужен `scripts/deploy.sh <vX.Y.Z> [--dry-run]`: проверяет форму тэга (иначе «тэг должен быть вида vX.Y.Z», код 2), что тэг есть в origin, собирает бинарник linux/amd64 из этого тэга в отдельном worktree (`CGO_ENABLED=0`, `-ldflags "-s -w"`), копирует на хост (`AGENTSTAFF_DEPLOY_HOST`, по умолчанию `agentstaff-vps`) как `agentstaff.new`, на хосте сохраняет прежний как `agentstaff.prev`, ставит новый, `systemctl restart agentstaff`, ждёт до 30 с `curl 127.0.0.1:8080/healthz` с версией X.Y.Z; если версия не совпала или служба не поднялась — откат на `agentstaff.prev` и код 1. `--dry-run` печатает план построчно (`план: …`) и не трогает сеть и хост. Каждая строка вывода начинается с `deploy:`

Команда приёмки

bash -n scripts/deploy.sh && scripts/deploy.sh --dry-run v0.1.0 && ! scripts/deploy.sh --dry-run notatag

Ожидаемый вывод

deploy: план: сборка v0.1.0 linux/amd64
deploy: план: проверка версии 0.1.0 через /healthz, при несовпадении откат
deploy: тэг должен быть вида vX.Y.Z

Ссылки

Issue #112 PR #113

Вердикты

delovdelo · 9c0bf39 · FAIL · 07.09.2026 10:10 delovdelo · 9c0bf39 · PASS · 07.09.2026 10:13

#117
Доска на проде: холодная загрузка после перезапуска дольше 60 с — 502 у владельца; отдавать последний снимок и обновлять в фоне
internal/app (Cached: фоновое обновление, снимок), internal/httpapi (ответ без снимка), cmd/agentstaff (прогрев при старте)PASS
Готово
PR #121 · слит · CI success · PASS 40942fd от delovdelo
готово

Цель

На проде 07.09 после перезапуска службы первый запрос `/board` и `/board.json` строил доску с нуля (130 запросов к GitHub, ~1–6 минут), nginx обрывал ожидание через 60 с, владелец видел 502, в журнале «context canceled». Нужно: (1) при старте `serve` продукт прогревает доску каждого арендатора в фоне, не блокируя HTTP; (2) `Cached` отдаёт последний успешно собранный снимок мгновенно, а по истечении TTL обновляет его в фоне (stale-while-revalidate), не заставляя запрос ждать; (3) пока снимка ещё нет, `/board` отвечает 200 со страницей «доска собирается, обновите через минуту» и заголовком `Retry-After: 30`, `/board.json` — 503 с тем же Retry-After; (4) ошибка обновления не портит имеющийся снимок — в журнал одна строка, снимок остаётся прежним

Команда приёмки

go test ./internal/app/... ./internal/httpapi/... -run 'TestCachedServesStaleWhileRefreshing|TestBoardWithoutSnapshot' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #117 PR #121

Вердикты

delovdelo · 40942fd · PASS · 07.09.2026 10:40

#119
Создание карточки по запросу приложения: POST /tenants/{t}/cards → Issue с блоком «Карточка»
internal/github (CreateIssue), internal/domain (сборка тела карточки из полей — обратная к ParseCard), internal/app (создание карточки через реестр арендаторов, токены по арендаторам), internal/httpapi (маршрут), cmd/agentstaff (AGENTSTAFF_TENANT_TOKENS)PASS
Готово
PR #125 · слит · CI success · PASS 2f818b6 от delovdelo
готово

Цель

Приложение владельца («Двойник») должно рождать факт, а не рисовать статус: `POST /tenants/{t}/cards` с Bearer-токеном арендатора и JSON `{title, goal, package, acceptance: {command, expected: [строки]}, mutation: {file, what, sed, expect}, stop?}` создаёт в репозитории арендатора GitHub Issue с блоком «## Карточка» ровно в формате STANDARD.md §3.2 (Цель, Пакет, Команда приёмки с отступом 4 пробела, Ожидаемый вывод построчно, Подлог с файл:/что:/sed:/ждём:, Стоп-условия) и отвечает `201 {"number": N, "url": …}`. Проверка: собранное тело проходит `domain.ParseCard` до отправки, иначе 422 с причиной. Токены арендаторов: `AGENTSTAFF_TENANT_TOKENS="имя=токен,…"` (арендатор по умолчанию продолжает принимать `AGENTSTAFF_TENANT_TOKEN`); без токена — 401; арендатор, для которого токена нет, — 403. Запись в GitHub — через существующий клиент (приложение имеет право Issues: write). Дубли: повторный запрос с тем же заголовком `Idempotency-Key` в течение часа возвращает тот же номер, а не второй Issue

Команда приёмки

go test ./internal/domain/... ./internal/httpapi/... -run 'TestRenderCardRoundTrip|TestCreateCard' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #119 PR #125

Вердикты

delovdelo · 2f818b6 · PASS · 07.09.2026 10:54

#120
События наружу: доставка событий карточки в приложение арендатора по адресу с подписью
internal/events (подпись конверта, HTTP-отправитель), internal/runtime (DeliverLoop: маршрут по арендатору, повторы), cmd/agentstaff (AGENTSTAFF_TENANT_SINKS)PASS
Готово
PR #123 · слит · CI success · PASS 28fef21 от delovdelo
готово

Цель

Приложение владельца должно узнавать «этап принят / провален / нужно решение» не опросом доски, а событием. Конфиг `AGENTSTAFF_TENANT_SINKS="имя=https://адрес,…"`: для арендатора с адресом каждое событие его outbox (конверт Event Contract v1, как в contracts/event-contract-v1.md) отправляется `POST` JSON на адрес с заголовками `X-AgentStaff-Event: <type>`, `X-AgentStaff-Delivery: <id конверта>`, `X-AgentStaff-Signature-256: sha256=<HMAC тела токеном арендатора>`; ответ 2xx — доставлено (delivered_at), иначе повтор с нарастающей паузой (1, 2, 4… до 5 минут) без блокировки других событий и арендаторов; после 24 часов неудач — одна строка ERROR в журнал и событие остаётся недоставленным (не теряется). Арендаторы без адреса — как сейчас (журнал). Существующий сигнал владельцу (комментарий в GitHub) не отключается

Команда приёмки

go test ./internal/events/... ./internal/runtime/... -run 'TestSignEnvelope|TestSinkDelivers' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #120 PR #123

Вердикты

delovdelo · 28fef21 · PASS · 07.09.2026 10:48

#129
CI-факт из commit status: засчитывать успешный статус коммита наравне с check run
internal/github (CombinedStatus, разбор вебхука status), internal/poll (эмиссия по статусу), internal/pipeline (без изменений, если событие приходит как check_suite)PASS
Готово
PR #168 · слит · CI success · PASS ba229f2 от delovdelo
готово

Цель

Сегодня движок признаёт факт «CI зелёный» только по check runs (GET …/commits/{sha}/check-runs и события check_suite/check_run). Арендаторы с проверками вне GitHub Actions (прогон в закрытом контуре, внешний CI) могут выставить только commit status (POST /repos/{r}/statuses/{sha}, событие `status`). Пилот infaton-1c 07.09: приёмка в тестовой базе 1С идёт с узла тайлнета, GitHub-runner до базы не достаёт. Нужно: опрос дополнительно читает combined status head PR (GET …/commits/{sha}/status) и эмитит `poll:ci:N:<sha>:<state>` по правилу: success — зелёный; failure/error — красный; pending — ничего; при наличии и check runs, и статусов — зелёный только если оба не красные и хотя бы один зелёный. Вебхук `status` разбирается как check_suite (нужна подписка на событие status в приложении — отметит владелец). Стоимость: +1 запрос на PR со сменой head, кэш как у check runs

Команда приёмки

go test ./internal/github/... ./internal/poll/... -run 'TestCombinedStatus|TestPollerEmitsCIFromStatus' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #129 PR #168

Вердикты

delovdelo · ba229f2 · PASS · 08.09.2026 06:39

#132
Ф2-2: agentstaff doccheck form — проверка формы документного этапа (паспорт, разделы, нумерация, источники, нить)
internal/doccheck (новый), cmd/agentstaff (подкоманда doccheck form)PASS
Готово
PR #161 · слит · CI success · PASS f782a60 от delovdelo
готово

Цель

Подкоманда `agentstaff doccheck form <файл.md>` проверяет документ этапа «Двойника» по docs/проект/Ф2-приёмка-документов.md §3–4 без обращения к 1С: (1) YAML-паспорт с полями этап (протокол|анализ|лт|тз), заявка, источник, база, предыдущий (кроме протокола), коды (список регулярных выражений схемы кодов требований арендатора, например П\d+, Т\d+\.\d+, FR-\d{3}), объекты; (2) обязательные разделы по этапу задаются таблицей в пакете (по docs/проект/Ф2-приёмка-документов.md §2а/§4: протокол — таблица Тема/Требования/Договорённости; анализ — пять таблиц этапа 1, этап 2, сопоставление с типовым функционалом; лт — 12 разделов Листа Требований; тз — Цель, Объекты, Алгоритм, Кейсы приёмки, Подлог, Ограничения) и легко расширяются; (3) требования имеют коды по схеме из паспорта, без дублей, у каждого есть источник; (4) нить: файл из «предыдущий» существует, и каждый код требования предыдущего документа (по его схеме) упомянут в текущем; (5) нет заглушек (TODO, «…», «уточнить»). Вывод построчно с префиксом `doccheck:`, последняя строка `doccheck: ok` либо `doccheck: FAIL <n> замечаний`, код возврата 0/1. Образцы трёх документов — в `internal/doccheck/testdata/`

Команда приёмки

go test ./internal/doccheck/... -run 'TestDoccheckForm' -v && go run ./cmd/agentstaff doccheck form internal/doccheck/testdata/анализ.md

Ожидаемый вывод

PASS
ok
doccheck: ok

Ссылки

Issue #132 PR #161

Вердикты

delovdelo · f782a60 · PASS · 08.09.2026 06:03

#133
Арендатор со своей установкой приложения GitHub: installation id в AGENTSTAFF_TENANTS
cmd/agentstaff (разбор суффикса, сборка клиентов по арендаторам), internal/app (Tenant несёт свой Reader), internal/github (несколько AppTokenSource на один ключ)PASS
Готово
PR #134 · слит · CI success · PASS a0ab235 от delovdelo
готово

Цель

Сегодня движок ходит в GitHub через одну установку приложения (AGENTSTAFF_GITHUB_INSTALLATION_ID), поэтому все арендаторы обязаны жить в одной учётке владельца. Репозиторий delovdelo/EventCode лежит во второй учётке владельца: приложение туда ставится отдельной установкой с другим installation id. Нужно: в AGENTSTAFF_TENANTS необязательный суффикс `@<installation_id>` (пример: `eventcode=delovdelo/EventCode@123456`); для арендатора с суффиксом клиент GitHub собирается со своим AppTokenSource (тот же App ID и ключ, другой installation id, свой кэш токена); без суффикса — установка по умолчанию, как сейчас. Poller, Loader/Board, Runtime (комментарии сигнала владельцу) и создание карточек арендатора используют клиент этого арендатора. Личный токен (AGENTSTAFF_GITHUB_TOKEN) с суффиксом несовместим — ошибка конфигурации с понятным текстом

Команда приёмки

go test ./cmd/agentstaff/... ./internal/github/... -run 'TestConfigFromEnvTenantInstallation|TestTenantClients|TestAppToken' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #133 PR #134

Вердикты

delovdelo · a0ab235 · PASS · 07.09.2026 19:51

#138
M8-1: адаптер мозга — Claude API и совместимые модели, два разных мозга для исполнителя и приёмщика
internal/brain (новый), cmd/agentstaff (чтение переменных, сборка двух мозгов, строка в журнал «мозг исполнителя: … / мозг приёмщика: …»)PASS
Готово
PR #141 · слит · CI success · PASS 1853659 от delovdelo
готово

Цель

Пакет `internal/brain`: единый интерфейс `Brain` (Complete(ctx, req) с системной инструкцией, сообщениями, инструментами и лимитом токенов → ответ с текстом и вызовами инструментов) и два адаптера: Anthropic Messages API (`/v1/messages`, заголовки x-api-key и anthropic-version, модель по умолчанию claude-sonnet-5) и OpenAI-совместимый chat completions (`/v1/chat/completions`, для LM Studio и локальных моделей, поле content, max_tokens). Конфигурация двух мозгов: `AGENTSTAFF_BRAIN_EXECUTOR_URL/KEY/MODEL` и `AGENTSTAFF_BRAIN_ACCEPTOR_URL/KEY/MODEL`; тип адаптера выводится по URL (api.anthropic.com → Anthropic, иначе совместимый). Правило двух мозгов: исполнитель и приёмщик обязаны различаться моделью или адресом (одинаковые URL и MODEL — ошибка конфигурации с понятным русским текстом; одинаковый URL с разными моделями — допустимо). Health: `Ping(ctx)` (Anthropic — короткий запрос с max_tokens 1; совместимые — GET /v1/models содержит модель). Ошибки сети/статуса — типизированные, с телом ответа (первые 200 байт); повтор 429/5xx до трёх раз с паузой. Без новых зависимостей

Команда приёмки

go test ./internal/brain/... ./cmd/agentstaff/... -run 'TestBrain|TestConfigFromEnvBrains' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #138 PR #141

Вердикты

delovdelo · 1853659 · PASS · 07.09.2026 20:47

#139
M8-2: приёмщик внутри продукта — логика accept.sh на Go с вердиктом от мозга приёмщика
internal/acceptor (новый), internal/domain (переиспользовать ParseCard), cmd/agentstaff (подкоманда accept)PASS
Готово
PR #142 · слит · CI success · PASS 1037eab от delovdelo
готово

Цель

Пакет `internal/acceptor` повторяет scripts/accept.sh внутри продукта: по PR читает карточку (Closes/Fixes/Resolves), команду приёмки, ожидаемые строки и подлог; готовит чистую копию репозитория на sha PR (git worktree в каталоге работ `AGENTSTAFF_WORKDIR`); выполняет команду (bash, таймаут из настроек, по умолчанию 20 минут); машинная сверка ожидаемых строк по порядку; `--- SKIP` ≠ PASS; подлог: sed по карточке, проверка суммы файла, сборка (`Сборка:` из карточки, если задана; иначе `go build ./...` при наличии go.mod, иначе пропуск с пометкой), команда обязана покраснеть (код возврата ≠ 0 или строка FAIL), контрольная правка комментария (// для .go, # для остальных) обязана остаться зелёной; затем закрытый вопрос мозгу приёмщика (internal/brain, ответ одной строкой PASS/FAIL/BLOCKED <sha>); мозг недоступен после трёх попыток — BLOCKED. Итог — структура Verdict {Kind, SHA, Reason, MachineCheck, MutationCheck, Log} и текст комментария первой строкой `PASS <sha>` / `FAIL <sha>: причина` / `BLOCKED <sha>: чего не хватило`. Публикация комментария — через интерфейс Poster (в этой карточке фейковый; реальная учётка приёмщика — следующая карточка). Подкоманда `agentstaff accept <owner/repo> <PR>` для ручного запуска

Команда приёмки

go test ./internal/acceptor/... -run 'TestAcceptor' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #139 PR #142

Вердикты

delovdelo · 1037eab · PASS · 07.09.2026 20:49

#144
M8-3: приёмщик сам запускается по «на приёмке», спрашивает мозг приёмщика и публикует вердикт своей учёткой
internal/acceptor (Asker/Poster/Git поверх brain и github), internal/runtime (подписчик события → запуск приёмки, WIP на арендатора), cmd/agentstaff (accept_serve.go: сборка приёмщика, переменная AGENTSTAFF_ACCEPTOR_GITHUB_TOKEN)PASS
Готово
PR #147 · слит · CI success · PASS e37ffa3 от delovdelo
готово

Цель

Связать internal/acceptor с продуктом: (1) `Asker` поверх `brain.Brain` (мозг приёмщика из AGENTSTAFF_BRAIN_ACCEPTOR_*): системная инструкция «ответь одной строкой PASS|FAIL|BLOCKED <sha>», MaxTokens не ниже 6000 (опыт accept.sh), ответ — первая строка с вердиктом; (2) `Poster` поверх `*github.Client`, собранного на токене учётки приёмщика `AGENTSTAFF_ACCEPTOR_GITHUB_TOKEN` (личный токен отдельной учётки, например delovdelo; логин учётки должен входить в AGENTSTAFF_ACCEPTORS — проверка при старте через GET /user, иначе отказ с понятным текстом); (3) `Source` и `Git` для приватных репозиториев: клон по https с токеном приёмщика через переменную окружения git (GIT_ASKPASS/credential helper), токен не в командной строке и не в журнале; (4) автозапуск в `serve`: при заданных мозгах и токене приёмщика продукт слушает события outbox `card.ready_for_acceptance` (как OwnerSignal слушает свои) и запускает приёмку PR арендатора, не больше одной одновременно на арендатора и не повторно для того же sha (память в процессе + проверка, что вердикта приёмщика на этот sha в PR ещё нет); результат публикуется комментарием, дальше конвейер видит его через вебхук/опрос как обычно; (5) `agentstaff accept <repo> <PR> --post` публикует по-настоящему. Живая проверка после слияния — архитектор запускает `agentstaff accept` на следующем PR рядом с scripts/accept.sh и сравнивает вердикты

Команда приёмки

go test ./internal/acceptor/... ./internal/runtime/... -run 'TestAcceptorBrainAsker|TestAcceptorAutoRun' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #144 PR #147

Вердикты

delovdelo · e37ffa3 · PASS · 07.09.2026 21:19

#145
M8-4: исполнитель внутри продукта — берёт карточку, работает в копии репозитория с мозгом исполнителя, открывает PR
internal/worker (новый), cmd/agentstaff (work.go: подкоманда и переменная токена исполнителя)PASS
Готово
PR #149 · слит · CI success · PASS 7ddce40 от delovdelo
готово

Цель

Пакет `internal/worker`: исполнитель по ADR-0006 п.4. Вход — карточка арендатора (номер, тело, репозиторий); шаги: аренда карточки (через существующий Leaser/queue, WIP=1 на исполнителя); чистая копия репозитория в AGENTSTAFF_WORKDIR на ветке `feat/<n>-<slug>` от ветки по умолчанию; цикл с мозгом исполнителя (brain.Brain) и инструментами, ограниченными каталогом копии: `list_files(path)`, `read_file(path)`, `write_file(path, content)`, `run(command)` (bash с таймаутом 10 минут, вывод обрезан до 200 строк), `run_acceptance()` (команда приёмки из карточки), `finish(summary)`; системная инструкция: цель, пакет, команда приёмки и ожидаемый вывод, стоп-условия из карточки и STANDARD.md §5, запрет трогать защищённые пути арендатора (список из карточки/стандарта), запрет менять карточку; бюджет: не больше 40 ходов и не больше 400 тысяч входных токенов суммарно, иначе стоп с сообщением в карточку; перед finish команда приёмки обязана быть зелёной локально; затем commit (сообщение по-русски, `Co-Authored-By: AgentStaff <noreply@agentstaff>`), push токеном исполнителя (`AGENTSTAFF_EXECUTOR_GITHUB_TOKEN`, отдельная учётка или установка с правом contents:write; токен не в командной строке), PR с телом «Closes #n» и выводом команды приёмки; исполнитель не сливает и не публикует вердиктов. Стоп-условие → комментарий в Issue одной записью и освобождение аренды. Подкоманда `agentstaff work <owner/repo> <n>` — один круг вручную

Команда приёмки

go test ./internal/worker/... -run 'TestWorker' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #145 PR #149

Вердикты

delovdelo · 7ddce40 · PASS · 07.09.2026 21:31

#150
cmd/agentstaff упёрся в лимит 3000 строк: вынести сборку арендаторов и команды в internal/boot
cmd/agentstaff, internal/boot (новый)PASS
Готово
PR #153 · слит · CI success · PASS 53b59cb от delovdelo
готово

Цель

Пакет cmd/agentstaff — 2996 строк из 3000 (STANDARD.md §6.3). Вынести без изменения поведения: сборку арендаторов (tenants.go: разбор AGENTSTAFF_TENANTS, клиенты по установкам, buildTenants, startReadOnlyTenants, warmTenants), сборку мозгов (brains.go), токенов (tokens.go), адресов событий (sinks.go) и приёмщика (accept_serve.go) в новый пакет `internal/boot` (сборка продукта из настроек; зависит от app, poll, runtime, github, brain, acceptor, events, store — cmd после этого только разбирает переменные окружения и вызывает boot). Тесты переезжают вместе с кодом; число тестов до/после совпадает; `go run ./tools/limits` — ok; ADR-0002 дополнит архитектор узлом `cmd → internal/boot → …`

Команда приёмки

go run ./tools/limits && go test ./cmd/agentstaff/... ./internal/boot/... -count=1

Ожидаемый вывод

limits: ok
ok

Ссылки

Issue #150 PR #153

Вердикты

delovdelo · 53b59cb · PASS · 07.09.2026 21:52

#152
agentstaff accept: без доступа к GitHub — код возврата 2, а не 0
cmd/agentstaff (accept.go, accept_test.go)
Готово
issue #152 закрыт, PR не найден
готово

Цель

Подкоманда `agentstaff accept` при незаданном доступе к GitHub (нет AGENTSTAFF_GITHUB_TOKEN и нет тройки переменных приложения) печатает «не задан доступ к GitHub: …» и завершается кодом 0 (замер архитектора 08.09 при живой сверке). Ошибка конфигурации обязана давать код 2, как остальные отказы подкоманды («приёмщик не собран» уже даёт 2). Исправить и закрепить тестом: при пустом окружении `runAccept` (или как называется точка входа подкоманды) возвращает код 2 и текст с «доступ к GitHub»

Команда приёмки

go test ./cmd/agentstaff/... -run 'TestAcceptWithoutGitHubAccessExits2' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #152 PR нет

Вердикты

вердиктов нет

#157
healthz: поле tenants — сколько арендаторов ведёт продукт
internal/httpapi (handler.go: healthResponse и обработчик /healthz; тест в board_test.go или новом healthz_test.go)PASS
Готово
PR #158 · слит · CI success · PASS 2037cd4 от delovdelo
готово

Цель

`GET /healthz` отвечает `{"ok":true,"version":"…"}`; для наблюдения за продом нужно видеть, сколько арендаторов ведёт продукт. Добавить в ответ поле `tenants` (целое число арендаторов из реестра; без реестра — 1, арендатор по умолчанию). Ответ остаётся совместимым: прежние поля не меняются

Команда приёмки

go test ./internal/httpapi/... -run 'TestHealthzTenants' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #157 PR #158

Вердикты

delovdelo · 2037cd4 · PASS · 08.09.2026 05:54

#159
Исполнитель: заполнять шаблон PR, а не вставлять его с плейсхолдерами
internal/worker (publish.go)PASS
Готово
PR #162 · слит · CI success · PASS ca9baab от delovdelo
готово

Цель

PR #158, сделанный встроенным исполнителем, содержит тело «Closes #157» и вывод команды приёмки, а затем — сырой шаблон .github/PULL_REQUEST_TEMPLATE.md с плейсхолдерами («Closes #<номер>», «<команда из карточки>»). Нужно: `internal/worker/publish.go` заполняет шаблон, если он есть: подставляет номер карточки, пакет из карточки, команду приёмки и её вывод в соответствующие разделы; плейсхолдеров в угловых скобках в теле PR не остаётся; без шаблона — прежнее тело. Тест `TestWorkerPullBodyFillsTemplate`: шаблон с плейсхолдерами → тело без «<» и «>» в строках Closes/команды, с номером карточки и командой

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerPullBodyFillsTemplate' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #159 PR #162

Вердикты

delovdelo · ca9baab · PASS · 08.09.2026 06:07

#163
Ф2-3: agentstaff doccheck facts — сверка документа с тестовой базой 1С через MCP-сервис
internal/facts (новый), internal/doccheck (facts.go), cmd/agentstaff (подкоманда doccheck facts)PASS
Готово
PR #169 · слит · CI success · PASS ef6fc42 от delovdelo
готово

Цель

Подкоманда `agentstaff doccheck facts <файл.md>` — слои 2 и 3 из docs/проект/Ф2-приёмка-документов.md: (1) адаптер к источнику фактов `internal/facts` — HTTP JSON-RPC 2.0 по образцу сервиса ERPDoctorMCP (POST на URL с basic auth: методы `tools/list` и `tools/call` с `params.name` и `params.arguments`; ответ `result` с `content[]` текст/JSON), настройки `AGENTSTAFF_FACTS_MCP_URL/LOGIN/PASSWORD`, таймаут 60 с, ошибки типизированные, пароль не в журнале; (2) проверки по документу: каждый объект из паспорта `объекты` и каждое полное имя вида `Справочник.X`/`Документ.X`/`РегистрСведений.X`/`РегистрНакопления.X` в теле существует — `tools/call get_object_metadata {full_name}` (ошибка/пусто → замечание «объект не найден»); реквизиты вида `Документ.X.Реквизит` — через `get_object_attributes`; (3) воспроизводимость: каждая цифра в разделе «Данные» (или таблице с колонкой «Запрос (1С)») имеет рядом запрос; проверка выполняет `execute_query {query, max_rows}`, сравнивает: целые количества точно, суммы с допуском 1 %; цифра без запроса — замечание «не воспроизводима» (не ошибка формы, но замечание); (4) паспортная `база` сверяется с `get_server_info` (имя базы). Вывод как у `doccheck form` (`doccheck: …`, ok/FAIL, коды 0/1/2; сервис недоступен — код 3 «BLOCKED: источник фактов недоступен»). Без новых зависимостей. Тесты — с фейковым MCP-сервером (httptest), отвечающим на tools/call по именам

Команда приёмки

go test ./internal/facts/... ./internal/doccheck/... -run 'TestFacts|TestDoccheckFacts' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #163 PR #169

Вердикты

delovdelo · ef6fc42 · PASS · 08.09.2026 06:43

#165
Исполнитель: экономить контекст — сжимать старые выводы инструментов, бюджет токенов настраиваемый
internal/worker (loop.go, publish.go), cmd/agentstaff (work.go: переменные)PASS
Готово
PR #166 · слит · CI success · PASS 3cdab51 от delovdelo
готово

Цель

Карточка #163 остановлена исполнителем на 16-м ходу: 429 тыс. входных токенов при пределе 400 тыс., потому что каждый ход пересылает всю историю с полными выводами инструментов (до 200 строк каждый). Нужно: (1) после каждого хода выводы инструментов старше N=4 ходов заменяются в истории на короткую сводку («read_file internal/x.go: 180 строк, сжато») — модель видит свежие выводы целиком, старые в одну строку; (2) бюджеты настраиваются переменными `AGENTSTAFF_WORKER_MAX_TURNS` (по умолчанию 40) и `AGENTSTAFF_WORKER_MAX_INPUT_TOKENS` (по умолчанию 800000), значения печатаются при старте круга; (3) при остановке по бюджету исполнитель перед комментарием в Issue делает commit и push того, что есть, в ветку feat/<n>-… и пишет в комментарий имя ветки — чтобы работу можно было продолжить, а не потерять; (4) в результате круга — суммарные входные и выходные токены

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerCompactsHistory|TestWorkerBudgetPushesBranch' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #165 PR #166

Вердикты

delovdelo · 3cdab51 · PASS · 08.09.2026 06:37

#167
internal/worker упёрся в лимит 3000 строк: вынести инструменты и историю в подпакеты
internal/worker (+ подпакеты tools, history)PASS
Готово
PR #176 · слит · CI success · PASS 328e0fa от delovdelo
готово

Цель

Пакет internal/worker — 2894 строки из 3000 (STANDARD.md §6.3). Вынести без изменения поведения: инструменты и границы каталога (tools.go, instruments.go) в `internal/worker/tools`, историю и сжатие (history.go) в `internal/worker/history`; тесты переезжают вместе с кодом; число тестов до/после совпадает; `go run ./tools/limits` — ok

Команда приёмки

go run ./tools/limits && go test ./internal/worker/... -count=1

Ожидаемый вывод

limits: ok
ok

Ссылки

Issue #167 PR #176

Вердикты

delovdelo · 328e0fa · PASS · 08.09.2026 07:06

#171
doccheck: объявление кода только в объявляющих разделах; подтверждение базы по адресу сервиса
internal/doccheck (codes.go, sections.go, facts.go)PASS
Готово
PR #174 · слит · CI success · PASS 53e3625 от delovdelo
готово

Цель

Живой прогон 08.09 на тестовой базе владельца показал два ложных замечания. (1) `doccheck form`: код требования в первой ячейке таблицы «Сопоставление с типовым функционалом 1С» (и любой другой таблицы вне объявляющих разделов) считался вторым объявлением → «дубль кода» и «нет источника». Правило: объявлением считается только строка таблицы (или заголовок) в объявляющем разделе этапа — для анализа это «Потребности», «Требования», «Проблемы»; для протокола — «Темы»; для лт — «Функциональные требования», «Пользовательские истории»; для тз — «Кейсы приёмки»; во всех остальных таблицах коды — ссылки (нить), не объявления. (2) `doccheck facts`: паспортная `база` (например `erp_dst`) сверялась только с полями ответа `get_server_info` (там `configuration_name: УправлениеПредприятием`, имени базы нет) → «база не подтверждена». Правило: база подтверждена, если её имя (без учёта регистра) встречается в любом строковом поле ответа get_server_info ИЛИ в пути адреса сервиса (`/erp_dst/hs/…`); иначе замечание с перечислением того, что сервис сообщил

Команда приёмки

go test ./internal/doccheck/... -run 'TestDoccheckFormReferencesAreNotDeclarations|TestDoccheckFactsBaseFromURL' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #171 PR #174

Вердикты

delovdelo · 53e3625 · PASS · 08.09.2026 06:57

#172
Исполнитель: план перед действиями, чтение файлов диапазоном строк, лимит суммарного чтения
internal/worker (loop.go, prompt.go), internal/worker/tools (после #167) или tools.goPASS
Готово
PR #182 · слит · CI success · PASS cc8dfdc от delovdelo
готово

Цель

Встроенный исполнитель дважды остановился на карточке #167 (разъезд пакета): 34 хода, 800 тыс. входных токенов, изменений нет — он читал файлы целиком и не начал писать. Нужно: (1) первый ход круга — план: мозг получает только карточку и список файлов пакета с размерами (`list_files` с числом строк) и обязан ответить планом из шагов (какие файлы читать, какие создавать), план записывается в журнал и в историю как один короткий ход; (2) `read_file(path, from, to)` — чтение диапазоном строк (по умолчанию первые 120), с подсказкой в ответе «файл N строк, показаны from–to»; (3) суммарный лимит прочитанных строк за круг (по умолчанию 6000; переменная `AGENTSTAFF_WORKER_MAX_READ_LINES`) — при превышении инструмент отвечает отказом «лимит чтения исчерпан, пиши код по тому, что уже прочитано»; (4) если к ходу 20 нет ни одного write_file — напоминание в ответе инструмента «половина бюджета без изменений — начни писать»; (5) в Result — число прочитанных и записанных строк

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerPlansFirst|TestWorkerReadBudget' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #172 PR #182

Вердикты

delovdelo · cc8dfdc · PASS · 08.09.2026 07:56

#173
deploy.sh: после выкладки сверять ключи настроек ключ-в-ключ с тем, что видит служба
scripts (deploy.sh)PASS
Готово
PR #178 · слит · CI success · PASS 73ea209 от delovdelo · FAIL ×1
готово

Цель

Урок соседнего проекта (архитектор EventCode, 08.09): выкат кода прошёл, а одна новая настройка до прода не доехала, и это никто не заметил. У нас служба systemd читает EnvironmentFile при старте, поэтому риск ниже, но проверки нет. Нужно: (1) `scripts/deploy.sh` после подтверждения версии сверяет список ключей (без значений) из `/opt/agentstaff/etc/agentstaff.env` со списком ключей окружения живого процесса (`systemctl show agentstaff -p MainPID` → `/proc/<pid>/environ`, только имена переменных `AGENTSTAFF_*` и `PORT`); расхождение → строка `deploy: настройки: ключ X есть в файле, но не в процессе` и код возврата 1 (без отката: код уже верный, но владельцу нужно знать); совпадение → `deploy: настройки: N ключей совпали`; (2) в dry-run — строка плана `deploy: план: сверка ключей настроек файла и процесса`. Значения в вывод не попадают

Команда приёмки

bash -n scripts/deploy.sh && scripts/deploy.sh --dry-run v0.1.0 | tee /dev/stderr | grep -q "сверка ключей настроек файла и процесса"

Ожидаемый вывод

deploy: план: сборка v0.1.0 linux/amd64
deploy: план: сверка ключей настроек файла и процесса

Ссылки

Issue #173 PR #178

Вердикты

delovdelo · 73ea209 · FAIL · 08.09.2026 07:12 delovdelo · 73ea209 · PASS · 08.09.2026 07:14

#179
Приёмщик: пропажа ожидаемой строки после подлога — тоже покраснение
internal/acceptor (mutation.go, verdict.go), scripts/accept.shPASS
Готово
PR #180 · слит · CI success · PASS 5c22e40 от delovdelo
готово

Цель

Приёмщик (internal/acceptor и scripts/accept.sh) после подлога считает команду покрасневшей только по коду возврата ≠ 0 или строке FAIL. Карточка #173 (PR #178): подлог убирал ожидаемую строку плана из вывода, команда оставалась зелёной по коду — вердикт «механизм не защищён тестами», хотя ожидаемые строки — часть контракта карточки. Нужно: после подлога краснотой считается любое из трёх: код ≠ 0, строка FAIL, или машинная сверка ожидаемых строк не прошла (та же функция, что в зелёном прогоне); в строке «подлог: …» вердикта указывать, чем именно покраснел («код 1» / «FAIL» / «пропала строка "…"»). Контрольная правка по-прежнему обязана оставлять все три зелёными. accept.sh — то же правило (эталон и продукт должны совпадать)

Команда приёмки

go test ./internal/acceptor/... -run 'TestAcceptorMutationMissingExpectedLineIsRed' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #179 PR #180

Вердикты

delovdelo · 5c22e40 · PASS · 08.09.2026 07:38

#183
doccheck all: форма и факты одной командой
internal/doccheck (all.go), cmd/agentstaff (doccheck.go)PASS
Готово
PR #196 · слит · CI success · PASS 6eddb87 от delovdelo · FAIL ×1 · не учтено вердиктов: 1
готово

Цель

Арендатор INFATON в команде приёмки документного этапа пишет две команды через `&&` (`doccheck form … && doccheck facts …`). Нужна подкоманда `agentstaff doccheck all <файл.md>`: сначала form; если form дал замечания — печатает их и завершается с кодом 1, facts не запускается (нет смысла сверять факты у документа без формы); если form ok — печатает `doccheck: форма ok` и запускает facts, итоговые строки как у facts (`doccheck: ok` / `doccheck: FAIL n замечаний` / `doccheck: BLOCKED: …`), коды 0/1/2/3 как у facts. Без переменных источника фактов — код 2 с текстом, какие переменные нужны (как у facts). Usage в main.go дополнить

Команда приёмки

go test ./internal/doccheck/... ./cmd/agentstaff/... -run 'TestDoccheckAll' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #183 PR #196

Вердикты

delovdelo · 6eddb87 · PASS · 08.09.2026 09:13 не учтено вердиктов: 1

#185
Исполнитель: трассировка ходов в журнал (AGENTSTAFF_WORKER_TRACE=1) и причина каждого отказа инструмента
internal/worker (loop.go, worker.go), internal/worker/tools (счётчики вызовов и отказов), cmd/agentstaff (work.go: переменная)PASS
Готово
PR #188 · слит · CI success · PASS dee57e7 от delovdelo
готово

Цель

Встроенный исполнитель четыре раза останавливался без единой записи (#163, #167, #179, #183: 16–40 ходов, до 800 тыс. токенов, «записано строк 0»), и из вывода круга нельзя понять, что делала модель. Нужно: (1) при `AGENTSTAFF_WORKER_TRACE=1` каждый ход пишется в журнал (slog INFO): номер хода, входные/выходные токены хода, stop_reason, первые 200 символов текста модели, и для каждого вызова инструмента — имя, аргументы (путь, диапазон, первые 80 символов команды/контента) и первые 120 символов ответа инструмента, включая отказы; (2) без переменной — как сейчас, плюс в итоговой строке круга число вызовов по инструментам (`read_file=…, write_file=…, run=…, отказов=…`); (3) отказы инструментов считаются и печатаются отдельно: «отказов инструментов: N (последний: …)»; (4) если модель три хода подряд не вызывает инструменты и не зовёт finish — исполнитель отвечает ей напоминанием «нужен вызов инструмента или finish» (в трассе видно)

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerTraceLogsTurns|TestWorkerCountsToolCalls' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #185 PR #188

Вердикты

delovdelo · dee57e7 · PASS · 08.09.2026 08:20

#186
Доставка событий: постоянный отказ получателя (4xx) не должен блокировать очередь арендатора
internal/events (Publisher.Deliver: классификация ошибок, пометка), internal/runtime (OwnerSignal/DeliverLoop: типизированные ошибки GitHub), internal/github (типизированная APIError со статусом уже есть — использовать)PASS
Готово
PR #190 · слит · CI success · PASS 8d7379e от delovdelo · FAIL ×1
готово

Цель

На проде 08.09 событие owner.decision_needed по «карточке» #47 (на самом деле PR) 856 раз за два часа получало 403 от GitHub (комментарий в PR требует права pull_requests:write, у приложения его нет) и всё это время держало 23 следующих события арендатора недоставленными: DeliverLoop повторяет головное событие каждые 5 секунд и не идёт дальше. Нужно: (1) ошибка получателя, которая не пройдёт от повтора — HTTP 4xx кроме 429 и 408 (403, 404, 410, 422) — считается окончательной: событие помечается доставленным с пометкой отказа (поле `delivery_note`/причина в журнале одной строкой ERROR), очередь идёт дальше; (2) временные ошибки (сеть, 5xx, 429) — как сейчас, но через ErrDeferred (не блокируя следующие события), с нарастающей паузой; (3) отдельная строка журнала раз в час: «недоставленных событий арендатора N, старейшее с …»; (4) сигнал владельцу, который нельзя доставить комментарием, дополнительно пишется в журнал WARN с текстом сигнала — чтобы факт не пропал

Команда приёмки

go test ./internal/events/... ./internal/runtime/... -run 'TestDeliverPermanentRefusalDoesNotBlock' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #186 PR #190

Вердикты

delovdelo · 8d7379e · FAIL · 08.09.2026 08:31 delovdelo · 8d7379e · PASS · 08.09.2026 08:37

#187
Конвейер: номер PR никогда не становится карточкой
internal/pipeline (applyPull: запрет создания из PR), internal/boot или internal/runtime (проверка при старте), internal/domain при необходимостиPASS
Готово
PR #189 · слит · CI success · PASS 353fe81 от delovdelo · FAIL ×1
готово

Цель

В БД прода есть «карточка» #47 арендатора agentstaff, хотя #47 — PR (закрыт 07.09): PR #48 в теле ссылался Closes #47, конвейер при pull_request/opened на неизвестную карточку завёл строку и повёл её до done, а затем сигнал владельцу упёрся в 403. Нужно: (1) конвейер при первом pull_request для номера, которого нет в таблице карточек, НЕ заводит строку сам: карточка появляется только из события issues (opened/edited) с блоком «## Карточка»; PR на неизвестный номер — «webhook ignored reason=card.unknown» (retryable, как сейчас для незнакомых), и это не меняется от того, что PR слит; (2) если в БД уже есть карточка, номер которой в GitHub — PR (проверка через Source при старте или при первом событии: у issue есть поле pull_request), конвейер помечает её `parked` с причиной «номер — PR» одной строкой WARN и больше не ведёт; (3) миграция не нужна — правка только логики и одна проверка при старте `serve` (список карточек арендатора × ListIssues: номера, которых нет среди issues, — паркуются с WARN)

Команда приёмки

go test ./internal/pipeline/... -run 'TestPipelineDoesNotInventCardFromPull' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #187 PR #189

Вердикты

delovdelo · 353fe81 · FAIL · 08.09.2026 08:30 delovdelo · 353fe81 · PASS · 08.09.2026 08:36

#191
Исполнитель: родные блоки разговора (thinking, tool_use, tool_result) и сжатие истории по объёму
internal/brain (brain.go, anthropic.go, compat.go), internal/worker/history, internal/worker (loop.go, trace.go, prompt.go), cmd/agentstaff (work.go: переменная предела)PASS
Готово
PR #194 · слит · CI success · PASS ec6e56d от delovdelo
готово

Цель

Трасса круга №183 (`AGENTSTAFF_WORKER_TRACE=1`, 14 ходов, 26 read_file, 0 write_file) показала две причины кругов без записи. (1) Модель claude-sonnet-5 по умолчанию отвечает блоками `thinking` (проверено живым запросом: `[thinking, text, tool_use]`, в `usage.output_tokens_details.thinking_tokens` их число); адаптер Anthropic их выбрасывает — ход 6 стоил 6446 выходных токенов, а в трассе `text=""` и три read_file. Разговор переигрывается модели текстом («[вызов …]» и «[read_file] …» в user), а не родными блоками, поэтому рассуждения и вызовы каждого хода теряются, и модель думает заново. (2) Сжатие истории по числу ходов (четыре последних) стирает содержимое прочитанных файлов уже при 20 тыс. входных токенов, и модель перечитывает те же файлы по кругу (ход 8 повторяет ход 3; ходы 12–14 — ходы 3–4), не доходя до записи. Нужно: (а) `brain.Message` несёт блоки, а не одну строку: у assistant — `thinking` (текст и `signature` как вернул API, возвращаются в запрос без изменений), `text`, `tool_use` (id, name, input); у user — `text` или `tool_result` (`tool_use_id`, content). Адаптер Anthropic шлёт и читает их в родном виде (`{"type":"thinking","thinking":…,"signature":…}`, `{"type":"tool_use",…}`, `{"type":"tool_result","tool_use_id":…,"content":…}`); совместимый адаптер — через `tool_calls` у assistant и сообщения `role: "tool"` с `tool_call_id`, `thinking` он не шлёт. `brain.Response` отдаёт блоки thinking (для переигрывания) и `ThinkingTokens` из `usage.output_tokens_details.thinking_tokens` (0, если поля нет). (б) История исполнителя переигрывает родные блоки: ход модели — как вернулся, ответы инструментов — tool_result по id вызова. Сжатие — по объёму, а не по ходам: ответы инструментов остаются целиком, пока история меньше предела в символах (по умолчанию 200 000; переменная `AGENTSTAFF_WORKER_HISTORY_CHARS`), при превышении сжимаются самые старые ответы (одной строкой «[сжато] …», как сейчас); вызовы и последний ход модели с его thinking не сжимаются никогда. (в) Трасса хода пишет `thinking_tokens` и перечень типов блоков ответа. (г) Строка системного промпта про «старше четырёх последних ходов» заменяется на правду про предел по объёму. Всё без сети: адаптеры проверяются на httptest с записанными телами.

Команда приёмки

go test ./internal/brain/ ./internal/worker/... -run 'TestAnthropicReplaysBlocks|TestCompatReplaysToolMessages|TestHistoryCompactsBySize|TestWorkerTraceLogsTurns' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #191 PR #194

Вердикты

delovdelo · ec6e56d · PASS · 08.09.2026 08:49

#197
Приёмщик: подлог, который не лёг, — BLOCKED, а не FAIL
internal/acceptor (mutation.go, verdict.go, run.go и их тесты)PASS
Готово
PR #201 · слит · CI success · PASS 2d55ba9 от delovdelo · FAIL ×1
готово

Цель

Подлог, который не лёг на код, — дефект карточки, а не работы, и не должен стоить исполнителю вердикта FAIL (08.09: три PR — #189, #190, #196 — получили FAIL от встроенного приёмщика только потому, что строка `sed:` карточки не совпала с кодом; второй такой FAIL блокирует карточку). Нужно: когда команда sed из карточки завершилась ошибкой или сумма файла после неё не изменилась (`mutationDidNotLand`), приёмщик выносит BLOCKED, а не FAIL: первая строка вердикта `BLOCKED <sha>: подлог не лёг: <причина>` (причина — «sed не отработал: …» либо «сумма файла не изменилась»), дальше как сейчас — сверка, подлог, модель. Мозг приёмщика в этом случае не спрашивается (закрытый вопрос задаётся только после состоявшегося подлога). Подлог, который лёг, но не покраснел, и подлог, который сломал сборку, остаются FAIL, как в STANDARD.md §4.4

Команда приёмки

go test ./internal/acceptor/ -run 'TestAcceptorMutationDidNotLandIsBlocked|TestAcceptorMutationStayedGreenIsFail' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #197 PR #201

Вердикты

delovdelo · 2d55ba9 · FAIL · 08.09.2026 09:30 delovdelo · 2d55ba9 · PASS · 08.09.2026 09:32

#198
Исполнитель: поле «Проверка:» и run_check — finish только после зелёной проверки арендатора
internal/worker (job.go, prompt.go, loop.go), internal/worker/tools (instruments.go, tools.go), их тестыPASS
Готово
PR #203 · слит · CI success · PASS f6fe93f от delovdelo
готово

Цель

Встроенный исполнитель закончил карточку #183 с зелёной командой приёмки, а CI покраснел на проверке `go run ./tools/limits` (функция теста длиннее 80 строк): исполнитель проверяет только команду приёмки, а арендатор проверяет ещё и стиль, пределы, запрещённые слова. Нужно: (1) необязательное поле карточки «Проверка:» — команда bash, которую исполнитель обязан прогнать зелёной после последней записи, прежде чем finish будет принят; разбирается из блока карточки так же, как «Сборка:» (одна строка или блок с отступом); (2) инструмент `finish` отказывает, если после последней записи команда проверки не прогонялась или была красной: ответ «сначала проверка: …» с последними 20 строками вывода; отказ считается в счётчиках отказов (#185); (3) новый инструмент `run_check` (как `run_acceptance`, но команда из поля «Проверка:»); карточка без поля — как сейчас, finish требует только зелёную приёмку; (4) в системном промпте исполнителя и в описании инструментов сказано про проверку; (5) первое сообщение круга показывает команду проверки рядом с командой приёмки

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerFinishNeedsGreenCheck|TestJobParsesCheckCommand' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #198 PR #203

Вердикты

delovdelo · f6fe93f · PASS · 08.09.2026 09:34

#199
Мозг: кэш промпта Anthropic и бюджет круга по некэшированным токенам
internal/brain (anthropic.go, brain.go), internal/worker (loop.go, trace.go, worker.go), cmd/agentstaff (work.go: строка итога)PASS
Готово
PR #207 · слит · CI success · PASS 28f7be1 от delovdelo
готово

Цель

После #191 история круга не сжимается по ходам, и каждый ход заново отправляет всё прочитанное: круги #197 и #198 остановились на бюджете входных токенов (840 302 и 820 600 при пределе 800 000 за 24 и 30 ходов) — один с пятью записями и зелёными тестами, но без приёмки, другой без единой записи. Бюджет считает повторно отправленные токены как новые, хотя Messages API умеет кэш промпта: повтор кэшированной части стоит десятую долю. Нужно: (1) адаптер Anthropic помечает системный промпт и последний блок последнего сообщения меткой `cache_control: {"type":"ephemeral"}` (описания инструментов кэшируются вместе с системой; метка в системе — блок `{"type":"text","text":…,"cache_control":…}`; в сообщениях — на последнем блоке содержимого последнего сообщения user); (2) `brain.Response` отдаёт `CachedInputTokens` из `usage.cache_read_input_tokens` и `CacheWriteTokens` из `usage.cache_creation_input_tokens` (0, если полей нет; совместимый адаптер — 0); (3) бюджет круга считает некэшированные входные токены: `input_tokens + cache_creation_input_tokens` (кэшированное чтение в бюджет не входит); предел по умолчанию остаётся 800 000; в итоговой строке круга и в трассе хода печатаются входные, из них кэшированные, и выходные; (4) при отсутствии кэша (совместимый адаптер) поведение прежнее. Проверить без сети на httptest: второй запрос несёт метки, а usage с cache_read_input_tokens не тратит бюджет

Команда приёмки

go test ./internal/brain/ ./internal/worker/... -run 'TestAnthropicMarksCache|TestWorkerBudgetSkipsCachedTokens' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #199 PR #207

Вердикты

delovdelo · 28f7be1 · PASS · 08.09.2026 09:59

#200
Исполнитель: незавершённая работа уходит в ветку, следующий круг продолжает с неё
internal/worker (git.go, publish.go, prompt.go, job.go, loop.go), их тестыPASS
Готово
PR #208 · слит · CI success · PASS b699738 от delovdelo
готово

Цель

Круг #197 остановился по бюджету с пятью записанными файлами и зелёными тестами; исполнитель закоммитил «незавершённая работа исполнителя: карточка #197» только в локальную копию, а следующий круг стирает копию (`Prepare` делает `RemoveAll`) и начинает с нуля — работа за 24 хода пропала. Уточнение по факту 08.09: незавершённую работу исполнитель уже отправляет в ветку карточки на origin (657428f в feat/197-blocked-fail); беда во втором круге: он начал с main, сделал ту же ветку заново и не смог запушить (non-fast-forward) — готовая работа с зелёной приёмкой осталась в копии. Нужно: (1) push ветки карточки — `--force-with-lease` от того sha, с которого круг начал (свой же прошлый круг перезаписывается, чужой коммит — нет); (2) следующий круг той же карточки начинает с этой ветки, если она есть на origin: копия берётся с неё, а не с main, ветка сохраняется; (3) первое сообщение круга говорит модели, что это продолжение: какой была причина остановки прошлого круга (из сообщения коммита) и `git diff --stat main...HEAD` — какие файлы уже изменены; план первого хода строится от этого; (4) если ветки нет — как сейчас; (5) готовая работа по-прежнему уходит в тот же PR-поток: коммит поверх незавершённого, push, PR с `Closes #N`

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerResumesFromUnfinishedBranch|TestWorkerPushesUnfinishedWork' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #200 PR #208

Вердикты

delovdelo · b699738 · PASS · 08.09.2026 10:05

#202
Исполнитель: видит подлог карточки, check_mutation и finish только после лёгшего подлога
internal/worker (job.go, prompt.go), internal/worker/tools (instruments.go, tools.go, новый mutation.go), их тесты
План
issue #202 открыт, PR ещё нет
ждёт исполнителя

Цель

Исполнитель не видит поля «Подлог» карточки: системный промпт несёт цель, пакет, команду приёмки, ожидаемый вывод и стоп-условия, а строку `sed:` — нет. Поэтому код пишется без оглядки на подлог, sed архитектора не ложится, и приёмщик отвечает «подлог не лёг» (08.09: PR #201 по карточке #197 — sed ждал `verdict.go`, исполнитель сделал развилку в `run.go`). Нужно: (1) блок «Подлог» карточки (файл, что, sed, ждём) попадает в системный промпт исполнителя с правилом: «код обязан быть построен так, чтобы sed подлога лёг ровно на одно место названного файла и после него команда приёмки краснела»; (2) новый инструмент `check_mutation`: применяет sed из карточки к копии названного файла во временном каталоге, отвечает «лёг» (сумма изменилась) или «не лёг» и число совпадений (`grep -c` шаблона до замены, если шаблон — простая подстановка `s/…/…/`); файлы копии не трогает; (3) `finish` отказан, если в карточке есть подлог, а `check_mutation` после последней записи не отвечал «лёг»: текст «сначала подлог: …»; отказ считается счётчиком отказов; (4) у карточки без подлога поведение прежнее

Команда приёмки

go test ./internal/worker/... -run 'TestWorkerFinishNeedsLandedMutation|TestToolsCheckMutation|TestJobParsesMutation' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #202 PR нет

Вердикты

вердиктов нет

#205
Исполнитель: edit_file для точечной правки, run не пишет файлы в обход инструментов
internal/worker/tools (новый edit.go, instruments.go, tools.go, shell.go или где живёт run), internal/worker (prompt.go), их тесты
План
issue #205 открыт, PR ещё нет
ждёт исполнителя

Цель

В круге #199 исполнитель правил файлы не инструментом write_file, а командами `run` с python-скриптами и heredoc (7 из 20 вызовов run), потому что write_file пишет файл целиком: переписать 346 строк ради двух — дорого в выходных токенах. Правки через run не считаются записью: счётчик «записано строк» врёт, состояние «после последней записи» для run_check и finish (#198) не сбрасывается, трасса не видит, что изменилось. Нужно: (1) инструмент `edit_file(path, old, new)`: заменяет ровно одно вхождение `old` на `new`; если вхождений нет или больше одного — отказ с числом вхождений, файл не тронут; пути — те же границы, что у write_file; правка считается записью (счётчик строк по разнице, сброс состояния проверки, трасса пишет путь и первые 80 символов old); (2) системный промпт: «для точечной правки — edit_file, для нового файла или полной замены — write_file; файлы командой run не править»; (3) `run` отказывает командам, которые явно пишут в файлы копии в обход инструментов: heredoc в файл (`<<` вместе с `>`/`>>` или `cat >`), `sed -i`, `python3 -`/`python -c` с `open(...,'w')`; текст отказа называет edit_file; обычные `go build`, `go test`, `grep`, `gofmt -l` не задеваются (gofmt -w — задевается: форматирование делается edit_file/write_file или перечитыванием, скажи об этом в промпте); (4) тесты на каждую ветку

Команда приёмки

go test ./internal/worker/... -run 'TestToolsEditFile|TestToolsRunRefusesFileWrites' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #205 PR нет

Вердикты

вердиктов нет

#206
Опрос: 403 на статусах коммитов не роняет чтение CI по check runs
internal/poll (poller_pull.go, poller_state.go), internal/github (тип ошибки 403 — StatusCode есть с #190), их тестыPASS
Готово
PR #210 · слит · CI success · PASS f5c4d3a от delovdelo
готово

Цель

Приложению GitHub на проде не выдано право Commit statuses: Read-only, и чтение свёрнутого статуса (`GET …/commits/{sha}/status`) отвечает 403 «Resource not accessible by integration». Поллер считает это ошибкой всего опроса PR: журнал за 90 минут 08.09 — 2921 строка ERROR, факт CI по check runs для этих PR из опроса пропадает (карточки двигались только вебхуками). Нужно: (1) 403/404 от источника commit status — не ошибка опроса: CI читается по check runs, как до карточки #129; (2) об отсутствии права продукт говорит одной строкой WARN на арендатора в час («статусы коммитов недоступны приложению (403): факт CI только по check runs; выдайте право Commit statuses: Read-only»), а не ERROR на каждый PR; (3) другие ошибки источника статусов (5xx, сеть) — как сейчас; (4) тест: читатель, чей CombinedStatus отвечает 403, а check runs зелёные, — карточка идёт в «на приёмке»

Команда приёмки

go test ./internal/poll/... -run 'TestPollerStatusForbiddenFallsBackToChecks' -v

Ожидаемый вывод

PASS
ok

Ссылки

Issue #206 PR #210

Вердикты

delovdelo · f5c4d3a · FAIL · 08.09.2026 10:12 delovdelo · f5c4d3a · PASS · 08.09.2026 10:13