TaskTracker.ts•12.1 kB
/**
* TaskTracker
*
* Analyzes and tracks task context including:
* - Task type inference from prompts
* - Focus area detection
* - Working files tracking
* - Recent actions history
*/
import * as path from 'path';
import logger from '../utils/Logger';
// Task types that can be inferred
export enum TaskType {
CREATION = 'creation',
DEBUGGING = 'debugging',
REFACTORING = 'refactoring',
STYLING = 'styling',
TESTING = 'testing',
DOCUMENTATION = 'documentation',
ANALYSIS = 'analysis',
API = 'api',
GENERAL = 'general'
}
// Focus areas within the codebase
export enum FocusArea {
FRONTEND = 'frontend',
BACKEND = 'backend',
DATABASE = 'database',
AUTHENTICATION = 'authentication',
COMPONENTS = 'components',
STATE = 'state',
ROUTING = 'routing',
STYLING = 'styling',
TESTING = 'testing',
DEPLOYMENT = 'deployment',
GENERAL = 'general'
}
// Task action history item
export interface TaskAction {
timestamp: number;
action: string;
files?: string[];
description: string;
}
// Task context information
export interface TaskContext {
taskType: TaskType;
focusArea: FocusArea;
relevantFiles: string[];
currentPrompt: string;
recentActions: TaskAction[];
taskStartTime: number;
}
// Keywords for task type detection
const TASK_TYPE_KEYWORDS: Record<TaskType, string[]> = {
[TaskType.CREATION]: [
'create', 'new', 'add', 'implement', 'build', 'generate', 'make'
],
[TaskType.DEBUGGING]: [
'debug', 'fix', 'issue', 'bug', 'error', 'problem', 'crash', 'not working'
],
[TaskType.REFACTORING]: [
'refactor', 'improve', 'optimize', 'clean', 'restructure', 'enhance', 'rewrite'
],
[TaskType.STYLING]: [
'style', 'css', 'design', 'layout', 'ui', 'appearance', 'look', 'theme'
],
[TaskType.TESTING]: [
'test', 'unit test', 'integration test', 'e2e', 'end-to-end', 'testing', 'jest', 'spec'
],
[TaskType.DOCUMENTATION]: [
'document', 'comment', 'readme', 'docs', 'documentation', 'explain'
],
[TaskType.ANALYSIS]: [
'analyze', 'review', 'check', 'evaluate', 'assess', 'examine'
],
[TaskType.API]: [
'api', 'endpoint', 'request', 'response', 'fetch', 'http', 'rest', 'graphql'
],
[TaskType.GENERAL]: [
'help', 'how', 'what', 'guide', 'learn', 'understand'
]
};
// Keywords for focus area detection
const FOCUS_AREA_KEYWORDS: Record<FocusArea, string[]> = {
[FocusArea.FRONTEND]: [
'component', 'react', 'vue', 'angular', 'ui', 'interface', 'jsx', 'tsx', 'html'
],
[FocusArea.BACKEND]: [
'server', 'api', 'endpoint', 'controller', 'service', 'middleware', 'express', 'node'
],
[FocusArea.DATABASE]: [
'database', 'db', 'model', 'schema', 'query', 'sql', 'nosql', 'mongo', 'postgres'
],
[FocusArea.AUTHENTICATION]: [
'auth', 'login', 'register', 'user', 'permission', 'role', 'jwt', 'session'
],
[FocusArea.COMPONENTS]: [
'component', 'button', 'form', 'input', 'modal', 'menu', 'navigation', 'sidebar'
],
[FocusArea.STATE]: [
'state', 'store', 'redux', 'context', 'recoil', 'zustand', 'mobx', 'useState'
],
[FocusArea.ROUTING]: [
'route', 'router', 'navigation', 'link', 'path', 'url', 'history', 'params'
],
[FocusArea.STYLING]: [
'css', 'scss', 'style', 'tailwind', 'theme', 'layout', 'responsive', 'design'
],
[FocusArea.TESTING]: [
'test', 'jest', 'enzyme', 'testing-library', 'cypress', 'selenium', 'unit', 'e2e'
],
[FocusArea.DEPLOYMENT]: [
'deploy', 'build', 'ci', 'cd', 'pipeline', 'docker', 'kubernetes', 'aws', 'cloud'
],
[FocusArea.GENERAL]: [
'general', 'help', 'guide', 'project', 'setup', 'configuration'
]
};
// File extension to focus area mapping
const FILE_EXTENSION_FOCUS: Record<string, FocusArea> = {
'.jsx': FocusArea.FRONTEND,
'.tsx': FocusArea.FRONTEND,
'.js': FocusArea.GENERAL,
'.ts': FocusArea.GENERAL,
'.css': FocusArea.STYLING,
'.scss': FocusArea.STYLING,
'.html': FocusArea.FRONTEND,
'.json': FocusArea.GENERAL,
'.md': FocusArea.GENERAL, // Changed from DOCUMENTATION which doesn't exist in enum
'.test.js': FocusArea.TESTING,
'.test.ts': FocusArea.TESTING,
'.spec.js': FocusArea.TESTING,
'.spec.ts': FocusArea.TESTING,
};
export class TaskTracker {
private currentTask: TaskContext;
private log = logger.createChildLogger('TaskTracker');
private maxRecentActions: number;
constructor(initialTaskType: TaskType = TaskType.GENERAL) {
this.maxRecentActions = 10;
// Initialize with default task context
this.currentTask = {
taskType: initialTaskType,
focusArea: FocusArea.GENERAL,
relevantFiles: [],
currentPrompt: '',
recentActions: [],
taskStartTime: Date.now(),
};
this.log.info(`Task tracker initialized with task type: ${initialTaskType}`);
}
/**
* Get current task context
*/
public getTaskContext(): TaskContext {
return { ...this.currentTask };
}
/**
* Reset task context with a new task
*/
public resetTask(taskType?: TaskType, prompt?: string): void {
this.currentTask = {
taskType: taskType || TaskType.GENERAL,
focusArea: FocusArea.GENERAL,
relevantFiles: [],
currentPrompt: prompt || '',
recentActions: [],
taskStartTime: Date.now(),
};
this.log.info(`Task context reset with task type: ${this.currentTask.taskType}`);
// Infer task details if prompt is provided
if (prompt) {
this.updateFromPrompt(prompt);
}
}
/**
* Update task context based on a new prompt
*/
public updateFromPrompt(prompt: string): TaskContext {
this.log.debug('Updating task context from prompt');
// Save the current prompt
this.currentTask.currentPrompt = prompt;
// Infer task type if not explicitly set
if (this.currentTask.taskType === TaskType.GENERAL) {
this.currentTask.taskType = this.inferTaskType(prompt);
this.log.debug(`Inferred task type: ${this.currentTask.taskType}`);
}
// Infer focus area
this.currentTask.focusArea = this.inferFocusArea(prompt);
this.log.debug(`Inferred focus area: ${this.currentTask.focusArea}`);
// Extract potential file references
this.extractFileReferences(prompt);
return this.getTaskContext();
}
/**
* Infer the type of task from a prompt
*/
public inferTaskType(prompt: string): TaskType {
const normalizedPrompt = prompt.toLowerCase();
// Calculate scores for each task type based on keyword matches
const scores: Record<TaskType, number> = Object.values(TaskType).reduce((acc, type) => {
acc[type] = 0;
return acc;
}, {} as Record<TaskType, number>);
// Check for keywords for each task type
for (const [taskType, keywords] of Object.entries(TASK_TYPE_KEYWORDS)) {
for (const keyword of keywords) {
if (normalizedPrompt.includes(keyword.toLowerCase())) {
scores[taskType as TaskType] += 1;
}
}
}
// Find the task type with the highest score
let bestMatch = TaskType.GENERAL;
let highestScore = 0;
for (const [type, score] of Object.entries(scores)) {
if (score > highestScore) {
highestScore = score;
bestMatch = type as TaskType;
}
}
return bestMatch;
}
/**
* Infer the focus area from a prompt
*/
public inferFocusArea(prompt: string): FocusArea {
const normalizedPrompt = prompt.toLowerCase();
// Calculate scores for each focus area based on keyword matches
const scores: Record<FocusArea, number> = Object.values(FocusArea).reduce((acc, area) => {
acc[area] = 0;
return acc;
}, {} as Record<FocusArea, number>);
// Check for keywords for each focus area
for (const [area, keywords] of Object.entries(FOCUS_AREA_KEYWORDS)) {
for (const keyword of keywords) {
if (normalizedPrompt.includes(keyword.toLowerCase())) {
scores[area as FocusArea] += 1;
}
}
}
// Find the focus area with the highest score
let bestMatch = FocusArea.GENERAL;
let highestScore = 0;
for (const [area, score] of Object.entries(scores)) {
if (score > highestScore) {
highestScore = score;
bestMatch = area as FocusArea;
}
}
return bestMatch;
}
/**
* Extract file references from a prompt
*/
private extractFileReferences(prompt: string): void {
// Simple file path extraction with common extensions
const filePathRegex = /[\w/.-]+\.(js|jsx|ts|tsx|css|scss|html|json|md|py|java|rb|go|php)/g;
const matches = prompt.match(filePathRegex) || [];
// Add extracted files to relevant files if not already there
for (const match of matches) {
if (!this.currentTask.relevantFiles.includes(match)) {
this.currentTask.relevantFiles.push(match);
this.log.debug(`Added relevant file from prompt: ${match}`);
}
}
}
/**
* Record a new action in the task history
*/
public recordAction(action: string, files?: string[], description?: string): void {
const taskAction: TaskAction = {
timestamp: Date.now(),
action,
files,
description: description || action
};
// Add to recent actions, maintaining max size
this.currentTask.recentActions.unshift(taskAction);
if (this.currentTask.recentActions.length > this.maxRecentActions) {
this.currentTask.recentActions = this.currentTask.recentActions.slice(0, this.maxRecentActions);
}
// Add files to relevant files list if not already there
if (files) {
for (const file of files) {
if (!this.currentTask.relevantFiles.includes(file)) {
this.currentTask.relevantFiles.push(file);
}
}
}
this.log.debug(`Recorded action: ${action}`);
}
/**
* Add relevant files to the task context
*/
public addRelevantFiles(files: string[]): void {
for (const file of files) {
if (!this.currentTask.relevantFiles.includes(file)) {
this.currentTask.relevantFiles.push(file);
// Update focus area based on file extension if current area is general
if (this.currentTask.focusArea === FocusArea.GENERAL) {
const ext = path.extname(file);
if (FILE_EXTENSION_FOCUS[ext]) {
this.currentTask.focusArea = FILE_EXTENSION_FOCUS[ext];
this.log.debug(`Updated focus area to ${this.currentTask.focusArea} based on file extension`);
}
}
}
}
this.log.debug(`Added relevant files: ${files.join(', ')}`);
}
/**
* Explicitly set the task type
*/
public setTaskType(taskType: TaskType): void {
this.currentTask.taskType = taskType;
this.log.info(`Task type explicitly set to: ${taskType}`);
}
/**
* Explicitly set the focus area
*/
public setFocusArea(focusArea: FocusArea): void {
this.currentTask.focusArea = focusArea;
this.log.info(`Focus area explicitly set to: ${focusArea}`);
}
/**
* Get task duration in seconds
*/
public getTaskDuration(): number {
return Math.floor((Date.now() - this.currentTask.taskStartTime) / 1000);
}
/**
* Generate a summary of the current task context
*/
public generateTaskSummary(): string {
const { taskType, focusArea, relevantFiles, recentActions } = this.currentTask;
const duration = this.getTaskDuration();
let summary = `Task Type: ${taskType}\n`;
summary += `Focus Area: ${focusArea}\n`;
summary += `Duration: ${duration} seconds\n\n`;
if (relevantFiles.length > 0) {
summary += `Relevant Files:\n${relevantFiles.join('\n')}\n\n`;
}
if (recentActions.length > 0) {
summary += 'Recent Actions:\n';
for (const action of recentActions) {
const timeAgo = Math.floor((Date.now() - action.timestamp) / 1000);
summary += `- ${action.description} (${timeAgo}s ago)\n`;
}
}
return summary;
}
}
export default TaskTracker;