Files
GmRelayBot/.mavis/plans/decision.json
T
Toutsu 2c9016a383
Deploy Telegram Bot / build-and-push (push) Successful in 7m18s
Deploy Telegram Bot / scan-images (push) Failing after 17s
Deploy Telegram Bot / deploy (push) Has been skipped
fix(shared,bot,discordbot): make club-picker Dapper calls AOT-safe (v3.9.2)
The 3.9.1 hotfix only repaired WizardDraftRepository, the most common
Dapper call in the wizard. The same AOT-unsafe CommandDefinition pattern
remained in 4 other places that the user hit immediately after the
deploy: the 'Choose visibility' wizard step triggers GetOwnerClubsAsync
when the user picks 'Публичная в витрине клуба' or 'Только для членов
клуба'. The wizard swallowed PlatformNotSupportedException, the
callback ack replied with '⚠️ Ошибка', and the next step never rendered.
Privacy 'didn't stick' from the user's perspective.

Two changes to fix the Discord side as well:

1. Switched GetOwnerClubsAsync / LoadClubsAsync / LoadManagerUserIdsAsync
   to the direct (sql, params) overload across TelegramWizardMessenger,
   DiscordWizardMessenger, DiscordWizardInteractionModule, and
   DiscordPermissionLookup — same pattern as the 3.9.1 fix.

2. Added Dapper.AOT module attribute ([module: Dapper.DapperAot]) and
   InterceptorsPreviewNamespaces to the DiscordBot project. The
   DiscordBot assembly was previously skipped by the AOT source
   generator, so even the direct-overload fix wouldn't have produced
   interceptors for the Discord-specific Dapper call sites. With this
   addition, the generator emits 3 DiscordBot-specific interceptors
   (DiscordWizardMessenger, DiscordWizardInteractionModule,
   DiscordPermissionLookup) and the AssemblyLoad ships with the right
   GmRelay.DiscordBot.generated.cs.

Also expanded the AOT shape regression tests to cover all 4
CommandDefinition sites + added a 'containingClass' parameter to
ExtractMethodBody to disambiguate the duplicated LoadClubsAsync names
in DiscordWizardInteractionModule.

Bumps: 3.9.1 -> 3.9.2.
2026-06-08 10:48:24 +03:00

23 lines
12 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"last_cycle": [
{
"task_id": "discord-wizard-impl",
"verdict": "manual_retry",
"reason": "Verifier FAIL — критически отсутствует `DiscordWizardInteractionModule`. Без него wizard может отрисовать только первый экран, кнопки/select/modal не обрабатываются, submitter зарегистрирован в DI но не вызывается. Стейт-машина Shared + IWizardMessenger + Discord-рендерер всё на месте, но без interaction module весь wizard декоративный. Также: deliverable.md в корне репо описывает Task 1, не Discord-адаптер; нет smoke-тестов на новый код. Новая попытка: добавить ТОЛЬКО interaction module, подключить submitter, обновить deliverable.md."
}
],
"next_cycle": [
{
"task_id": "discord-wizard-impl",
"title": "Discord-адаптер: добавить interaction module (handlers) + подключить submitter",
"prompt": "**Единственная цель** этой попытки: добавить недостающий `DiscordWizardInteractionModule` с обработчиками кнопок, StringSelectMenu и модалов. Без него wizard декоративен — verifier уже сделал FAIL.\n\n## Контекст: что УЖЕ есть (не трогай, не переписывай)\n\nПроверь, что у тебя на диске в `src/GmRelay.DiscordBot/Features/Sessions/Wizard/` (commit b81d865 на `feat/issue-112-wizard-refactor`):\n- `DiscordWizardContextStore.cs` — кэш контекста (guildId/channelId/messageId)\n- `DiscordWizardMessenger.cs` — реализация `IWizardMessenger` (edit/send/answer/getClubs)\n- `DiscordWizardStep.cs` — рендер кнопок/select/modal customId\n- `DiscordWizardSubmitter.cs` — финализация (BuildCommand → Shared.CreateSessionHandler → edit «✅ Создано»)\n- `DiscordWizardCommand.cs` — slash `/newsession-wizard`\n- `DiscordPermissionLookup.cs` — helper для owner/co-GM проверки\n\n## Что нужно добавить\n\n### 1. `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs`\n\nСоздай `DiscordWizardInteractionModule : ComponentInteractionModule<...>` (или split на 2 модуля, если NetCord требует). Реализуй handlers для:\n\n**Кнопки** (`[ComponentInteraction(\"wizard:btn:...\")]`):\n- `wizard:btn:choice:<step>:<value>` — choice-кнопки (Type, System, Duration, Capacity waitlist, Visibility, PickClub, Publish, PoolAddSlots, PoolSlotCapacity)\n- `wizard:btn:cancel` — отмена (delete draft + edit msg на «❌ Мастер отменён» + remove context)\n- `wizard:btn:back` — назад (draft.Step = previous, persist, re-render)\n- `wizard:btn:create` — финализация (вызвать `DiscordWizardSubmitter.SubmitAsync`)\n- `wizard:btn:resume:continue` — ре-рендер текущего шага draft'а\n- `wizard:btn:resume:restart` — удалить draft и запустить новый\n\n**StringSelectMenu** (`[ComponentInteraction(\"wizard:select:...\")]`):\n- `wizard:select:<step>` — Visibility / PickClub / PoolSystemDuration\n\n**Modal** (`[ModalInteraction(\"wizard:modal:...\")]`):\n- `wizard:modal:<step>` — Title / Description / Cover / System free-text / Duration free-text / DateTime / Capacity / PoolSlotDateTime / PoolSlotCapacity / PoolSystemDuration free-text\n\nКаждый handler:\n1. Достаёт `draft_id` из customId (формат: `wizard:btn:choice:step:value:<draftId>` — посмотри `DiscordWizardStep.ButtonCustomId` для фактического формата и подгони).\n2. Загружает draft из `IWizardDraftRepository.GetByIdAsync(draftId, ct)` — если нет, ответь ephemeral «Мастер не найден».\n3. Проверяет `OwnerId == Context.User.Id.ToString()` (иначе чужая кнопка → ignore + ephemeral).\n4. Формирует `WizardInteraction` (OwnerId, Text/CallbackPayload, InteractionId).\n5. Вызывает `GameCreationWizard.HandleInteractionAsync(draft, interaction, ct)`.\n6. Если рендер вернул `openModal != null` — ответь `InteractionCallback.Modal(modalProperties)` где `modalProperties = DiscordWizardStep.BuildModal(openModal, draft)`.\n7. Иначе — пере-рендери edit и ответь `InteractionCallback.DeferredModifyMessage` (или сразу edit через messenger).\n\n**Modal handler** отличается: после `HandleInteractionAsync` нужно отредактировать сообщение на новый шаг (через `DiscordWizardMessenger.EditDraftMessageAsync`).\n\n### 2. Подключи `DiscordWizardSubmitter`\n\nВ `DiscordWizardInteractionModule`:\n- Инжектируй `DiscordWizardSubmitter` через конструктор.\n- На нажатие `wizard:btn:create`: получи draft, вызови `_submitter.SubmitAsync(draft, ct)`, обработай результат (edit msg на «✅ Создано: N сессий» или retry/cancel buttons).\n- Удали из контекст-стора после успеха.\n\n### 3. Smoke-тест (минимально, 2-3 кейса)\n\n`tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs`:\n- Проверь, что класс существует и наследует `ComponentInteractionModule`.\n- Проверь, что есть методы с атрибутами `[ComponentInteraction(\"wizard:btn:choice:...\")]` и `[ModalInteraction(\"wizard:modal:...\")]` через reflection.\n- Проверь, что customId форматы совпадают с теми, что генерит `DiscordWizardStep`.\n\nЭто не настоящие runtime-тесты, а source-level проверки. Достаточно чтобы `dotnet test` показал зелёный smoke test на эту категорию.\n\n### 4. Обнови `deliverable.md` в корне репо\n\nЗамени содержимое на summary по Discord-адаптеру:\n- Список файлов (6 старых + новый `DiscordWizardInteractionModule.cs`)\n- Какие customId форматы, какие handlers\n- «Открытые вопросы»: deferred features (например, если не успел pool-flow или не все шаги)\n- Ссылка на коммит\n\n### 5. Build + test + commit + push\n\n```\ndotnet build GM-Relay.slnx\ndotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --no-build\ndotnet format --verify-no-changes\ngit add -A\ngit commit -m \"feat(discord): wire interaction handlers for wizard (issue #112)\"\ngit push origin feat/issue-112-wizard-refactor\n```\n\n## Acceptance (минимум)\n- `DiscordWizardInteractionModule.cs` существует, реализует handlers для buttons + select + modal\n- `DiscordWizardSubmitter` вызывается из `wizard:btn:create` handler'а\n- Build зелёный, все wizard-тесты + 2-3 новых smoke-теста зелёные\n- `dotnet format` без замечаний\n- deliverable.md обновлён\n- Коммит запушен\n\n## Не делай\n- Не переписывай существующие 6 файлов (если нет compile error).\n- Не пиши unit-тесты handlers с моками NetCord (используй source-level reflection как указано выше).\n- Не открывай PR (это owner work).\n- Не добавляй новые пакеты.\n- Не улучшай UX — только рабочий handler chain.\n\n## Если не хватает времени\nПриоритет (по убыванию, 30 мин cap):\n1. Только `DiscordWizardInteractionModule.cs` с handlers (≥ buttons + create + cancel) — это MUST.\n2. Wire submitter.\n3. Smoke test.\n4. Update deliverable.\n\nЕсли не успеваешь 1+2+3 — коммить то, что есть, и помечай в deliverable «открытые вопросы». Build green лучше, чем идеально завершённый код.\n\nЧитай `D:\\Projects\\Game\\CLAUDE.md` для команд.",
"assigned_to": "coder",
"verified_by": "verifier",
"verify_prompt": "Adversarial verification Discord-визарда (issue #112), retry attempt.\nНЕ перечитывай описание исполнителя — перезапускай команды и атакуй поведение.\n\n## Шаги\n\n1. `dotnet build D:\\Projects\\Game\\GM-Relay.slnx` — успех.\n2. `dotnet test D:\\Projects\\Game\\tests\\GmRelay.Bot.Tests\\GmRelay.Bot.Tests.csproj --verbosity normal` — все wizard-тесты + новые smoke-тесты на interaction module зелёные.\n3. `dotnet format --verify-no-changes --verbosity diagnostic` — без замечаний.\n4. `dotnet list package --vulnerable --include-transitive` в src/GmRelay.Bot и src/GmRelay.DiscordBot — нет HIGH/CRITICAL.\n5. `git grep \"Telegram.Bot\" D:\\Projects\\Game\\src\\GmRelay.DiscordBot/` — пусто.\n6. `git grep \"NetCord\" D:\\Projects\\Game\\src\\GmRelay.Shared/` — пусто (только doc-комменты допустимы).\n7. `git grep \"Reflection\" D:\\Projects\\Game\\src\\GmRelay.DiscordBot\\Features\\Sessions\\Wizard\\` — пусто.\n8. `DiscordWizardInteractionModule` существует в `src/GmRelay.DiscordBot/Features/Sessions/Wizard/`. Содержит handlers для `wizard:btn:*`, `wizard:select:*`, `wizard:modal:*` (через `[ComponentInteraction]` / `[ModalInteraction]`).\n9. `DiscordWizardSubmitter` вызывается из `DiscordWizardInteractionModule` (не dead code — есть явный `_submitter.SubmitAsync(...)` вызов в create-button handler).\n10. `deliverable.md` в корне репо обновлён — описывает Discord-адаптер, не Task 1.\n11. `DiscordWizardCommand` (slash `/newsession-wizard`) и `DiscordNewSessionCommand` (text `/newsession`) оба компилируются.\n12. `dotnet grep` — `GameCreationWizard` существует только в Shared.\n\n## Adversarial\n- Прочти `DiscordWizardInteractionModule.cs` целиком. Ищи: NRE на `Context.User as GuildInteractionUser`, отсутствие null-проверок, customId overflow, потеря draft_id при парсинге customId.\n- Проверь, что `wizard:btn:create` handler действительно вызывает `DiscordWizardSubmitter` (а не просто `AnswerCallback`).\n- Проверь, что `wizard:btn:cancel` handler удаляет draft И чистит `DiscordWizardContextStore`.\n- Проверь, что modal handler корректно парсит `ModalInteractionData.Components` и достаёт `TextInput` value.\n\n## Критерии FAIL\n- `DiscordWizardInteractionModule` всё ещё отсутствует или содержит только stub/empty handlers.\n- `DiscordWizardSubmitter` остаётся dead code (не вызывается нигде).\n- Любой тест красный.\n- `dotnet build` падает.\n- `dotnet format` ругается.\n- Есть vulnerable HIGH/CRITICAL.\n- Telegram.Bot в DiscordBot или NetCord в Shared (кроме doc).\n- Reflection в AOT-коде.\n- `GameCreationWizard` дублируется.\n- `/newsession` text-only сломан.\n- deliverable.md в репо не обновлён (по-прежнему описывает Task 1).\n- Коммит не запушен.\n- Серьёзные баги: NRE без null-проверки, modal handler теряет значение TextInput, create button не вызывает submitter.\n\nНа FAIL укажи конкретный файл/строку/команду и обоснование.",
"timeout_ms": 1800000
}
],
"plan_complete": false,
"message_to_user": "Возвращаю Discord-адаптер на доработку: главная проблема — нет interaction module (handlers кнопок/select/modal), без них wizard декоративный. Подключить submitter, добавить smoke-тесты, обновить deliverable."
}