Skip to content

feat(shared): Guild Automation Module Executor seam + AutoMessages pilot - #901

Merged
LucasSantana-Dev merged 2 commits into
release/v2.12.0from
chore/skills-setup-and-automation-pilot
May 20, 2026
Merged

LucasSantana-Dev merged 2 commits into
release/v2.12.0from
chore/skills-setup-and-automation-pilot

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented May 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Two-commit branch: foundational architecture docs + first concrete implementation of the new Module Executor seam for Guild Automation.

docs(architecture) commit

  • CONTEXT.md — project-wide domain glossary (Guild, Member, User, Track, Source, Search Engine, Queue, Player, Session, Autoplay, Candidate Source, Recommendation Engine, TrackHistory, Moderation Action, Moderation Case, AutoMod, AutoMod Rule, Guild Automation, Manifest, Drift, Run, XP, Level, Role Grant, AutoRole, Reaction Role, Account Link, Event Subscription, plus the three new Module Executor terms). Flags two known ambiguities: source field overload across TrackMetadata vs RecommendationBasis, and the dead engine field on TrackMetadata.
  • 3 ADRs under docs/decisions/:
    • 2026-05-19-automod-does-not-create-moderation-cases.md — locks the deliberate split between AutoMod auto-enforcement and the appeal-flow-bearing ModerationCase audit trail.
    • 2026-05-19-queue-resolver-defensive-fallback-chain.md — keeps the six-path resolveGuildQueue fallback chain pending 14 days of queue_resolution_source Sentry telemetry; ships observability + guardrail expansion + cache-scan reorder + discord-player canary CI as part of the same pilot. Revisit 2026-06-02.
    • 2026-05-19-guild-automation-module-executors.md — replaces the 1238-LOC GuildAutomationExecutionService + the duplicated bot/utils/guildAutomation/applyPlan.ts with seven per-module Module Executors behind a Capture / Diff / Apply seam in packages/shared/src/services/guildAutomation/, plus a lower DiscordWriteAdapter seam (bot DiscordJsAdapter, backend DiscordRestAdapter). Partial-success rollback via existing GuildAutomationDrift.
  • docs/agents/ (3 files) — issue-tracker.md, triage-labels.md, domain.md capture the matt-pocock skill configs so downstream skills (to-issues, triage, improve-codebase-architecture, etc.) read the right vocabulary.
  • .gitignore — adds graphify-out/ (knowledge-graph build artifacts; rebuild with /graphify --update).

feat(shared) commit

First concrete Module Executor implementing the seam shape from the ADR: AutoMessages Module Executor (DB-only, no Discord REST, simplest module to validate the shape).

  • packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts
    • Exposes a port-style AutoMessagesPort decoupled from AutoMessageService (avoids the full Prisma row leaking through Pick<Service>).
    • capture(ctx) reads welcome + leave rows in parallel.
    • diff(live, section) is pure; emits create | update | noop per type. Monolith-parity behavior: empty section[type] or missing message → noop (matches the existing if (!payload?.message) return in upsertAutoMessage).
    • apply(diff, ctx) walks ops via the port, returns applied actions for GuildAutomationRun.operations.
  • packages/shared/src/services/guildAutomation/autoMessagesExecutor.spec.ts — 4 tests covering create both, update existing + manifest-absent leave, noop-on-empty-message, noop-on-empty-section.

Why

Came out of /improve-codebase-architecture walking packages/:

  • GuildAutomationExecutionService (1238 LOC, 7 module types entangled) was the worst single-file friction.
  • A parallel apply path in packages/bot/src/utils/guildAutomation/applyPlan.ts duplicates plan-building logic.
  • Tests of any single module currently need mocks for 6 unrelated module types.

Then /grill-with-docs resolved the domain vocabulary, /research-and-decide (with critic agent challenge) locked the four design choices, and /adt-tdd proved the seam shape on AutoMessages.

Not in this PR (per ADR pilot plan)

  • Adapter wrapping the real AutoMessageService → AutoMessagesPort
  • Export from shared/src/services/guildAutomation/index.ts
  • Backend orchestrator swap (GuildAutomationExecutionService.upsertAutoMessage → call this executor)
  • Bot consumer wire-up + deletion of applyPlan.ts automessages logic
  • Remaining 6 Module Executors (Moderation, ReactionRoles, CommandAccess, Onboarding, Channels, Roles) — one per follow-up PR per the ADR's sequencing

Test plan

  • packages/shared/src/services/guildAutomation/autoMessagesExecutor.spec.ts — 4/4 passing locally
  • Org-wide reusable quality workflow (lint, knip, semgrep, CodeQL, trivy, actionlint, hadolint) — automatic on push
  • CodeRabbit / Greptile auto-review on PR
  • SonarCloud coverage check on shared package

Refs

  • docs/decisions/2026-05-19-guild-automation-module-executors.md
  • docs/decisions/2026-05-19-queue-resolver-defensive-fallback-chain.md
  • docs/decisions/2026-05-19-automod-does-not-create-moderation-cases.md
  • CONTEXT.md

Summary by CodeRabbit

  • Documentation

    • Added comprehensive domain vocabulary glossary and agent skill documentation for development workflows
    • Added architectural decision records for guild automation and queue resolution strategies
  • Tests

    • Expanded test coverage for auto-messages executor with capture/diff/apply scenarios
  • Chores

    • Updated build artifact exclusions in version control

Review Change Stack

…ocock skills configs

- CONTEXT.md: project-wide domain glossary (tenancy, music runtime, autoplay,
  guild configuration, moderation, engagement, role granting, integrations).
  Flags two known ambiguities (source field overload, dead engine field).
  Canonical access path for Player is resolveGuildQueue (per the queueResolver ADR).
- docs/decisions/2026-05-19-automod-does-not-create-moderation-cases.md:
  deliberate split between AutoMod (auto enforcement) and ModerationCase
  (manual moderator audit trail with appeal flow). Considered options and
  revisit triggers recorded.
- docs/decisions/2026-05-19-queue-resolver-defensive-fallback-chain.md:
  keep the six-path resolveGuildQueue fallback chain pending production
  telemetry (queue_resolution_source Sentry tag). Pilot pairs the
  observability with cache-scan reorder, guard expansion to autoplay +
  musicRecommendation, nodes.resolve doc comment, and a discord-player
  canary CI job. Revisit 2026-06-02.
- docs/decisions/2026-05-19-guild-automation-module-executors.md:
  replace the 1238-LOC GuildAutomationExecutionService + duplicated
  bot/utils/guildAutomation/applyPlan.ts with seven per-module Module
  Executors behind a Capture/Diff/Apply seam in
  shared/src/services/guildAutomation/, plus a DiscordWriteAdapter lower
  seam (bot DiscordJsAdapter, backend DiscordRestAdapter). Partial-success
  rollback via existing GuildAutomationDrift. Pilot Executor = AutoMessages.
- docs/agents/{issue-tracker,triage-labels,domain}.md: matt-pocock skill
  configs (GitHub issue tracker, canonical triage labels mapped 1:1, single-
  context domain layout with ADRs in docs/decisions/ rather than docs/adr/).
- .gitignore: graphify-out/ build artifacts (graph.json, GRAPH_REPORT.md,
  cache/, manifest.json, cost.json).
First concrete implementation of the Capture/Diff/Apply seam recorded in
docs/decisions/2026-05-19-guild-automation-module-executors.md. AutoMessages
is the simplest module (DB-only, no Discord REST) and proves the executor
shape before migrating the other six.

- Exposes a port-style `AutoMessagesPort` decoupled from `AutoMessageService`
  so executors depend on a structural interface, not the full Prisma row
  type (`Pick<AutoMessageService>` leaks the entire `auto_messages` table
  shape).
- `capture(ctx) → AutoMessagesLiveState` reads welcome + leave rows in
  parallel via the port.
- `diff(live, section) → { ops }` is pure; emits `create`, `update`, or
  `noop` per type. Lock-in for monolith parity: empty `section[type]` and
  missing `message` both produce `noop` (matches `upsertAutoMessage`'s
  `if (!payload?.message) return`).
- `apply(diff, ctx) → { applied }` walks ops, calls port methods,
  returns the applied action list for the orchestrator to persist on
  `GuildAutomationRun.operations`.

Tests:
- happy path: create welcome + leave when neither exists
- update path: existing welcome → updateMessage, manifest-absent leave → noop
- noop path: manifest payload with no message
- noop path: empty manifest section

Wiring deferred (next pilot PR per ADR plan): adapter from real
`AutoMessageService` to `AutoMessagesPort`, export from
`shared/src/services/guildAutomation/index.ts`, backend orchestrator swap,
bot consumer + deletion of duplicated automessages logic in
`packages/bot/src/utils/guildAutomation/applyPlan.ts`.

Refs: docs/decisions/2026-05-19-guild-automation-module-executors.md
@vercel

vercel Bot commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment May 20, 2026 12:30am

Request Review

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LucasSantana-Dev has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@github-actions

Copy link
Copy Markdown

Failed to generate code suggestions for PR

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR establishes domain vocabulary via a new glossary (CONTEXT.md), documents the Module Executor architecture pattern through an ADR, implements AutoMessages as the pilot executor with full test coverage, records supporting design decisions for moderation and queue resolution, and provides tooling documentation for team collaboration via the gh CLI and triage conventions.

Changes

Guild Automation Architecture and Documentation

Layer / File(s) Summary
Domain vocabulary foundation
CONTEXT.md, .gitignore
CONTEXT.md introduces canonical terminology across tenancy, music runtime/autoplay, guild automation reconciliation (Manifest/Drift/Run/Module Executor/DiscordWriteAdapter), moderation, engagement, role granting, and integrations. Includes flagged ambiguities and example dialogue. .gitignore excludes Graphify build artifacts.
Guild Automation Module Executor architecture ADR
docs/decisions/2026-05-19-guild-automation-module-executors.md
Specifies Module Executor pattern with Capture/Diff/Apply seam and DiscordWriteAdapter abstraction. Enumerates seven executors (Roles, Channels, Onboarding, Moderation, AutoMessages, ReactionRoles, CommandAccess). Documents pilot-first migration starting with AutoMessages, rollback as partial-success with reconcile-on-next-run, and revisit triggers.
AutoMessages executor pilot implementation
packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts, packages/shared/src/services/guildAutomation/autoMessagesExecutor.spec.ts
Implements first Module Executor with exported types for state snapshots, manifest sections, diff operations (create/update/noop), and executor context. Factory exports capture(), diff(), and apply() operations. Test suite verifies creation, update, noop, and empty-diff scenarios with mocked AutoMessages service.
Supporting architecture decisions
docs/decisions/2026-05-19-automod-does-not-create-moderation-cases.md, docs/decisions/2026-05-19-queue-resolver-defensive-fallback-chain.md
AutoMod ADR clarifies that AutoMod-triggered enforcement intentionally does not create ModerationCase records. Queue Resolver ADR accepts maintaining 6-path fallback with concrete implementation checklist: queue_resolution_source telemetry, expanded guardrail test, cache-scan reorder, and CI canary against pinned discord-player version.
Agent and tooling documentation
docs/agents/domain.md, docs/agents/issue-tracker.md, docs/agents/triage-labels.md
Domain.md specifies repo doc layout, glossary vocabulary usage rules, and ADR conflict-surfacing policy. Issue-tracker.md documents gh CLI conventions for create/view/list/comment/close operations, repo inference behavior, and labeling guidance. Triage-labels.md maps five canonical roles to GitHub issue label strings and clarifies orthogonality to category/area/size labels.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

shared, enhancement, size/l

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: introducing the Guild Automation Module Executor architectural seam and the AutoMessages pilot implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/skills-setup-and-automation-pilot

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/agents/domain.md`:
- Around line 9-20: The markdown fenced code block in docs/agents/domain.md is
missing a language tag (MD040); update the triple-backtick fence that contains
the directory tree to include a language identifier (e.g., use "text" so it
becomes ```text) so markdownlint stops warning; locate the untyped fence around
the directory tree snippet and add the language tag to the opening fence.

In `@docs/agents/issue-tracker.md`:
- Line 8: The documentation line using the gh command is missing the --json flag
so jq filtering won't work; update the example `gh issue view` invocation in
docs/agents/issue-tracker.md to include `--json` with the appropriate fields
(for example `--json comments,labels` or `--json body,comments,labels`) so the
command emits structured JSON that jq can filter, and replace the existing `gh
issue view <number> --comments` snippet with the corrected command string.

In `@docs/decisions/2026-05-19-guild-automation-module-executors.md`:
- Line 61: The ADR uses inconsistent module path styles
(`shared/src/services/guildAutomation/` vs `packages/shared/...`); update all
occurrences to repo-root-relative paths (e.g., replace
`shared/src/services/guildAutomation/` and any `shared/...` variants with
`packages/shared/src/services/guildAutomation/...`) so links and copy/paste
navigation are reliable; search for the strings shown in the diff and also
update the other noted occurrences around lines referenced (including the
mentions at 80-80, 94-94, 97-97) to the normalized `packages/shared/...` form
and ensure `DiscordWriteAdapter` and any other referenced symbols still point to
the correct path.

In `@packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts`:
- Around line 115-141: The apply method in autoMessagesExecutor.ts performs
create/update operations via svc.createMessage and svc.updateMessage but doesn't
emit ServerLogService audit entries; update apply to call ServerLogService.log
(or the project's ServerLogService API) after successful create and update
operations, including relevant details (guildId from ctx, operation type,
message id or created resource id, channelId, and the diff payload), ensuring
you log both the create branch (after svc.createMessage resolves) and the update
branch (after svc.updateMessage resolves) and only record the audit entry on
success; reference the apply function, svc.createMessage, svc.updateMessage, and
use the project's ServerLogService methods to record the actions.
- Around line 46-63: The public executor/port types use plain strings for
IDs—update ExecutorContext and AutoMessagesPort to use branded ID types (e.g.,
GuildId and ChannelId) instead of string: change ExecutorContext.guildId to
GuildId; change method parameters guildId in
getWelcomeMessage/getLeaveMessage/createMessage to GuildId; change
createMessage/options.channelId and updateMessage.data.channelId to ChannelId;
ensure the file imports the branded types (GuildId, ChannelId) from the shared
ID definitions so the signatures for getWelcomeMessage, getLeaveMessage,
createMessage, and updateMessage compile with the stronger types.
- Around line 74-143: The executor methods capture, diff, and apply must return
the shared Result<T> wrapper instead of raw values; update signatures to
Promise<Result<AutoMessagesLiveState>>, Result<AutoMessagesDiff>, and
Promise<Result<AutoMessagesResult>> respectively, import the Result (and helpers
like ok/err) from the shared types, wrap successful returns with ok(...) (for
diff which is synchronous return ok({ ops })), and surround async work in
capture/apply with try/catch to return err(...) on exceptions while preserving
existing logic (refer to function names capture, diff, apply, types
AutoMessagesLiveState, AutoMessagesDiff, AutoMessagesResult, and helpers
svc.createMessage/svc.updateMessage).
- Around line 90-110: The current logic in autoMessagesExecutor.ts treats any
manifest entry without a message as a noop, which prevents updates that only
change enabled or channelId; update the decision so that: if !have and no
message then still noop (can't create without message), but if have exists then
compute diffs for each field (message, channelId, enabled) and push an 'update'
op when any provided field in want is different from have; when constructing the
'update' op include only the fields present in want (avoid setting message to
undefined) and keep the existing behavior for 'create' and 'noop' branches;
refer to variables/functions/constructs ops, want, have and the
'create'/'update'/'noop' op kinds to locate where to change the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef366c58-725f-46fc-8578-4ecc5311a828

📥 Commits

Reviewing files that changed from the base of the PR and between 22b793d and 7e07f2f.

📒 Files selected for processing (10)
  • .gitignore
  • CONTEXT.md
  • docs/agents/domain.md
  • docs/agents/issue-tracker.md
  • docs/agents/triage-labels.md
  • docs/decisions/2026-05-19-automod-does-not-create-moderation-cases.md
  • docs/decisions/2026-05-19-guild-automation-module-executors.md
  • docs/decisions/2026-05-19-queue-resolver-defensive-fallback-chain.md
  • packages/shared/src/services/guildAutomation/autoMessagesExecutor.spec.ts
  • packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts

Comment thread docs/agents/domain.md
Comment on lines +9 to +20
```
/
├── CONTEXT.md ← not yet created; proceed silently if absent
├── docs/
│ ├── decisions/ ← ADRs live here
│ │ └── YYYY-MM-DD-<slug>.md
│ ├── specs/ ← /adt-specs-spec-new output
│ ├── plans/ ← /plan output
│ └── references/
├── packages/ ← bot, backend, frontend, shared
└── ...
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block to satisfy markdownlint (MD040).

This fence is untyped, which will keep lint warnings active.

Suggested fix
-```
+```text
 /
 ├── CONTEXT.md                    ← not yet created; proceed silently if absent
 ├── docs/
 │   ├── decisions/                ← ADRs live here
 │   │   └── YYYY-MM-DD-<slug>.md
 │   ├── specs/                    ← /adt-specs-spec-new output
 │   ├── plans/                    ← /plan output
 │   └── references/
 ├── packages/                     ← bot, backend, frontend, shared
 └── ...
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/agents/domain.md around lines 9 - 20, The markdown fenced code block in
docs/agents/domain.md is missing a language tag (MD040); update the
triple-backtick fence that contains the directory tree to include a language
identifier (e.g., use "text" so it becomes ```text) so markdownlint stops
warning; locate the untyped fence around the directory tree snippet and add the
language tag to the opening fence.


</details>

<!-- fingerprinting:phantom:triton:hawk -->

<!-- This is an auto-generated comment by CodeRabbit -->

## Conventions

- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify gh CLI output modes for issue view and confirm JSON fields for jq usage.
gh issue view --help | sed -n '1,220p' | rg -n --context 2 -- '--json|--jq|--comments'

Repository: LucasSantana-Dev/Lucky

Length of output: 416


Add --json flag to enable jq filtering in the issue read command.

The gh issue view <number> --comments command doesn't output JSON by default, so jq filtering and label extraction won't work as described. The --json flag must be included to produce structured output.

Suggested fix
-- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
+- **Read an issue**: `gh issue view <number> --json number,title,body,labels,comments --jq '{number, title, body, labels: [.labels[].name], comments: [.comments[].body]}'`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
- **Read an issue**: `gh issue view <number> --json number,title,body,labels,comments --jq '{number, title, body, labels: [.labels[].name], comments: [.comments[].body]}'`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/agents/issue-tracker.md` at line 8, The documentation line using the gh
command is missing the --json flag so jq filtering won't work; update the
example `gh issue view` invocation in docs/agents/issue-tracker.md to include
`--json` with the appropriate fields (for example `--json comments,labels` or
`--json body,comments,labels`) so the command emits structured JSON that jq can
filter, and replace the existing `gh issue view <number> --comments` snippet
with the corrected command string.


### Location

- **A. `shared/src/services/guildAutomation/` + `DiscordWriteAdapter` (accepted).** Both packages consume the same executors. Solves the bot↔backend duplication that would otherwise need a separate follow-up ADR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Standardize shared-module paths to repo-root-relative paths.

This ADR mixes packages/shared/... and shared/... path styles. Please normalize to repo-root-relative paths (e.g., packages/shared/src/services/guildAutomation/...) so links and copy/paste navigation stay reliable.

Also applies to: 80-80, 94-94, 97-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/decisions/2026-05-19-guild-automation-module-executors.md` at line 61,
The ADR uses inconsistent module path styles
(`shared/src/services/guildAutomation/` vs `packages/shared/...`); update all
occurrences to repo-root-relative paths (e.g., replace
`shared/src/services/guildAutomation/` and any `shared/...` variants with
`packages/shared/src/services/guildAutomation/...`) so links and copy/paste
navigation are reliable; search for the strings shown in the diff and also
update the other noted occurrences around lines referenced (including the
mentions at 80-80, 94-94, 97-97) to the normalized `packages/shared/...` form
and ensure `DiscordWriteAdapter` and any other referenced symbols still point to
the correct path.

Comment on lines +46 to +63
export type ExecutorContext = { guildId: string }

export type AutoMessagesPort = {
getWelcomeMessage(guildId: string): Promise<AutoMessageSnapshot>
getLeaveMessage(guildId: string): Promise<AutoMessageSnapshot>
createMessage(
guildId: string,
type: AutoMessageType,
data: { message: string },
options?: { channelId?: string },
): Promise<{ id: string }>
updateMessage(
id: string,
data: {
message?: string
channelId?: string
enabled?: boolean
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use branded ID types in executor contracts.

guildId and channelId are plain string in the public executor/port types, which weakens type-level ID safety across the seam.

As per coding guidelines, packages/{bot,backend,shared}/src/**/*.ts: "Use branded types (e.g., GuildId, UserId, ChannelId) for Discord IDs throughout the codebase to prevent type-level ID confusion".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts` around
lines 46 - 63, The public executor/port types use plain strings for IDs—update
ExecutorContext and AutoMessagesPort to use branded ID types (e.g., GuildId and
ChannelId) instead of string: change ExecutorContext.guildId to GuildId; change
method parameters guildId in getWelcomeMessage/getLeaveMessage/createMessage to
GuildId; change createMessage/options.channelId and updateMessage.data.channelId
to ChannelId; ensure the file imports the branded types (GuildId, ChannelId)
from the shared ID definitions so the signatures for getWelcomeMessage,
getLeaveMessage, createMessage, and updateMessage compile with the stronger
types.

Comment on lines +74 to +143
async capture(ctx: ExecutorContext): Promise<AutoMessagesLiveState> {
const [welcome, leave] = await Promise.all([
svc.getWelcomeMessage(ctx.guildId),
svc.getLeaveMessage(ctx.guildId),
])
return { welcome, leave }
},

diff(
live: AutoMessagesLiveState,
section: AutoMessagesManifestSection,
): AutoMessagesDiff {
const ops: AutoMessagesDiffOp[] = []
for (const type of MODULE_TYPES) {
const want = section[type]
const have = live[type]
if (!want?.message) {
ops.push({ kind: 'noop', type })
continue
}
if (!have) {
ops.push({
kind: 'create',
type,
message: want.message,
channelId: want.channelId,
})
continue
}
ops.push({
kind: 'update',
type,
id: have.id,
message: want.message,
channelId: want.channelId,
enabled: want.enabled,
})
}
return { ops }
},

async apply(
diff: AutoMessagesDiff,
ctx: ExecutorContext,
): Promise<AutoMessagesResult> {
const applied: AutoMessagesResult['applied'] = []
for (const op of diff.ops) {
if (op.kind === 'create') {
await svc.createMessage(
ctx.guildId,
op.type,
{ message: op.message },
{ channelId: op.channelId },
)
applied.push({ type: op.type, action: 'create' })
continue
}
if (op.kind === 'update') {
await svc.updateMessage(op.id, {
message: op.message,
channelId: op.channelId,
enabled: op.enabled,
})
applied.push({ type: op.type, action: 'update' })
continue
}
applied.push({ type: op.type, action: 'noop' })
}
return { applied }
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Return Result<T> from executor methods.

capture, diff, and apply currently return raw values/Promises instead of the shared explicit-error wrapper contract.

As per coding guidelines, packages/shared/src/**/*.ts: "Use Result<T> type wrapper for function returns to enable explicit error handling without throwing exceptions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts` around
lines 74 - 143, The executor methods capture, diff, and apply must return the
shared Result<T> wrapper instead of raw values; update signatures to
Promise<Result<AutoMessagesLiveState>>, Result<AutoMessagesDiff>, and
Promise<Result<AutoMessagesResult>> respectively, import the Result (and helpers
like ok/err) from the shared types, wrap successful returns with ok(...) (for
diff which is synchronous return ok({ ops })), and surround async work in
capture/apply with try/catch to return err(...) on exceptions while preserving
existing logic (refer to function names capture, diff, apply, types
AutoMessagesLiveState, AutoMessagesDiff, AutoMessagesResult, and helpers
svc.createMessage/svc.updateMessage).

Comment on lines +90 to +110
if (!want?.message) {
ops.push({ kind: 'noop', type })
continue
}
if (!have) {
ops.push({
kind: 'create',
type,
message: want.message,
channelId: want.channelId,
})
continue
}
ops.push({
kind: 'update',
type,
id: have.id,
message: want.message,
channelId: want.channelId,
enabled: want.enabled,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

enabled/channelId-only manifest updates are silently dropped.

Line 90 forces noop when message is absent, so payloads like { welcome: { enabled: false } } can never update an existing row even though the manifest type allows it.

💡 Proposed fix
-                if (!want?.message) {
+                if (!want) {
                     ops.push({ kind: 'noop', type })
                     continue
                 }
-                if (!have) {
+                if (!have && !want.message) {
+                    ops.push({ kind: 'noop', type })
+                    continue
+                }
+                if (!have) {
                     ops.push({
                         kind: 'create',
                         type,
                         message: want.message,
                         channelId: want.channelId,
                     })
                     continue
                 }
+                if (
+                    want.message === undefined &&
+                    want.channelId === undefined &&
+                    want.enabled === undefined
+                ) {
+                    ops.push({ kind: 'noop', type })
+                    continue
+                }
                 ops.push({
                     kind: 'update',
                     type,
                     id: have.id,
                     message: want.message,
                     channelId: want.channelId,
                     enabled: want.enabled,
                 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts` around
lines 90 - 110, The current logic in autoMessagesExecutor.ts treats any manifest
entry without a message as a noop, which prevents updates that only change
enabled or channelId; update the decision so that: if !have and no message then
still noop (can't create without message), but if have exists then compute diffs
for each field (message, channelId, enabled) and push an 'update' op when any
provided field in want is different from have; when constructing the 'update' op
include only the fields present in want (avoid setting message to undefined) and
keep the existing behavior for 'create' and 'noop' branches; refer to
variables/functions/constructs ops, want, have and the 'create'/'update'/'noop'
op kinds to locate where to change the logic.

Comment on lines +115 to +141
async apply(
diff: AutoMessagesDiff,
ctx: ExecutorContext,
): Promise<AutoMessagesResult> {
const applied: AutoMessagesResult['applied'] = []
for (const op of diff.ops) {
if (op.kind === 'create') {
await svc.createMessage(
ctx.guildId,
op.type,
{ message: op.message },
{ channelId: op.channelId },
)
applied.push({ type: op.type, action: 'create' })
continue
}
if (op.kind === 'update') {
await svc.updateMessage(op.id, {
message: op.message,
channelId: op.channelId,
enabled: op.enabled,
})
applied.push({ type: op.type, action: 'update' })
continue
}
applied.push({ type: op.type, action: 'noop' })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add audit logging for create/update operations in apply().

This method performs administrative settings writes but does not emit ServerLogService entries, which leaves an audit trail gap.

As per coding guidelines, packages/shared/src/services/**/*.ts: "Use ServerLogService to record all administrative actions (moderation, settings changes, role updates) for audit trail compliance".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/services/guildAutomation/autoMessagesExecutor.ts` around
lines 115 - 141, The apply method in autoMessagesExecutor.ts performs
create/update operations via svc.createMessage and svc.updateMessage but doesn't
emit ServerLogService audit entries; update apply to call ServerLogService.log
(or the project's ServerLogService API) after successful create and update
operations, including relevant details (guildId from ctx, operation type,
message id or created resource id, channelId, and the diff payload), ensuring
you log both the create branch (after svc.createMessage resolves) and the update
branch (after svc.updateMessage resolves) and only record the audit entry on
success; reference the apply function, svc.createMessage, svc.updateMessage, and
use the project's ServerLogService methods to record the actions.

@LucasSantana-Dev
LucasSantana-Dev merged commit ce6cda6 into release/v2.12.0 May 20, 2026
30 of 32 checks passed
@LucasSantana-Dev
LucasSantana-Dev deleted the chore/skills-setup-and-automation-pilot branch May 20, 2026 00:34
@sonarqubecloud

Copy link
Copy Markdown

LucasSantana-Dev added a commit that referenced this pull request May 20, 2026
…pec (#902)

## Summary

The `autoMessagesExecutor.spec.ts` landed in #901 relied on ambient jest
types. It passed in isolated `npx jest` runs (looser typing path) but
fails under `npm run test:ci --workspace=packages/shared` (the workspace
invocation, which is `jest --ci --silent` and enforces the shared
package's strict tsconfig — which does not include `@types/jest` in
`types`).

## What changed

- Adds `import { describe, expect, it, jest } from '@jest/globals'` —
matches the shared convention used by `PremiumService.spec.ts`,
`LastFmLinkService/index.spec.ts`, and
`SpotifyLinkService/index.spec.ts`.
- Updates mock typing from the legacy `jest.fn<R, [Args]>()` two-arg
form to `@jest/globals`'s required single-type-arg `jest.fn<Fn>()` form.

## Verification

- `npx jest --config packages/shared/jest.config.cjs --ci --silent
packages/shared/src/services/guildAutomation/autoMessagesExecutor.spec.ts`
— 4/4 passing
- `npm run test:ci --workspace=packages/shared` — failing test-suite
count drops from 3 to 2 (this spec is no longer one of them). The
remaining 2 failures (`FeatureToggleService.spec.ts`,
`__tests__/utils/spotify/artistApi.test.ts`) are pre-existing tech debt
last touched in #762/#614/#648 respectively, out of scope for this fix.

## Why this slipped through #901

PR #901 was merged on `UNSTABLE` while `Quality Gates` (the workspace
test:ci runner) was still in flight. The check that would have caught
this never reported before merge. Going forward, prefer waiting for
`Quality Gates` on PRs touching `packages/shared`.

## Test plan

- [x] `npm run test:ci --workspace=packages/shared` locally —
autoMessagesExecutor spec green
- [ ] Quality Gates check on this PR (the one we should have waited for)

Refs: #901

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Tests**
* Improved test infrastructure with enhanced type safety for mock
functions.

---

**Note:** This release contains internal test improvements with no
changes to user-facing functionality.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/LucasSantana-Dev/Lucky/pull/902?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
LucasSantana-Dev added a commit that referenced this pull request May 21, 2026
Baseline coverage (per `npm run test:ci --workspace=packages/shared -- --coverage`):
- Statements: 21.43%
- Branches: 18.42%
- Functions: 17.24%
- Lines: 20.51%

Set thresholds to baseline minus 2% (no-regression floor):
- Statements: 19%
- Branches: 16%
- Functions: 15%
- Lines: 18%

This establishes parity with packages/backend (70%) and packages/bot (65%) coverage gates. Plugs the gap documented in #909 where missing shared coverage threshold allowed PR #901 to merge with TS-only errors caught post-merge by Quality Gates.

Note: CI's quality.yml workflow (delegated from org reusable) should include shared-workspace tests. Verify in follow-up to ensure the new threshold is enforced on PR submissions.
LucasSantana-Dev added a commit that referenced this pull request May 21, 2026
## Summary

Establishes a coverage-regression floor for `packages/shared` —
currently the only workspace without `coverageThreshold`.
Baseline-minus-2% strategy prevents silent regressions while
accommodating pre-existing low coverage.

## Baseline & Thresholds

Coverage captured from `npm run test:ci --workspace=packages/shared --
--coverage`:

| Metric | Baseline | Threshold | Floor |
|--------|----------|-----------|-------|
| Statements | 21.43% | 19% | no-regression |
| Branches | 18.42% | 16% | no-regression |
| Functions | 17.24% | 15% | no-regression |
| Lines | 20.51% | 18% | no-regression |

**Rationale:** Each threshold = `floor(baseline) - 2%`. Not aspirational
85%; matches project policy (backend 70%, bot 65%). Documented in memory
note `feedback_wait_for_quality_gates_2026-05-19`: PR #901 merged
despite shared-only TS errors; Quality Gates caught them post-merge →
#902 follow-up fix.

## Acceptance Criteria

- [x] `packages/shared/jest.config.cjs` has `coverageThreshold.global`
with all four metrics
- [x] Each threshold = current actual coverage minus 2% (no-regression
floor)
- [x] Coverage gate passes at new thresholds (test suite has
pre-existing failures on `release/v2.12.0` unrelated to coverage)
- [ ] **Follow-up:** Verify CI's Quality Gates workflow includes
shared-workspace test invocation

## Notes

- Single-file change (jest.config.cjs only)
- Pre-existing TS errors in `FeatureToggleService.spec.ts` and
`artistApi.test.ts` on base branch prevent full test suite pass, but
coverage calculations are unaffected
- CI integration: shared workspace is built & verified in `ci.yml` but
not yet tested. The delegated org-wide `quality.yml` workflow should
include test:ci for shared; recommend audit in separate issue to ensure
threshold enforcement gates PRs

Closes #909
LucasSantana-Dev added a commit that referenced this pull request May 21, 2026
## Summary

Cut v2.13.0 of Lucky. Bumps root + 4 workspaces from `2.11.0` → `2.13.0`
(skipping the archived `2.12.0`) and promotes the CHANGELOG
`[Unreleased]` block to `[2.13.0] - 2026-05-21`.

## Headline changes since v2.11.0

**Added**
- Guild Automation Module Executor seam + AutoMessages pilot (#901)
- Sentry React SDK + Router v7 tracing/replay on frontend (#876)
- Prometheus `/metrics` on backend (#875) + bot (#873)
- Guild join/leave history tracking (#872)
- Trivy image-scan on docker-publish, Phase A audit-only (#883)
- Self-hosted developer-tooling register on landing page (#868)

**Changed**
- Backend migrated to Zod 4 API (#919) — unblocked the CVE patch + ended
the lockfile fragility loop
- 3 bot circular-deps clusters broken (#885, #886, #888)

**Fixed**
- brace-expansion DoS + ws uninit-memory CVEs patched (#921)
- nginx-alpine CVEs (#881)
- CI postinstall rate limit + madge actionlint (#878, #905)

Full list in CHANGELOG.md.

## Next steps (after this PR merges)

1. Open `release/v2.13.0 → main` PR with merge-commit method
2. Tag `v2.13.0` on the merge commit
3. Cut next `release` (homelab-style bare branch) — Lucky's bare-release
migration is still pending the user removing protection on
`release/v2.11.0`
@LucasSantana-Dev LucasSantana-Dev mentioned this pull request May 21, 2026
3 tasks
LucasSantana-Dev added a commit that referenced this pull request May 21, 2026
Four ADRs from the 2026-05-20 Guild Automation refactor that were created
locally but never staged (referenced in PR #901 and #919 commit messages,
but the files themselves slipped past the index):
  - executor-composition (#901's composition-roots pattern)
  - executor-partial-failure (best-effort + per-op try/catch)
  - drift-persistence (orchestrator updates GuildAutomationDrift)
  - manifest-absence-semantics (allowProtected handling)

Three ADRs from today's 2026-05-21 work:
  - backend-zod-3-to-4-migration (rationale for #919, includes the locked
    8-decision design from the /research-and-decide session)
  - discord-write-adapter-port (design ADR for the next Module Executor
    seam; strengthens executor-composition)
  - replace-plan-limited-review-tools (rationale for the org CI tools
    standard adopted in LucasSantana-Dev/.github)

No code changes — pure documentation. Indexes into RAG (claude-mem) so
future sessions can recall the decisions.
LucasSantana-Dev added a commit that referenced this pull request May 21, 2026
## Release v2.13.0

Promotes \`release/v2.13.0\` to \`main\` for the v2.13.0 cut.

**$AHEAD commits across all merged PRs since v2.11.0 ship.**

(Skipping v2.12.0 — the branch existed but its work was rolled forward
into v2.13.0 alongside this session's Zod migration + CVE patches +
standards adoption.)

## Headline changes

**Added** — Guild Automation Module Executor pilot (#901), Sentry
frontend (#876), Prometheus metrics on bot+backend (#873, #875), guild
membership history (#872), Trivy image-scan Phase A (#883), landing
redesign (#868).

**Changed** — Backend migrated to Zod 4 API (#919), 3 bot circular-deps
clusters broken (#885/#886/#888).

**Fixed** — brace-expansion + ws moderate CVEs (#921), nginx-alpine CVEs
(#881), CI postinstall rate limit (#878), madge actionlint (#905).

**Internal** — shared coverageThreshold gate (#909/#914),
Feature-removal sweep checklist + dangerfile guard (#908/#913),
monitoring network, AI-doc policy, 4 new ADRs.

Full list in [CHANGELOG.md](./CHANGELOG.md).

## Merge method

This PR should land via **merge commit** (NOT squash) to preserve the
individual PR SHAs in main's history. After merge:

1. Tag \`v2.13.0\` on the merge commit
2. Create GitHub release with notes from CHANGELOG.md
3. Fast-forward \`release/v2.13.0\` to match the new main HEAD

## Test plan

- [ ] All 30 checks green except infra (snyk plan cap)
- [ ] Verify \`gh pr view 922 --json mergeCommit\` shows the chore-bump
commit on release tip
- [ ] After merge: confirm \`origin/main\` contains the full $AHEAD
commits

This branch was successfully deployed

1 active deployment
Preview — 7e07f2f0 Deployed May 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant