fix: add complexity score to tm list and tm show (#1270)
This commit is contained in:
8
.changeset/whole-pigs-say.md
Normal file
8
.changeset/whole-pigs-say.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix complexity score not showing for `task-master show` and `task-master list`
|
||||||
|
|
||||||
|
- Added complexity score on "next task" when running `task-master list`
|
||||||
|
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||||
@@ -281,9 +281,14 @@ export class ListTasksCommand extends Command {
|
|||||||
const priorityBreakdown = getPriorityBreakdown(tasks);
|
const priorityBreakdown = getPriorityBreakdown(tasks);
|
||||||
|
|
||||||
// Find next task following the same logic as findNextTask
|
// Find next task following the same logic as findNextTask
|
||||||
const nextTask = this.findNextTask(tasks);
|
const nextTaskInfo = this.findNextTask(tasks);
|
||||||
|
|
||||||
// Display dashboard boxes
|
// Get the full task object with complexity data already included
|
||||||
|
const nextTask = nextTaskInfo
|
||||||
|
? tasks.find((t) => String(t.id) === String(nextTaskInfo.id))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Display dashboard boxes (nextTask already has complexity from storage enrichment)
|
||||||
displayDashboards(
|
displayDashboards(
|
||||||
taskStats,
|
taskStats,
|
||||||
subtaskStats,
|
subtaskStats,
|
||||||
@@ -303,14 +308,16 @@ export class ListTasksCommand extends Command {
|
|||||||
|
|
||||||
// Display recommended next task section immediately after table
|
// Display recommended next task section immediately after table
|
||||||
if (nextTask) {
|
if (nextTask) {
|
||||||
// Find the full task object to get description
|
const description = getTaskDescription(nextTask);
|
||||||
const fullTask = tasks.find((t) => String(t.id) === String(nextTask.id));
|
|
||||||
const description = fullTask ? getTaskDescription(fullTask) : undefined;
|
|
||||||
|
|
||||||
displayRecommendedNextTask({
|
displayRecommendedNextTask({
|
||||||
...nextTask,
|
id: nextTask.id,
|
||||||
status: 'pending', // Next task is typically pending
|
title: nextTask.title,
|
||||||
description
|
priority: nextTask.priority,
|
||||||
|
status: nextTask.status,
|
||||||
|
dependencies: nextTask.dependencies,
|
||||||
|
description,
|
||||||
|
complexity: nextTask.complexity as number | undefined
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
displayRecommendedNextTask(undefined);
|
displayRecommendedNextTask(undefined);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task, TaskPriority } from '@tm/core/types';
|
import type { Task, TaskPriority } from '@tm/core/types';
|
||||||
|
import { getComplexityWithColor } from '../../utils/ui.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Statistics for task collection
|
* Statistics for task collection
|
||||||
@@ -479,7 +480,7 @@ export function displayDependencyDashboard(
|
|||||||
? chalk.cyan(nextTask.dependencies.join(', '))
|
? chalk.cyan(nextTask.dependencies.join(', '))
|
||||||
: chalk.gray('None')
|
: chalk.gray('None')
|
||||||
}\n` +
|
}\n` +
|
||||||
`Complexity: ${nextTask?.complexity || chalk.gray('N/A')}`;
|
`Complexity: ${nextTask?.complexity !== undefined ? getComplexityWithColor(nextTask.complexity) : chalk.gray('N/A')}`;
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core/types';
|
||||||
|
import { getComplexityWithColor } from '../../utils/ui.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next task display options
|
* Next task display options
|
||||||
@@ -17,6 +18,7 @@ export interface NextTaskDisplayOptions {
|
|||||||
status?: string;
|
status?: string;
|
||||||
dependencies?: (string | number)[];
|
dependencies?: (string | number)[];
|
||||||
description?: string;
|
description?: string;
|
||||||
|
complexity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +84,11 @@ export function displayRecommendedNextTask(
|
|||||||
: chalk.cyan(task.dependencies.join(', '));
|
: chalk.cyan(task.dependencies.join(', '));
|
||||||
content.push(`Dependencies: ${depsDisplay}`);
|
content.push(`Dependencies: ${depsDisplay}`);
|
||||||
|
|
||||||
|
// Complexity with color and label
|
||||||
|
if (typeof task.complexity === 'number') {
|
||||||
|
content.push(`Complexity: ${getComplexityWithColor(task.complexity)}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Description if available
|
// Description if available
|
||||||
if (task.description) {
|
if (task.description) {
|
||||||
content.push('');
|
content.push('');
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import Table from 'cli-table3';
|
|||||||
import { marked, MarkedExtension } from 'marked';
|
import { marked, MarkedExtension } from 'marked';
|
||||||
import { markedTerminal } from 'marked-terminal';
|
import { markedTerminal } from 'marked-terminal';
|
||||||
import type { Task } from '@tm/core/types';
|
import type { Task } from '@tm/core/types';
|
||||||
import { getStatusWithColor, getPriorityWithColor } from '../../utils/ui.js';
|
import {
|
||||||
|
getStatusWithColor,
|
||||||
|
getPriorityWithColor,
|
||||||
|
getComplexityWithColor
|
||||||
|
} from '../../utils/ui.js';
|
||||||
|
|
||||||
// Configure marked to use terminal renderer with subtle colors
|
// Configure marked to use terminal renderer with subtle colors
|
||||||
marked.use(
|
marked.use(
|
||||||
@@ -108,7 +112,9 @@ export function displayTaskProperties(task: Task): void {
|
|||||||
getStatusWithColor(task.status),
|
getStatusWithColor(task.status),
|
||||||
getPriorityWithColor(task.priority),
|
getPriorityWithColor(task.priority),
|
||||||
deps,
|
deps,
|
||||||
'N/A',
|
typeof task.complexity === 'number'
|
||||||
|
? getComplexityWithColor(task.complexity)
|
||||||
|
: chalk.gray('N/A'),
|
||||||
task.description || ''
|
task.description || ''
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,23 @@ export function getPriorityWithColor(priority: TaskPriority): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get colored complexity display
|
* Get complexity color and label based on score thresholds
|
||||||
|
*/
|
||||||
|
function getComplexityLevel(score: number): {
|
||||||
|
color: (text: string) => string;
|
||||||
|
label: string;
|
||||||
|
} {
|
||||||
|
if (score >= 7) {
|
||||||
|
return { color: chalk.hex('#CC0000'), label: 'High' };
|
||||||
|
} else if (score >= 4) {
|
||||||
|
return { color: chalk.hex('#FF8800'), label: 'Medium' };
|
||||||
|
} else {
|
||||||
|
return { color: chalk.green, label: 'Low' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get colored complexity display with dot indicator (simple format)
|
||||||
*/
|
*/
|
||||||
export function getComplexityWithColor(complexity: number | string): string {
|
export function getComplexityWithColor(complexity: number | string): string {
|
||||||
const score =
|
const score =
|
||||||
@@ -94,13 +110,20 @@ export function getComplexityWithColor(complexity: number | string): string {
|
|||||||
return chalk.gray('N/A');
|
return chalk.gray('N/A');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (score >= 8) {
|
const { color } = getComplexityLevel(score);
|
||||||
return chalk.red.bold(`${score} (High)`);
|
return color(`● ${score}`);
|
||||||
} else if (score >= 5) {
|
}
|
||||||
return chalk.yellow(`${score} (Medium)`);
|
|
||||||
} else {
|
/**
|
||||||
return chalk.green(`${score} (Low)`);
|
* Get colored complexity display with /10 format (for dashboards)
|
||||||
|
*/
|
||||||
|
export function getComplexityWithScore(complexity: number | undefined): string {
|
||||||
|
if (typeof complexity !== 'number') {
|
||||||
|
return chalk.gray('N/A');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { color, label } = getComplexityLevel(complexity);
|
||||||
|
return color(`${complexity}/10 (${label})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,8 +346,12 @@ export function createTaskTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showComplexity) {
|
if (showComplexity) {
|
||||||
// Show N/A if no complexity score
|
// Show complexity score from report if available
|
||||||
row.push(chalk.gray('N/A'));
|
if (typeof task.complexity === 'number') {
|
||||||
|
row.push(getComplexityWithColor(task.complexity));
|
||||||
|
} else {
|
||||||
|
row.push(chalk.gray('N/A'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.push(row);
|
table.push(row);
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ export class TaskEntity implements Task {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
complexity?: Task['complexity'];
|
complexity?: Task['complexity'];
|
||||||
|
recommendedSubtasks?: number;
|
||||||
|
expansionPrompt?: string;
|
||||||
|
complexityReasoning?: string;
|
||||||
|
|
||||||
constructor(data: Task | (Omit<Task, 'id'> & { id: number | string })) {
|
constructor(data: Task | (Omit<Task, 'id'> & { id: number | string })) {
|
||||||
this.validate(data);
|
this.validate(data);
|
||||||
@@ -62,6 +65,9 @@ export class TaskEntity implements Task {
|
|||||||
this.tags = data.tags;
|
this.tags = data.tags;
|
||||||
this.assignee = data.assignee;
|
this.assignee = data.assignee;
|
||||||
this.complexity = data.complexity;
|
this.complexity = data.complexity;
|
||||||
|
this.recommendedSubtasks = data.recommendedSubtasks;
|
||||||
|
this.expansionPrompt = data.expansionPrompt;
|
||||||
|
this.complexityReasoning = data.complexityReasoning;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +252,10 @@ export class TaskEntity implements Task {
|
|||||||
actualEffort: this.actualEffort,
|
actualEffort: this.actualEffort,
|
||||||
tags: this.tags,
|
tags: this.tags,
|
||||||
assignee: this.assignee,
|
assignee: this.assignee,
|
||||||
complexity: this.complexity
|
complexity: this.complexity,
|
||||||
|
recommendedSubtasks: this.recommendedSubtasks,
|
||||||
|
expansionPrompt: this.expansionPrompt,
|
||||||
|
complexityReasoning: this.complexityReasoning
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,3 +61,12 @@ export { getLogger, createLogger, setGlobalLogger } from './logger/index.js';
|
|||||||
|
|
||||||
// Re-export executors
|
// Re-export executors
|
||||||
export * from './executors/index.js';
|
export * from './executors/index.js';
|
||||||
|
|
||||||
|
// Re-export reports
|
||||||
|
export {
|
||||||
|
ComplexityReportManager,
|
||||||
|
type ComplexityReport,
|
||||||
|
type ComplexityReportMetadata,
|
||||||
|
type ComplexityAnalysis,
|
||||||
|
type TaskComplexityData
|
||||||
|
} from './reports/index.js';
|
||||||
|
|||||||
185
packages/tm-core/src/reports/complexity-report-manager.ts
Normal file
185
packages/tm-core/src/reports/complexity-report-manager.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview ComplexityReportManager - Handles loading and managing complexity analysis reports
|
||||||
|
* Follows the same pattern as ConfigManager and AuthManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import type {
|
||||||
|
ComplexityReport,
|
||||||
|
ComplexityAnalysis,
|
||||||
|
TaskComplexityData
|
||||||
|
} from './types.js';
|
||||||
|
import { getLogger } from '../logger/index.js';
|
||||||
|
|
||||||
|
const logger = getLogger('ComplexityReportManager');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages complexity analysis reports
|
||||||
|
* Handles loading, caching, and providing complexity data for tasks
|
||||||
|
*/
|
||||||
|
export class ComplexityReportManager {
|
||||||
|
private projectRoot: string;
|
||||||
|
private reportCache: Map<string, ComplexityReport> = new Map();
|
||||||
|
|
||||||
|
constructor(projectRoot: string) {
|
||||||
|
this.projectRoot = projectRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the complexity report file for a given tag
|
||||||
|
*/
|
||||||
|
private getReportPath(tag?: string): string {
|
||||||
|
const reportsDir = path.join(this.projectRoot, '.taskmaster', 'reports');
|
||||||
|
const tagSuffix = tag && tag !== 'master' ? `_${tag}` : '';
|
||||||
|
return path.join(reportsDir, `task-complexity-report${tagSuffix}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load complexity report for a given tag
|
||||||
|
* Results are cached to avoid repeated file reads
|
||||||
|
*/
|
||||||
|
async loadReport(tag?: string): Promise<ComplexityReport | null> {
|
||||||
|
const resolvedTag = tag || 'master';
|
||||||
|
const cacheKey = resolvedTag;
|
||||||
|
|
||||||
|
// Check cache first
|
||||||
|
if (this.reportCache.has(cacheKey)) {
|
||||||
|
return this.reportCache.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportPath = this.getReportPath(tag);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if file exists
|
||||||
|
await fs.access(reportPath);
|
||||||
|
|
||||||
|
// Read and parse the report
|
||||||
|
const content = await fs.readFile(reportPath, 'utf-8');
|
||||||
|
const report = JSON.parse(content) as ComplexityReport;
|
||||||
|
|
||||||
|
// Validate basic structure
|
||||||
|
if (!report.meta || !Array.isArray(report.complexityAnalysis)) {
|
||||||
|
logger.warn(
|
||||||
|
`Invalid complexity report structure at ${reportPath}, ignoring`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the report
|
||||||
|
this.reportCache.set(cacheKey, report);
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Loaded complexity report for tag '${resolvedTag}' with ${report.complexityAnalysis.length} analyses`
|
||||||
|
);
|
||||||
|
|
||||||
|
return report;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
// File doesn't exist - this is normal, not all projects have complexity reports
|
||||||
|
logger.debug(`No complexity report found for tag '${resolvedTag}'`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other errors (parsing, permissions, etc.)
|
||||||
|
logger.warn(
|
||||||
|
`Failed to load complexity report for tag '${resolvedTag}': ${error.message}`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get complexity data for a specific task ID
|
||||||
|
*/
|
||||||
|
async getComplexityForTask(
|
||||||
|
taskId: string | number,
|
||||||
|
tag?: string
|
||||||
|
): Promise<TaskComplexityData | null> {
|
||||||
|
const report = await this.loadReport(tag);
|
||||||
|
if (!report) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the analysis for this task
|
||||||
|
const analysis = report.complexityAnalysis.find(
|
||||||
|
(a) => String(a.taskId) === String(taskId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!analysis) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to TaskComplexityData format
|
||||||
|
return {
|
||||||
|
complexityScore: analysis.complexityScore,
|
||||||
|
recommendedSubtasks: analysis.recommendedSubtasks,
|
||||||
|
expansionPrompt: analysis.expansionPrompt,
|
||||||
|
complexityReasoning: analysis.complexityReasoning
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get complexity data for multiple tasks at once
|
||||||
|
* More efficient than calling getComplexityForTask multiple times
|
||||||
|
*/
|
||||||
|
async getComplexityForTasks(
|
||||||
|
taskIds: (string | number)[],
|
||||||
|
tag?: string
|
||||||
|
): Promise<Map<string, TaskComplexityData>> {
|
||||||
|
const result = new Map<string, TaskComplexityData>();
|
||||||
|
const report = await this.loadReport(tag);
|
||||||
|
|
||||||
|
if (!report) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map for fast lookups
|
||||||
|
const analysisMap = new Map<string, ComplexityAnalysis>();
|
||||||
|
report.complexityAnalysis.forEach((analysis) => {
|
||||||
|
analysisMap.set(String(analysis.taskId), analysis);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Map each task ID to its complexity data
|
||||||
|
taskIds.forEach((taskId) => {
|
||||||
|
const analysis = analysisMap.get(String(taskId));
|
||||||
|
if (analysis) {
|
||||||
|
result.set(String(taskId), {
|
||||||
|
complexityScore: analysis.complexityScore,
|
||||||
|
recommendedSubtasks: analysis.recommendedSubtasks,
|
||||||
|
expansionPrompt: analysis.expansionPrompt,
|
||||||
|
complexityReasoning: analysis.complexityReasoning
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the report cache
|
||||||
|
* @param tag - Specific tag to clear, or undefined to clear all cached reports
|
||||||
|
* Useful when reports are regenerated or modified externally
|
||||||
|
*/
|
||||||
|
clearCache(tag?: string): void {
|
||||||
|
if (tag) {
|
||||||
|
this.reportCache.delete(tag);
|
||||||
|
} else {
|
||||||
|
// Clear all cached reports
|
||||||
|
this.reportCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a complexity report exists for a tag
|
||||||
|
*/
|
||||||
|
async hasReport(tag?: string): Promise<boolean> {
|
||||||
|
const reportPath = this.getReportPath(tag);
|
||||||
|
try {
|
||||||
|
await fs.access(reportPath);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
packages/tm-core/src/reports/index.ts
Normal file
11
packages/tm-core/src/reports/index.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Reports module exports
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { ComplexityReportManager } from './complexity-report-manager.js';
|
||||||
|
export type {
|
||||||
|
ComplexityReport,
|
||||||
|
ComplexityReportMetadata,
|
||||||
|
ComplexityAnalysis,
|
||||||
|
TaskComplexityData
|
||||||
|
} from './types.js';
|
||||||
65
packages/tm-core/src/reports/types.ts
Normal file
65
packages/tm-core/src/reports/types.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Type definitions for complexity analysis reports
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analysis result for a single task
|
||||||
|
*/
|
||||||
|
export interface ComplexityAnalysis {
|
||||||
|
/** Task ID being analyzed */
|
||||||
|
taskId: string | number;
|
||||||
|
/** Task title */
|
||||||
|
taskTitle: string;
|
||||||
|
/** Complexity score (1-10 scale) */
|
||||||
|
complexityScore: number;
|
||||||
|
/** Recommended number of subtasks */
|
||||||
|
recommendedSubtasks: number;
|
||||||
|
/** AI-generated prompt for task expansion */
|
||||||
|
expansionPrompt: string;
|
||||||
|
/** Reasoning behind the complexity assessment */
|
||||||
|
complexityReasoning: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about the complexity report
|
||||||
|
*/
|
||||||
|
export interface ComplexityReportMetadata {
|
||||||
|
/** When the report was generated */
|
||||||
|
generatedAt: string;
|
||||||
|
/** Number of tasks analyzed in this run */
|
||||||
|
tasksAnalyzed: number;
|
||||||
|
/** Total number of tasks in the file */
|
||||||
|
totalTasks?: number;
|
||||||
|
/** Total analyses in the report (across all runs) */
|
||||||
|
analysisCount?: number;
|
||||||
|
/** Complexity threshold score used */
|
||||||
|
thresholdScore: number;
|
||||||
|
/** Project name */
|
||||||
|
projectName?: string;
|
||||||
|
/** Whether research mode was used */
|
||||||
|
usedResearch: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete complexity analysis report
|
||||||
|
*/
|
||||||
|
export interface ComplexityReport {
|
||||||
|
/** Report metadata */
|
||||||
|
meta: ComplexityReportMetadata;
|
||||||
|
/** Array of complexity analyses */
|
||||||
|
complexityAnalysis: ComplexityAnalysis[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complexity data to be attached to a Task
|
||||||
|
*/
|
||||||
|
export interface TaskComplexityData {
|
||||||
|
/** Complexity score (1-10 scale) */
|
||||||
|
complexityScore?: number;
|
||||||
|
/** Recommended number of subtasks */
|
||||||
|
recommendedSubtasks?: number;
|
||||||
|
/** AI-generated expansion prompt */
|
||||||
|
expansionPrompt?: string;
|
||||||
|
/** Reasoning behind the assessment */
|
||||||
|
complexityReasoning?: string;
|
||||||
|
}
|
||||||
@@ -397,16 +397,6 @@ export class TaskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complexity filter
|
|
||||||
if (filter.complexity) {
|
|
||||||
const complexities = Array.isArray(filter.complexity)
|
|
||||||
? filter.complexity
|
|
||||||
: [filter.complexity];
|
|
||||||
if (!task.complexity || !complexities.includes(task.complexity)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search filter
|
// Search filter
|
||||||
if (filter.search) {
|
if (filter.search) {
|
||||||
const searchLower = filter.search.toLowerCase();
|
const searchLower = filter.search.toLowerCase();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
import { FormatHandler } from './format-handler.js';
|
import { FormatHandler } from './format-handler.js';
|
||||||
import { FileOperations } from './file-operations.js';
|
import { FileOperations } from './file-operations.js';
|
||||||
import { PathResolver } from './path-resolver.js';
|
import { PathResolver } from './path-resolver.js';
|
||||||
|
import { ComplexityReportManager } from '../../reports/complexity-report-manager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File-based storage implementation using a single tasks.json file with separated concerns
|
* File-based storage implementation using a single tasks.json file with separated concerns
|
||||||
@@ -19,11 +20,13 @@ export class FileStorage implements IStorage {
|
|||||||
private formatHandler: FormatHandler;
|
private formatHandler: FormatHandler;
|
||||||
private fileOps: FileOperations;
|
private fileOps: FileOperations;
|
||||||
private pathResolver: PathResolver;
|
private pathResolver: PathResolver;
|
||||||
|
private complexityManager: ComplexityReportManager;
|
||||||
|
|
||||||
constructor(projectPath: string) {
|
constructor(projectPath: string) {
|
||||||
this.formatHandler = new FormatHandler();
|
this.formatHandler = new FormatHandler();
|
||||||
this.fileOps = new FileOperations();
|
this.fileOps = new FileOperations();
|
||||||
this.pathResolver = new PathResolver(projectPath);
|
this.pathResolver = new PathResolver(projectPath);
|
||||||
|
this.complexityManager = new ComplexityReportManager(projectPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,6 +90,7 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load tasks from the single tasks.json file for a specific tag
|
* Load tasks from the single tasks.json file for a specific tag
|
||||||
|
* Enriches tasks with complexity data from the complexity report
|
||||||
*/
|
*/
|
||||||
async loadTasks(tag?: string): Promise<Task[]> {
|
async loadTasks(tag?: string): Promise<Task[]> {
|
||||||
const filePath = this.pathResolver.getTasksPath();
|
const filePath = this.pathResolver.getTasksPath();
|
||||||
@@ -94,7 +98,10 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const rawData = await this.fileOps.readJson(filePath);
|
const rawData = await this.fileOps.readJson(filePath);
|
||||||
return this.formatHandler.extractTasks(rawData, resolvedTag);
|
const tasks = this.formatHandler.extractTasks(rawData, resolvedTag);
|
||||||
|
|
||||||
|
// Enrich tasks with complexity data
|
||||||
|
return await this.enrichTasksWithComplexity(tasks, resolvedTag);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
return []; // File doesn't exist, return empty array
|
return []; // File doesn't exist, return empty array
|
||||||
@@ -596,6 +603,46 @@ export class FileStorage implements IStorage {
|
|||||||
|
|
||||||
await this.saveTasks(tasks, targetTag);
|
await this.saveTasks(tasks, targetTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enrich tasks with complexity data from the complexity report
|
||||||
|
* Private helper method called by loadTasks()
|
||||||
|
*/
|
||||||
|
private async enrichTasksWithComplexity(
|
||||||
|
tasks: Task[],
|
||||||
|
tag: string
|
||||||
|
): Promise<Task[]> {
|
||||||
|
// Get all task IDs for bulk lookup
|
||||||
|
const taskIds = tasks.map((t) => t.id);
|
||||||
|
|
||||||
|
// Load complexity data for all tasks at once (more efficient)
|
||||||
|
const complexityMap = await this.complexityManager.getComplexityForTasks(
|
||||||
|
taskIds,
|
||||||
|
tag
|
||||||
|
);
|
||||||
|
|
||||||
|
// If no complexity data found, return tasks as-is
|
||||||
|
if (complexityMap.size === 0) {
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich each task with its complexity data
|
||||||
|
return tasks.map((task) => {
|
||||||
|
const complexityData = complexityMap.get(String(task.id));
|
||||||
|
if (!complexityData) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge complexity data into the task
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
complexity: complexityData.complexityScore,
|
||||||
|
recommendedSubtasks: complexityData.recommendedSubtasks,
|
||||||
|
expansionPrompt: complexityData.expansionPrompt,
|
||||||
|
complexityReasoning: complexityData.complexityReasoning
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export as default for convenience
|
// Export as default for convenience
|
||||||
|
|||||||
@@ -72,7 +72,13 @@ export interface Task {
|
|||||||
actualEffort?: number;
|
actualEffort?: number;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
complexity?: TaskComplexity;
|
|
||||||
|
// Complexity analysis (from complexity report)
|
||||||
|
// Can be either enum ('simple' | 'moderate' | 'complex' | 'very-complex') or numeric score (1-10)
|
||||||
|
complexity?: TaskComplexity | number;
|
||||||
|
recommendedSubtasks?: number;
|
||||||
|
expansionPrompt?: string;
|
||||||
|
complexityReasoning?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,7 +151,6 @@ export interface TaskFilter {
|
|||||||
hasSubtasks?: boolean;
|
hasSubtasks?: boolean;
|
||||||
search?: string;
|
search?: string;
|
||||||
assignee?: string;
|
assignee?: string;
|
||||||
complexity?: TaskComplexity | TaskComplexity[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user