diff --git a/.env.example b/.env.example index 3c80dcd..c890efc 100644 --- a/.env.example +++ b/.env.example @@ -163,6 +163,15 @@ GITEA_REPO_NAME=stack # Configure in Gitea: Repository Settings → Webhooks → Add Webhook GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET +# ====================== +# Discord Bridge (Optional) +# ====================== +# Discord bot integration for chat-based control +# Get bot token from: https://discord.com/developers/applications +# DISCORD_BOT_TOKEN=your-discord-bot-token-here +# DISCORD_GUILD_ID=your-discord-server-id +# DISCORD_CONTROL_CHANNEL_ID=channel-id-for-commands + # ====================== # Logging & Debugging # ====================== diff --git a/apps/api/package.json b/apps/api/package.json index 7a2dc7c..bd3f2fa 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -49,6 +49,7 @@ "bullmq": "^5.67.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", + "discord.js": "^14.25.1", "gray-matter": "^4.0.3", "highlight.js": "^11.11.1", "ioredis": "^5.9.2", diff --git a/apps/api/src/bridge/bridge.module.spec.ts b/apps/api/src/bridge/bridge.module.spec.ts new file mode 100644 index 0000000..4ae1ba9 --- /dev/null +++ b/apps/api/src/bridge/bridge.module.spec.ts @@ -0,0 +1,96 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { BridgeModule } from "./bridge.module"; +import { DiscordService } from "./discord/discord.service"; +import { StitcherService } from "../stitcher/stitcher.service"; +import { PrismaService } from "../prisma/prisma.service"; +import { BullMqService } from "../bullmq/bullmq.service"; +import { describe, it, expect, beforeEach, vi } from "vitest"; + +// Mock discord.js +const mockReadyCallbacks: Array<() => void> = []; +const mockClient = { + login: vi.fn().mockImplementation(async () => { + mockReadyCallbacks.forEach((cb) => cb()); + return Promise.resolve(); + }), + destroy: vi.fn().mockResolvedValue(undefined), + on: vi.fn(), + once: vi.fn().mockImplementation((event: string, callback: () => void) => { + if (event === "ready") { + mockReadyCallbacks.push(callback); + } + }), + user: { tag: "TestBot#1234" }, + channels: { + fetch: vi.fn(), + }, + guilds: { + fetch: vi.fn(), + }, +}; + +vi.mock("discord.js", () => { + return { + Client: class MockClient { + login = mockClient.login; + destroy = mockClient.destroy; + on = mockClient.on; + once = mockClient.once; + user = mockClient.user; + channels = mockClient.channels; + guilds = mockClient.guilds; + }, + Events: { + ClientReady: "ready", + MessageCreate: "messageCreate", + Error: "error", + }, + GatewayIntentBits: { + Guilds: 1 << 0, + GuildMessages: 1 << 9, + MessageContent: 1 << 15, + }, + }; +}); + +describe("BridgeModule", () => { + let module: TestingModule; + + beforeEach(async () => { + // Set environment variables + process.env.DISCORD_BOT_TOKEN = "test-token"; + process.env.DISCORD_GUILD_ID = "test-guild-id"; + process.env.DISCORD_CONTROL_CHANNEL_ID = "test-channel-id"; + + // Clear ready callbacks + mockReadyCallbacks.length = 0; + + module = await Test.createTestingModule({ + imports: [BridgeModule], + }) + .overrideProvider(PrismaService) + .useValue({}) + .overrideProvider(BullMqService) + .useValue({}) + .compile(); + + // Clear all mocks + vi.clearAllMocks(); + }); + + it("should be defined", () => { + expect(module).toBeDefined(); + }); + + it("should provide DiscordService", () => { + const discordService = module.get(DiscordService); + expect(discordService).toBeDefined(); + expect(discordService).toBeInstanceOf(DiscordService); + }); + + it("should provide StitcherService", () => { + const stitcherService = module.get(StitcherService); + expect(stitcherService).toBeDefined(); + expect(stitcherService).toBeInstanceOf(StitcherService); + }); +}); diff --git a/apps/api/src/bridge/bridge.module.ts b/apps/api/src/bridge/bridge.module.ts new file mode 100644 index 0000000..af359c3 --- /dev/null +++ b/apps/api/src/bridge/bridge.module.ts @@ -0,0 +1,16 @@ +import { Module } from "@nestjs/common"; +import { DiscordService } from "./discord/discord.service"; +import { StitcherModule } from "../stitcher/stitcher.module"; + +/** + * Bridge Module - Chat platform integrations + * + * Provides integration with chat platforms (Discord, Slack, Matrix, etc.) + * for controlling Mosaic Stack via chat commands. + */ +@Module({ + imports: [StitcherModule], + providers: [DiscordService], + exports: [DiscordService], +}) +export class BridgeModule {} diff --git a/apps/api/src/bridge/discord/discord.service.spec.ts b/apps/api/src/bridge/discord/discord.service.spec.ts new file mode 100644 index 0000000..d532fc8 --- /dev/null +++ b/apps/api/src/bridge/discord/discord.service.spec.ts @@ -0,0 +1,461 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { DiscordService } from "./discord.service"; +import { StitcherService } from "../../stitcher/stitcher.service"; +import { Client, Events, GatewayIntentBits, Message } from "discord.js"; +import { vi, describe, it, expect, beforeEach } from "vitest"; +import type { ChatMessage, ChatCommand } from "../interfaces"; + +// Mock discord.js Client +const mockReadyCallbacks: Array<() => void> = []; +const mockClient = { + login: vi.fn().mockImplementation(async () => { + // Trigger ready callback when login is called + mockReadyCallbacks.forEach((cb) => cb()); + return Promise.resolve(); + }), + destroy: vi.fn().mockResolvedValue(undefined), + on: vi.fn(), + once: vi.fn().mockImplementation((event: string, callback: () => void) => { + if (event === "ready") { + mockReadyCallbacks.push(callback); + } + }), + user: { tag: "TestBot#1234" }, + channels: { + fetch: vi.fn(), + }, + guilds: { + fetch: vi.fn(), + }, +}; + +vi.mock("discord.js", () => { + return { + Client: class MockClient { + login = mockClient.login; + destroy = mockClient.destroy; + on = mockClient.on; + once = mockClient.once; + user = mockClient.user; + channels = mockClient.channels; + guilds = mockClient.guilds; + }, + Events: { + ClientReady: "ready", + MessageCreate: "messageCreate", + Error: "error", + }, + GatewayIntentBits: { + Guilds: 1 << 0, + GuildMessages: 1 << 9, + MessageContent: 1 << 15, + }, + }; +}); + +describe("DiscordService", () => { + let service: DiscordService; + let stitcherService: StitcherService; + + const mockStitcherService = { + dispatchJob: vi.fn().mockResolvedValue({ + jobId: "test-job-id", + queueName: "main", + status: "PENDING", + }), + trackJobEvent: vi.fn().mockResolvedValue(undefined), + }; + + beforeEach(async () => { + // Set environment variables for testing + process.env.DISCORD_BOT_TOKEN = "test-token"; + process.env.DISCORD_GUILD_ID = "test-guild-id"; + process.env.DISCORD_CONTROL_CHANNEL_ID = "test-channel-id"; + + // Clear ready callbacks + mockReadyCallbacks.length = 0; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + DiscordService, + { + provide: StitcherService, + useValue: mockStitcherService, + }, + ], + }).compile(); + + service = module.get(DiscordService); + stitcherService = module.get(StitcherService); + + // Clear all mocks + vi.clearAllMocks(); + }); + + describe("Connection Management", () => { + it("should connect to Discord", async () => { + await service.connect(); + + expect(mockClient.login).toHaveBeenCalledWith("test-token"); + }); + + it("should disconnect from Discord", async () => { + await service.connect(); + await service.disconnect(); + + expect(mockClient.destroy).toHaveBeenCalled(); + }); + + it("should check connection status", async () => { + expect(service.isConnected()).toBe(false); + + await service.connect(); + expect(service.isConnected()).toBe(true); + + await service.disconnect(); + expect(service.isConnected()).toBe(false); + }); + }); + + describe("Message Handling", () => { + it("should send a message to a channel", async () => { + const mockChannel = { + send: vi.fn().mockResolvedValue({}), + isTextBased: () => true, + }; + (mockClient.channels.fetch as any).mockResolvedValue(mockChannel); + + await service.connect(); + await service.sendMessage("test-channel-id", "Hello, Discord!"); + + expect(mockClient.channels.fetch).toHaveBeenCalledWith("test-channel-id"); + expect(mockChannel.send).toHaveBeenCalledWith("Hello, Discord!"); + }); + + it("should throw error if channel not found", async () => { + (mockClient.channels.fetch as any).mockResolvedValue(null); + + await service.connect(); + + await expect(service.sendMessage("invalid-channel", "Test")).rejects.toThrow( + "Channel not found" + ); + }); + }); + + describe("Thread Management", () => { + it("should create a thread for job updates", async () => { + const mockChannel = { + isTextBased: () => true, + threads: { + create: vi.fn().mockResolvedValue({ + id: "thread-123", + send: vi.fn(), + }), + }, + }; + (mockClient.channels.fetch as any).mockResolvedValue(mockChannel); + + await service.connect(); + const threadId = await service.createThread({ + channelId: "test-channel-id", + name: "Job #42", + message: "Starting job...", + }); + + expect(threadId).toBe("thread-123"); + expect(mockChannel.threads.create).toHaveBeenCalledWith({ + name: "Job #42", + reason: "Job updates thread", + }); + }); + + it("should send a message to a thread", async () => { + const mockThread = { + send: vi.fn().mockResolvedValue({}), + isThread: () => true, + }; + (mockClient.channels.fetch as any).mockResolvedValue(mockThread); + + await service.connect(); + await service.sendThreadMessage({ + threadId: "thread-123", + content: "Step completed", + }); + + expect(mockThread.send).toHaveBeenCalledWith("Step completed"); + }); + }); + + describe("Command Parsing", () => { + it("should parse @mosaic fix command", () => { + const message: ChatMessage = { + id: "msg-1", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic fix 42", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "fix", + args: ["42"], + message, + }); + }); + + it("should parse @mosaic status command", () => { + const message: ChatMessage = { + id: "msg-2", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic status job-123", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "status", + args: ["job-123"], + message, + }); + }); + + it("should parse @mosaic cancel command", () => { + const message: ChatMessage = { + id: "msg-3", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic cancel job-456", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "cancel", + args: ["job-456"], + message, + }); + }); + + it("should parse @mosaic verbose command", () => { + const message: ChatMessage = { + id: "msg-4", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic verbose job-789", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "verbose", + args: ["job-789"], + message, + }); + }); + + it("should parse @mosaic quiet command", () => { + const message: ChatMessage = { + id: "msg-5", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic quiet", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "quiet", + args: [], + message, + }); + }); + + it("should parse @mosaic help command", () => { + const message: ChatMessage = { + id: "msg-6", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic help", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "help", + args: [], + message, + }); + }); + + it("should return null for non-command messages", () => { + const message: ChatMessage = { + id: "msg-7", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "Just a regular message", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toBeNull(); + }); + + it("should return null for messages without @mosaic mention", () => { + const message: ChatMessage = { + id: "msg-8", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "fix 42", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toBeNull(); + }); + + it("should handle commands with multiple arguments", () => { + const message: ChatMessage = { + id: "msg-9", + channelId: "channel-1", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic fix 42 high-priority", + timestamp: new Date(), + }; + + const command = service.parseCommand(message); + + expect(command).toEqual({ + command: "fix", + args: ["42", "high-priority"], + message, + }); + }); + }); + + describe("Command Execution", () => { + it("should forward fix command to stitcher", async () => { + const message: ChatMessage = { + id: "msg-1", + channelId: "test-channel-id", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic fix 42", + timestamp: new Date(), + }; + + const mockThread = { + id: "thread-123", + send: vi.fn(), + isThread: () => true, + }; + + const mockChannel = { + isTextBased: () => true, + threads: { + create: vi.fn().mockResolvedValue(mockThread), + }, + }; + + // Mock channels.fetch to return channel first, then thread + (mockClient.channels.fetch as any) + .mockResolvedValueOnce(mockChannel) + .mockResolvedValueOnce(mockThread); + + await service.connect(); + await service.handleCommand({ + command: "fix", + args: ["42"], + message, + }); + + expect(stitcherService.dispatchJob).toHaveBeenCalledWith({ + workspaceId: "default-workspace", + type: "code-task", + priority: 10, + metadata: { + issueNumber: 42, + command: "fix", + channelId: "test-channel-id", + threadId: "thread-123", + authorId: "user-1", + authorName: "TestUser", + }, + }); + }); + + it("should respond with help message", async () => { + const message: ChatMessage = { + id: "msg-1", + channelId: "test-channel-id", + authorId: "user-1", + authorName: "TestUser", + content: "@mosaic help", + timestamp: new Date(), + }; + + const mockChannel = { + send: vi.fn(), + isTextBased: () => true, + }; + (mockClient.channels.fetch as any).mockResolvedValue(mockChannel); + + await service.connect(); + await service.handleCommand({ + command: "help", + args: [], + message, + }); + + expect(mockChannel.send).toHaveBeenCalledWith(expect.stringContaining("Available commands:")); + }); + }); + + describe("Configuration", () => { + it("should throw error if DISCORD_BOT_TOKEN is not set", async () => { + delete process.env.DISCORD_BOT_TOKEN; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + DiscordService, + { + provide: StitcherService, + useValue: mockStitcherService, + }, + ], + }).compile(); + + const newService = module.get(DiscordService); + + await expect(newService.connect()).rejects.toThrow("DISCORD_BOT_TOKEN is required"); + + // Restore for other tests + process.env.DISCORD_BOT_TOKEN = "test-token"; + }); + + it("should use default workspace if not configured", async () => { + // This is tested through the handleCommand test above + // which verifies workspaceId: 'default-workspace' + expect(true).toBe(true); + }); + }); +}); diff --git a/apps/api/src/bridge/discord/discord.service.ts b/apps/api/src/bridge/discord/discord.service.ts new file mode 100644 index 0000000..f52e738 --- /dev/null +++ b/apps/api/src/bridge/discord/discord.service.ts @@ -0,0 +1,387 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { Client, Events, GatewayIntentBits, TextChannel, ThreadChannel } from "discord.js"; +import { StitcherService } from "../../stitcher/stitcher.service"; +import type { + IChatProvider, + ChatMessage, + ChatCommand, + ThreadCreateOptions, + ThreadMessageOptions, +} from "../interfaces"; + +/** + * Discord Service - Discord chat platform integration + * + * Responsibilities: + * - Connect to Discord via bot token + * - Listen for commands in designated channels + * - Forward commands to stitcher + * - Receive status updates from herald + * - Post updates to threads + */ +@Injectable() +export class DiscordService implements IChatProvider { + private readonly logger = new Logger(DiscordService.name); + private client: Client; + private connected = false; + private readonly botToken: string; + private readonly controlChannelId: string; + + constructor(private readonly stitcherService: StitcherService) { + this.botToken = process.env.DISCORD_BOT_TOKEN ?? ""; + this.controlChannelId = process.env.DISCORD_CONTROL_CHANNEL_ID ?? ""; + + // Initialize Discord client with required intents + this.client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], + }); + + this.setupEventHandlers(); + } + + /** + * Setup event handlers for Discord client + */ + private setupEventHandlers(): void { + this.client.once(Events.ClientReady, () => { + this.connected = true; + const userTag = this.client.user?.tag ?? "Unknown"; + this.logger.log(`Discord bot connected as ${userTag}`); + }); + + this.client.on(Events.MessageCreate, (message) => { + // Ignore bot messages + if (message.author.bot) return; + + // Check if message is in control channel + if (message.channelId !== this.controlChannelId) return; + + // Parse message into ChatMessage format + const chatMessage: ChatMessage = { + id: message.id, + channelId: message.channelId, + authorId: message.author.id, + authorName: message.author.username, + content: message.content, + timestamp: message.createdAt, + ...(message.channel.isThread() && { threadId: message.channelId }), + }; + + // Parse command + const command = this.parseCommand(chatMessage); + if (command) { + void this.handleCommand(command); + } + }); + + this.client.on(Events.Error, (error) => { + this.logger.error("Discord client error:", error); + }); + } + + /** + * Connect to Discord + */ + async connect(): Promise { + if (!this.botToken) { + throw new Error("DISCORD_BOT_TOKEN is required"); + } + + this.logger.log("Connecting to Discord..."); + await this.client.login(this.botToken); + } + + /** + * Disconnect from Discord + */ + async disconnect(): Promise { + this.logger.log("Disconnecting from Discord..."); + this.connected = false; + await this.client.destroy(); + } + + /** + * Check if the provider is connected + */ + isConnected(): boolean { + return this.connected; + } + + /** + * Send a message to a channel or thread + */ + async sendMessage(channelId: string, content: string): Promise { + const channel = await this.client.channels.fetch(channelId); + + if (!channel) { + throw new Error("Channel not found"); + } + + if (channel.isTextBased()) { + await (channel as TextChannel).send(content); + } else { + throw new Error("Channel is not text-based"); + } + } + + /** + * Create a thread for job updates + */ + async createThread(options: ThreadCreateOptions): Promise { + const { channelId, name, message } = options; + + const channel = await this.client.channels.fetch(channelId); + + if (!channel) { + throw new Error("Channel not found"); + } + + if (!channel.isTextBased()) { + throw new Error("Channel does not support threads"); + } + + const thread = await (channel as TextChannel).threads.create({ + name, + reason: "Job updates thread", + }); + + // Send initial message to thread + await thread.send(message); + + return thread.id; + } + + /** + * Send a message to a thread + */ + async sendThreadMessage(options: ThreadMessageOptions): Promise { + const { threadId, content } = options; + + const thread = await this.client.channels.fetch(threadId); + + if (!thread) { + throw new Error("Thread not found"); + } + + if (thread.isThread()) { + await (thread as ThreadChannel).send(content); + } else { + throw new Error("Channel is not a thread"); + } + } + + /** + * Parse a command from a message + */ + parseCommand(message: ChatMessage): ChatCommand | null { + const { content } = message; + + // Check if message mentions @mosaic + if (!content.toLowerCase().includes("@mosaic")) { + return null; + } + + // Extract command and arguments + const parts = content.trim().split(/\s+/); + const mosaicIndex = parts.findIndex((part) => part.toLowerCase().includes("@mosaic")); + + if (mosaicIndex === -1 || mosaicIndex === parts.length - 1) { + return null; + } + + const commandPart = parts[mosaicIndex + 1]; + if (!commandPart) { + return null; + } + + const command = commandPart.toLowerCase(); + const args = parts.slice(mosaicIndex + 2); + + // Valid commands + const validCommands = ["fix", "status", "cancel", "verbose", "quiet", "help"]; + + if (!validCommands.includes(command)) { + return null; + } + + return { + command, + args, + message, + }; + } + + /** + * Handle a parsed command + */ + async handleCommand(command: ChatCommand): Promise { + const { command: cmd, args, message } = command; + + this.logger.log( + `Handling command: ${cmd} with args: ${args.join(", ")} from ${message.authorName}` + ); + + switch (cmd) { + case "fix": + await this.handleFixCommand(args, message); + break; + case "status": + await this.handleStatusCommand(args, message); + break; + case "cancel": + await this.handleCancelCommand(args, message); + break; + case "verbose": + await this.handleVerboseCommand(args, message); + break; + case "quiet": + await this.handleQuietCommand(args, message); + break; + case "help": + await this.handleHelpCommand(args, message); + break; + default: + await this.sendMessage( + message.channelId, + `Unknown command: ${cmd}. Type \`@mosaic help\` for available commands.` + ); + } + } + + /** + * Handle fix command - Start a job for an issue + */ + private async handleFixCommand(args: string[], message: ChatMessage): Promise { + if (args.length === 0 || !args[0]) { + await this.sendMessage(message.channelId, "Usage: `@mosaic fix `"); + return; + } + + const issueNumber = parseInt(args[0], 10); + + if (isNaN(issueNumber)) { + await this.sendMessage( + message.channelId, + "Invalid issue number. Please provide a numeric issue number." + ); + return; + } + + // Create thread for job updates + const threadId = await this.createThread({ + channelId: message.channelId, + name: `Job #${String(issueNumber)}`, + message: `Starting job for issue #${String(issueNumber)}...`, + }); + + // Dispatch job to stitcher + const result = await this.stitcherService.dispatchJob({ + workspaceId: "default-workspace", // TODO: Get from configuration + type: "code-task", + priority: 10, + metadata: { + issueNumber, + command: "fix", + channelId: message.channelId, + threadId: threadId, + authorId: message.authorId, + authorName: message.authorName, + }, + }); + + // Send confirmation to thread + await this.sendThreadMessage({ + threadId, + content: `Job created: ${result.jobId}\nStatus: ${result.status}\nQueue: ${result.queueName}`, + }); + } + + /** + * Handle status command - Get job status + */ + private async handleStatusCommand(args: string[], message: ChatMessage): Promise { + if (args.length === 0 || !args[0]) { + await this.sendMessage(message.channelId, "Usage: `@mosaic status `"); + return; + } + + const jobId = args[0]; + + // TODO: Implement job status retrieval from stitcher + await this.sendMessage( + message.channelId, + `Status command not yet implemented for job: ${jobId}` + ); + } + + /** + * Handle cancel command - Cancel a running job + */ + private async handleCancelCommand(args: string[], message: ChatMessage): Promise { + if (args.length === 0 || !args[0]) { + await this.sendMessage(message.channelId, "Usage: `@mosaic cancel `"); + return; + } + + const jobId = args[0]; + + // TODO: Implement job cancellation in stitcher + await this.sendMessage( + message.channelId, + `Cancel command not yet implemented for job: ${jobId}` + ); + } + + /** + * Handle verbose command - Stream full logs to thread + */ + private async handleVerboseCommand(args: string[], message: ChatMessage): Promise { + if (args.length === 0 || !args[0]) { + await this.sendMessage(message.channelId, "Usage: `@mosaic verbose `"); + return; + } + + const jobId = args[0]; + + // TODO: Implement verbose logging + await this.sendMessage(message.channelId, `Verbose mode not yet implemented for job: ${jobId}`); + } + + /** + * Handle quiet command - Reduce notifications + */ + private async handleQuietCommand(_args: string[], message: ChatMessage): Promise { + // TODO: Implement quiet mode + await this.sendMessage( + message.channelId, + "Quiet mode not yet implemented. Currently showing milestone updates only." + ); + } + + /** + * Handle help command - Show available commands + */ + private async handleHelpCommand(_args: string[], message: ChatMessage): Promise { + const helpMessage = ` +**Available commands:** + +\`@mosaic fix \` - Start job for issue +\`@mosaic status \` - Get job status +\`@mosaic cancel \` - Cancel running job +\`@mosaic verbose \` - Stream full logs to thread +\`@mosaic quiet\` - Reduce notifications +\`@mosaic help\` - Show this help message + +**Noise Management:** +• Main channel: Low verbosity (milestones only) +• Job threads: Medium verbosity (step completions) +• DMs: Configurable per user + `.trim(); + + await this.sendMessage(message.channelId, helpMessage); + } +} diff --git a/apps/api/src/bridge/index.ts b/apps/api/src/bridge/index.ts new file mode 100644 index 0000000..c9aed0f --- /dev/null +++ b/apps/api/src/bridge/index.ts @@ -0,0 +1,3 @@ +export * from "./bridge.module"; +export * from "./discord/discord.service"; +export * from "./interfaces"; diff --git a/apps/api/src/bridge/interfaces/chat-provider.interface.ts b/apps/api/src/bridge/interfaces/chat-provider.interface.ts new file mode 100644 index 0000000..382ca82 --- /dev/null +++ b/apps/api/src/bridge/interfaces/chat-provider.interface.ts @@ -0,0 +1,79 @@ +/** + * Chat Provider Interface + * + * Defines the contract for chat platform integrations (Discord, Slack, Matrix, etc.) + */ + +export interface ChatMessage { + id: string; + channelId: string; + authorId: string; + authorName: string; + content: string; + timestamp: Date; + threadId?: string; +} + +export interface ChatCommand { + command: string; + args: string[]; + message: ChatMessage; +} + +export interface ThreadCreateOptions { + channelId: string; + name: string; + message: string; +} + +export interface ThreadMessageOptions { + threadId: string; + content: string; +} + +export interface VerbosityLevel { + level: "low" | "medium" | "high"; + description: string; +} + +/** + * Chat Provider Interface + * + * All chat platform integrations must implement this interface + */ +export interface IChatProvider { + /** + * Connect to the chat platform + */ + connect(): Promise; + + /** + * Disconnect from the chat platform + */ + disconnect(): Promise; + + /** + * Check if the provider is connected + */ + isConnected(): boolean; + + /** + * Send a message to a channel or thread + */ + sendMessage(channelId: string, content: string): Promise; + + /** + * Create a thread for job updates + */ + createThread(options: ThreadCreateOptions): Promise; + + /** + * Send a message to a thread + */ + sendThreadMessage(options: ThreadMessageOptions): Promise; + + /** + * Parse a command from a message + */ + parseCommand(message: ChatMessage): ChatCommand | null; +} diff --git a/apps/api/src/bridge/interfaces/index.ts b/apps/api/src/bridge/interfaces/index.ts new file mode 100644 index 0000000..194db50 --- /dev/null +++ b/apps/api/src/bridge/interfaces/index.ts @@ -0,0 +1 @@ +export * from "./chat-provider.interface"; diff --git a/docs/reports/m4.2-token-tracking.md b/docs/reports/m4.2-token-tracking.md index 57e4ab3..d498a01 100644 --- a/docs/reports/m4.2-token-tracking.md +++ b/docs/reports/m4.2-token-tracking.md @@ -87,24 +87,28 @@ ### Issue 168 - [INFRA-006] Job steps tracking - **Estimate:** 45,000 tokens (sonnet) -- **Actual:** _pending_ -- **Variance:** _pending_ -- **Agent ID:** _pending_ -- **Status:** pending +- **Actual:** ~66,000 tokens (sonnet) +- **Variance:** +47% (over estimate) +- **Agent ID:** afdbbe9 +- **Status:** ✅ completed +- **Commit:** efe624e - **Dependencies:** #164, #167 -- **Notes:** Granular step tracking within jobs (SETUP, EXECUTION, VALIDATION, CLEANUP) +- **Quality Gates:** ✅ All passed (16 tests, 100% coverage, typecheck, lint, build) +- **Notes:** Implemented step CRUD, status tracking (PENDING→RUNNING→COMPLETED/FAILED), token usage per step, duration calculation. Endpoints: GET /runner-jobs/:jobId/steps, GET /runner-jobs/:jobId/steps/:stepId --- ### Issue 169 - [INFRA-007] Job events and audit logging - **Estimate:** 55,000 tokens (sonnet) -- **Actual:** _pending_ -- **Variance:** _pending_ -- **Agent ID:** _pending_ -- **Status:** pending +- **Actual:** ~66,700 tokens (sonnet) +- **Variance:** +21% (over estimate) +- **Agent ID:** aa98d29 +- **Status:** ✅ completed +- **Commit:** efe624e (with #168) - **Dependencies:** #164, #167 -- **Notes:** Event sourcing pattern, PostgreSQL + Valkey Streams + Pub/Sub +- **Quality Gates:** ✅ All passed (17 tests, typecheck, lint, build) +- **Notes:** Implemented 17 event types (job, step, AI, gate lifecycles). PostgreSQL persistence with emitEvent() and query methods. GET /runner-jobs/:jobId/events endpoint. --- @@ -147,12 +151,14 @@ ### Issue 173 - [INFRA-011] WebSocket gateway for job events - **Estimate:** 45,000 tokens (sonnet) -- **Actual:** _pending_ -- **Variance:** _pending_ -- **Agent ID:** _pending_ -- **Status:** pending +- **Actual:** ~49,000 tokens (sonnet) +- **Variance:** +9% (over estimate) +- **Agent ID:** af03015 +- **Status:** ✅ completed +- **Commit:** fd78b72 - **Dependencies:** #169 -- **Notes:** Extend existing WebSocket gateway, subscription management +- **Quality Gates:** ✅ All passed (22 tests, typecheck, lint) +- **Notes:** Extended existing WebSocket gateway with 6 event emission methods. Supports workspace-level and job-specific subscriptions. --- @@ -253,9 +259,9 @@ ### Phase 2: Stitcher Service - **Estimated:** 205,000 tokens -- **Actual:** _in_progress_ (~138,000 for #166, #167) -- **Variance:** _pending_ -- **Issues:** #166 (✅), #167 (✅), #168, #169 +- **Actual:** ~270,700 tokens +- **Variance:** +32% (over estimate) +- **Issues:** #166 (✅), #167 (✅), #168 (✅), #169 (✅) ### Phase 3: Chat Integration @@ -339,6 +345,11 @@ _Execution events will be logged here as work progresses._ [2026-02-01 19:32] Issue #167 COMPLETED - Agent aa914a0 - ~76,000 tokens [2026-02-01 19:32] Wave 2 COMPLETE - Total: ~138,000 tokens [2026-02-01 19:32] Wave 3 STARTED - Stitcher events (#168, #169) +[2026-02-01 19:40] Issue #168 COMPLETED - Agent afdbbe9 - ~66,000 tokens +[2026-02-01 19:48] Issue #169 COMPLETED - Agent aa98d29 - ~66,700 tokens +[2026-02-01 19:48] Wave 3 COMPLETE - Phase 2 done - Total: ~132,700 tokens +[2026-02-01 19:48] Wave 4 STARTED - Chat + Real-time (#170, #173 parallel, then #171, #174) +[2026-02-01 19:55] Issue #173 COMPLETED - Agent af03015 - ~49,000 tokens ``` ## Notes diff --git a/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md new file mode 100644 index 0000000..f1878cd --- /dev/null +++ b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:21:52 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md new file mode 100644 index 0000000..616ecc6 --- /dev/null +++ b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:24:40 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md new file mode 100644 index 0000000..24d0dbb --- /dev/null +++ b/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:25:49 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/escalated/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.spec.ts_20260201-2123_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.spec.ts_20260201-2123_1_remediation_needed.md new file mode 100644 index 0000000..aa1d52e --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.spec.ts_20260201-2123_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/bridge.module.spec.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:23:26 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.spec.ts_20260201-2123_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.ts_20260201-2123_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.ts_20260201-2123_1_remediation_needed.md new file mode 100644 index 0000000..0a9c6df --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.ts_20260201-2123_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/bridge.module.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:23:07 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-bridge.module.ts_20260201-2123_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2120_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2120_1_remediation_needed.md new file mode 100644 index 0000000..e0fad4d --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2120_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:20:01 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2120_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_1_remediation_needed.md new file mode 100644 index 0000000..14c326c --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:21:03 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_2_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_2_remediation_needed.md new file mode 100644 index 0000000..70a7d92 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_2_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 2 +**Generated:** 2026-02-01 21:21:13 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_2_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_3_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_3_remediation_needed.md new file mode 100644 index 0000000..4294b19 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_3_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 3 +**Generated:** 2026-02-01 21:21:24 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_3_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_4_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_4_remediation_needed.md new file mode 100644 index 0000000..4b8ab93 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_4_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 4 +**Generated:** 2026-02-01 21:21:32 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_4_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md new file mode 100644 index 0000000..eace54f --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:21:37 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2121_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_1_remediation_needed.md new file mode 100644 index 0000000..a270709 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:22:15 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_2_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_2_remediation_needed.md new file mode 100644 index 0000000..75f58fa --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_2_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 2 +**Generated:** 2026-02-01 21:22:26 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_2_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_3_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_3_remediation_needed.md new file mode 100644 index 0000000..5c19b9c --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_3_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 3 +**Generated:** 2026-02-01 21:22:43 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_3_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_4_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_4_remediation_needed.md new file mode 100644 index 0000000..17d9174 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_4_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 4 +**Generated:** 2026-02-01 21:22:50 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_4_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_5_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_5_remediation_needed.md new file mode 100644 index 0000000..4e2d496 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:22:57 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.spec.ts_20260201-2122_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2120_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2120_1_remediation_needed.md new file mode 100644 index 0000000..1aea747 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2120_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:20:47 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2120_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_1_remediation_needed.md new file mode 100644 index 0000000..72b1733 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:24:00 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_2_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_2_remediation_needed.md new file mode 100644 index 0000000..ad56423 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_2_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 2 +**Generated:** 2026-02-01 21:24:05 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_2_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_3_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_3_remediation_needed.md new file mode 100644 index 0000000..9b7c027 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_3_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 3 +**Generated:** 2026-02-01 21:24:11 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_3_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_4_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_4_remediation_needed.md new file mode 100644 index 0000000..63e5282 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_4_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 4 +**Generated:** 2026-02-01 21:24:17 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_4_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md new file mode 100644 index 0000000..ade53d0 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:24:23 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2124_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_1_remediation_needed.md new file mode 100644 index 0000000..b2fe00b --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:25:17 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_2_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_2_remediation_needed.md new file mode 100644 index 0000000..fc3ba36 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_2_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 2 +**Generated:** 2026-02-01 21:25:24 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_2_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_3_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_3_remediation_needed.md new file mode 100644 index 0000000..2f0e1b2 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_3_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 3 +**Generated:** 2026-02-01 21:25:28 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_3_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_4_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_4_remediation_needed.md new file mode 100644 index 0000000..a3252c2 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_4_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 4 +**Generated:** 2026-02-01 21:25:32 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_4_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md new file mode 100644 index 0000000..72f7364 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/discord/discord.service.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 5 +**Generated:** 2026-02-01 21:25:39 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-discord-discord.service.ts_20260201-2125_5_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-index.ts_20260201-2123_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-index.ts_20260201-2123_1_remediation_needed.md new file mode 100644 index 0000000..8a9ff42 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-index.ts_20260201-2123_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/index.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:23:11 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-index.ts_20260201-2123_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-chat-provider.interface.ts_20260201-2119_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-chat-provider.interface.ts_20260201-2119_1_remediation_needed.md new file mode 100644 index 0000000..2666320 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-chat-provider.interface.ts_20260201-2119_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/interfaces/chat-provider.interface.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:19:20 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-chat-provider.interface.ts_20260201-2119_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-index.ts_20260201-2119_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-index.ts_20260201-2119_1_remediation_needed.md new file mode 100644 index 0000000..0c4c134 --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-index.ts_20260201-2119_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/bridge/interfaces/index.ts +**Tool Used:** Write +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:19:22 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-bridge-interfaces-index.ts_20260201-2119_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.spec.ts_20260201-2119_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.spec.ts_20260201-2119_1_remediation_needed.md new file mode 100644 index 0000000..0a292ed --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.spec.ts_20260201-2119_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/websocket/websocket.gateway.spec.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:19:35 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.spec.ts_20260201-2119_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2119_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2119_1_remediation_needed.md new file mode 100644 index 0000000..fba66fd --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2119_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/websocket/websocket.gateway.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:19:49 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2119_1_remediation_needed.md" +``` diff --git a/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2120_1_remediation_needed.md b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2120_1_remediation_needed.md new file mode 100644 index 0000000..4ed8c9a --- /dev/null +++ b/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2120_1_remediation_needed.md @@ -0,0 +1,20 @@ +# QA Remediation Report + +**File:** /home/jwoltje/src/mosaic-stack/apps/api/src/websocket/websocket.gateway.ts +**Tool Used:** Edit +**Epic:** general +**Iteration:** 1 +**Generated:** 2026-02-01 21:20:09 + +## Status + +Pending QA validation + +## Next Steps + +This report was created by the QA automation hook. +To process this report, run: + +```bash +claude -p "Use Task tool to launch universal-qa-agent for report: /home/jwoltje/src/mosaic-stack/docs/reports/qa-automation/pending/home-jwoltje-src-mosaic-stack-apps-api-src-websocket-websocket.gateway.ts_20260201-2120_1_remediation_needed.md" +``` diff --git a/docs/scratchpads/170-discord-bridge.md b/docs/scratchpads/170-discord-bridge.md new file mode 100644 index 0000000..d7b8447 --- /dev/null +++ b/docs/scratchpads/170-discord-bridge.md @@ -0,0 +1,83 @@ +# Issue #170: Implement mosaic-bridge module for Discord + +## Objective + +Create the mosaic-bridge module to enable Discord integration. This module will: + +- Connect to Discord via bot token +- Listen for commands in designated channels +- Forward commands to stitcher +- Receive status updates from herald +- Post updates to threads with appropriate verbosity + +## Prerequisites + +- Issue #166 (Stitcher module) must be complete - StitcherService available + +## Approach + +1. Create bridge module structure +2. Define chat provider interface for extensibility +3. Implement Discord service using Discord.js +4. Add command parsing (basic implementation) +5. Implement thread management for job updates +6. Add configuration management +7. Follow TDD: Write tests before implementation + +## Commands to Implement + +- `@mosaic fix ` - Start job for issue +- `@mosaic status ` - Get job status +- `@mosaic cancel ` - Cancel running job +- `@mosaic verbose ` - Stream full logs to thread +- `@mosaic quiet` - Reduce notifications +- `@mosaic help` - Show commands + +## Noise Management Strategy + +- **Main channel**: Low verbosity (milestones only) +- **Job threads**: Medium verbosity (step completions) +- **DMs**: Configurable per user + +## Progress + +- [x] Install discord.js dependency +- [x] Create bridge module structure +- [x] Define ChatProvider interface +- [x] Write tests for Discord service (RED phase) +- [x] Implement Discord service (GREEN phase) +- [x] Implement command parsing +- [x] Implement thread management +- [x] Add configuration +- [x] Refactor and optimize (REFACTOR phase) +- [x] Run quality gates (typecheck, lint, build, test) +- [x] Commit changes + +## Results + +- **Tests**: 23/23 passing (20 Discord service + 3 module tests) +- **Typecheck**: PASSED +- **Lint**: PASSED +- **Build**: PASSED +- **Coverage**: High (all critical paths tested) + +## Testing Strategy + +- Unit tests for command parsing +- Unit tests for thread management +- Mock Discord.js client for testing +- Test stitcher integration +- Verify configuration loading + +## Environment Variables + +- `DISCORD_BOT_TOKEN` - Bot authentication token +- `DISCORD_GUILD_ID` - Server/Guild ID +- `DISCORD_CONTROL_CHANNEL_ID` - Channel for commands + +## Notes + +- Keep Discord.js interactions isolated in discord.service.ts +- Use ChatProvider interface to allow future platform additions (Slack, Matrix, etc.) +- Basic command parsing in this issue; detailed parsing comes in #171 +- DO NOT push to remote, just commit locally diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9127000..2b689b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers: class-validator: specifier: ^0.14.3 version: 0.14.3 + discord.js: + specifier: ^14.25.1 + version: 14.25.1 gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -729,6 +732,34 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@discordjs/builders@1.13.1': + resolution: {integrity: sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@1.5.3': + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@2.1.1': + resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} + engines: {node: '>=18'} + + '@discordjs/formatters@0.6.2': + resolution: {integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/rest@2.6.0': + resolution: {integrity: sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==} + engines: {node: '>=18'} + + '@discordjs/util@1.2.0': + resolution: {integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==} + engines: {node: '>=18'} + + '@discordjs/ws@1.2.3': + resolution: {integrity: sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==} + engines: {node: '>=16.11.0'} + '@dnd-kit/accessibility@3.1.1': resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} peerDependencies: @@ -2318,6 +2349,18 @@ packages: cpu: [x64] os: [win32] + '@sapphire/async-queue@1.5.5': + resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@sapphire/shapeshift@4.0.0': + resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} + engines: {node: '>=v16'} + + '@sapphire/snowflake@3.5.3': + resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -2676,6 +2719,9 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.54.0': resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2817,6 +2863,10 @@ packages: '@vitest/utils@4.0.18': resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vladfrangu/async_event_emitter@2.4.7': + resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3691,6 +3741,13 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + discord-api-types@0.38.38: + resolution: {integrity: sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q==} + + discord.js@14.25.1: + resolution: {integrity: sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==} + engines: {node: '>=18'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -4582,6 +4639,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -4629,6 +4689,9 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + magic-bytes.js@1.13.0: + resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} @@ -5729,6 +5792,9 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + tsconfig-paths-webpack-plugin@4.2.0: resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} engines: {node: '>=10.13.0'} @@ -5823,6 +5889,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + engines: {node: '>=18.17'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -6684,6 +6754,55 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} + '@discordjs/builders@1.13.1': + dependencies: + '@discordjs/formatters': 0.6.2 + '@discordjs/util': 1.2.0 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.8.1 + + '@discordjs/collection@1.5.3': {} + + '@discordjs/collection@2.1.1': {} + + '@discordjs/formatters@0.6.2': + dependencies: + discord-api-types: 0.38.38 + + '@discordjs/rest@2.6.0': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@sapphire/snowflake': 3.5.3 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.38 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + + '@discordjs/util@1.2.0': + dependencies: + discord-api-types: 0.38.38 + + '@discordjs/ws@1.2.3': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@types/ws': 8.18.1 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.38 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@dnd-kit/accessibility@3.1.1(react@19.2.4)': dependencies: react: 19.2.4 @@ -8358,6 +8477,15 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.57.0': optional: true + '@sapphire/async-queue@1.5.5': {} + + '@sapphire/shapeshift@4.0.0': + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.23 + + '@sapphire/snowflake@3.5.3': {} + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.1.0': {} @@ -8760,6 +8888,10 @@ snapshots: '@types/validator@13.15.10': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.19.7 + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8991,6 +9123,8 @@ snapshots: '@vitest/pretty-format': 4.0.18 tinyrainbow: 3.0.3 + '@vladfrangu/async_event_emitter@2.4.7': {} + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -9922,6 +10056,27 @@ snapshots: detect-libc@2.1.2: {} + discord-api-types@0.38.38: {} + + discord.js@14.25.1: + dependencies: + '@discordjs/builders': 1.13.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.2 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@discordjs/ws': 1.2.3 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -10798,6 +10953,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash.snakecase@4.1.1: {} + lodash@4.17.21: {} lodash@4.17.23: {} @@ -10839,6 +10996,8 @@ snapshots: lz-string@1.5.0: {} + magic-bytes.js@1.13.0: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -12034,6 +12193,8 @@ snapshots: ts-dedent@2.2.0: {} + ts-mixer@6.0.4: {} + tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 @@ -12127,6 +12288,8 @@ snapshots: undici-types@6.21.0: {} + undici@6.21.3: {} + universalify@2.0.1: {} unpipe@1.0.0: {}