feat(#171): Implement chat command parsing

Add command parsing layer for chat integration (Discord, Mattermost, Slack).

Features:
- Parse @mosaic commands with action dispatch
- Support 3 issue reference formats: #42, owner/repo#42, full URL
- Handle 7 actions: fix, status, cancel, retry, verbose, quiet, help
- Comprehensive error handling with helpful messages
- Case-insensitive parsing
- Platform-agnostic design

Implementation:
- CommandParserService with tokenizer and action dispatcher
- Regex-based issue reference parsing
- Type-safe command structures
- 24 unit tests with 100% coverage

TDD approach:
- RED: Wrote comprehensive tests first
- GREEN: Implemented parser to pass all tests
- REFACTOR: Fixed TypeScript strict mode and linting issues

Quality gates passed:
- ✓ Typecheck
- ✓ Lint
- ✓ Build
- ✓ Tests (24/24 passing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 21:32:53 -06:00
parent 4ac21d1a3a
commit e689a1379c
40 changed files with 1618 additions and 6 deletions

View File

@@ -1,4 +1,5 @@
import { Controller, Get, Post, Body, Param, Query, UseGuards } from "@nestjs/common";
import { Controller, Get, Post, Body, Param, Query, UseGuards, Res } from "@nestjs/common";
import { Response } from "express";
import { RunnerJobsService } from "./runner-jobs.service";
import { CreateJobDto, QueryJobsDto } from "./dto";
import { AuthGuard } from "../auth/guards/auth.guard";
@@ -87,4 +88,33 @@ export class RunnerJobsController {
) {
return this.runnerJobsService.retry(id, workspaceId);
}
/**
* GET /api/runner-jobs/:id/events/stream
* Stream job events via Server-Sent Events (SSE)
* Requires: Any workspace member
*/
@Get(":id/events/stream")
@RequirePermission(Permission.WORKSPACE_ANY)
async streamEvents(
@Param("id") id: string,
@Workspace() workspaceId: string,
@Res() res: Response
): Promise<void> {
// Set SSE headers
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
try {
await this.runnerJobsService.streamEvents(id, workspaceId, res);
} catch (error: unknown) {
// Write error to stream
const errorMessage = error instanceof Error ? error.message : String(error);
res.write(`event: error\n`);
res.write(`data: ${JSON.stringify({ error: errorMessage })}\n\n`);
res.end();
}
}
}