feat(telemetry): emit task lifecycle events (US-802)

TaskService now emits telemetry events for:
- task.created: when a new task is created
- task.status_changed: when task status changes (with previousStatus)
- task.archived: when a task is archived
- task.restored: when a task is restored from archive

All events include taskId, project, and current status.
This commit is contained in:
Brad Groux 2026-01-26 12:15:02 -06:00
parent 62bdd78cc7
commit e55628def8

View file

@ -2,7 +2,8 @@ import fs from 'fs/promises';
import path from 'path';
import matter from 'gray-matter';
import { nanoid } from 'nanoid';
import type { Task, CreateTaskInput, UpdateTaskInput, ReviewComment, Subtask } from '@veritas-kanban/shared';
import type { Task, CreateTaskInput, UpdateTaskInput, ReviewComment, Subtask, TaskTelemetryEvent } from '@veritas-kanban/shared';
import { getTelemetryService, type TelemetryService } from './telemetry-service.js';
// Simple slug function to avoid CJS/ESM issues with slugify
function makeSlug(text: string): string {
@ -21,15 +22,18 @@ const DEFAULT_ARCHIVE_DIR = path.join(DEFAULT_PROJECT_ROOT, 'tasks', 'archive');
export interface TaskServiceOptions {
tasksDir?: string;
archiveDir?: string;
telemetryService?: TelemetryService;
}
export class TaskService {
private tasksDir: string;
private archiveDir: string;
private telemetry: TelemetryService;
constructor(options: TaskServiceOptions = {}) {
this.tasksDir = options.tasksDir || DEFAULT_TASKS_DIR;
this.archiveDir = options.archiveDir || DEFAULT_ARCHIVE_DIR;
this.telemetry = options.telemetryService || getTelemetryService();
this.ensureDirectories();
}
@ -152,6 +156,14 @@ export class TaskService {
await fs.writeFile(filepath, content, 'utf-8');
// Emit telemetry event
await this.telemetry.emit<TaskTelemetryEvent>({
type: 'task.created',
taskId: task.id,
project: task.project,
status: task.status,
});
return task;
}
@ -159,6 +171,10 @@ export class TaskService {
const task = await this.getTask(id);
if (!task) return null;
// Track if status changed for telemetry
const previousStatus = task.status;
const statusChanged = input.status !== undefined && input.status !== previousStatus;
// Handle git field separately to merge properly
const { git: gitUpdate, ...restInput } = input;
@ -182,6 +198,17 @@ export class TaskService {
await fs.writeFile(filepath, content, 'utf-8');
// Emit telemetry event if status changed
if (statusChanged) {
await this.telemetry.emit<TaskTelemetryEvent>({
type: 'task.status_changed',
taskId: updatedTask.id,
project: updatedTask.project,
status: updatedTask.status,
previousStatus,
});
}
return updatedTask;
}
@ -205,6 +232,14 @@ export class TaskService {
await fs.rename(sourcePath, destPath);
// Emit telemetry event
await this.telemetry.emit<TaskTelemetryEvent>({
type: 'task.archived',
taskId: task.id,
project: task.project,
status: task.status,
});
return true;
}
@ -254,6 +289,14 @@ export class TaskService {
const content = this.taskToMarkdown(restoredTask);
await fs.writeFile(destPath, content, 'utf-8');
// Emit telemetry event
await this.telemetry.emit<TaskTelemetryEvent>({
type: 'task.restored',
taskId: restoredTask.id,
project: restoredTask.project,
status: restoredTask.status,
});
return restoredTask;
}